summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt6
-rw-r--r--src/audio_core/cubeb_sink.cpp2
-rw-r--r--src/audio_core/stream.cpp7
-rw-r--r--src/common/CMakeLists.txt2
-rw-r--r--src/common/algorithm.h3
-rw-r--r--src/common/alignment.h14
-rw-r--r--src/common/assert.h7
-rw-r--r--src/common/atomic_ops.cpp37
-rw-r--r--src/common/atomic_ops.h10
-rw-r--r--src/common/bit_field.h19
-rw-r--r--src/common/bit_util.h34
-rw-r--r--src/common/cityhash.h23
-rw-r--r--src/common/color.h38
-rw-r--r--src/common/common_funcs.h28
-rw-r--r--src/common/common_paths.h1
-rw-r--r--src/common/concepts.h34
-rw-r--r--src/common/detached_tasks.cpp3
-rw-r--r--src/common/dynamic_library.cpp2
-rw-r--r--src/common/dynamic_library.h13
-rw-r--r--src/common/fiber.h2
-rw-r--r--src/common/file_util.cpp69
-rw-r--r--src/common/file_util.h86
-rw-r--r--src/common/hash.h25
-rw-r--r--src/common/hex_util.cpp34
-rw-r--r--src/common/hex_util.h33
-rw-r--r--src/common/logging/backend.cpp22
-rw-r--r--src/common/logging/backend.h16
-rw-r--r--src/common/lz4_compression.cpp16
-rw-r--r--src/common/lz4_compression.h24
-rw-r--r--src/common/math_util.h14
-rw-r--r--src/common/memory_detect.h2
-rw-r--r--src/common/multi_level_queue.h37
-rw-r--r--src/common/page_table.h4
-rw-r--r--src/common/param_package.h12
-rw-r--r--src/common/quaternion.h45
-rw-r--r--src/common/ring_buffer.h4
-rw-r--r--src/common/spin_lock.h2
-rw-r--r--src/common/string_util.h46
-rw-r--r--src/common/telemetry.cpp4
-rw-r--r--src/common/telemetry.h16
-rw-r--r--src/common/thread.cpp12
-rw-r--r--src/common/thread.h9
-rw-r--r--src/common/thread_queue_list.h10
-rw-r--r--src/common/threadsafe_queue.h12
-rw-r--r--src/common/time_zone.h4
-rw-r--r--src/common/timer.h16
-rw-r--r--src/common/uint128.h6
-rw-r--r--src/common/uuid.h14
-rw-r--r--src/common/vector_math.h204
-rw-r--r--src/common/virtual_buffer.cpp11
-rw-r--r--src/common/virtual_buffer.h10
-rw-r--r--src/common/wall_clock.h16
-rw-r--r--src/common/x64/xbyak_abi.h32
-rw-r--r--src/common/zstd_compression.cpp1
-rw-r--r--src/common/zstd_compression.h17
-rw-r--r--src/core/CMakeLists.txt7
-rw-r--r--src/core/arm/cpu_interrupt_handler.cpp6
-rw-r--r--src/core/arm/cpu_interrupt_handler.h3
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.cpp13
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.cpp13
-rw-r--r--src/core/core.cpp17
-rw-r--r--src/core/core_timing.cpp28
-rw-r--r--src/core/core_timing.h7
-rw-r--r--src/core/core_timing_util.cpp1
-rw-r--r--src/core/core_timing_util.h1
-rw-r--r--src/core/cpu_manager.cpp37
-rw-r--r--src/core/crypto/aes_util.cpp23
-rw-r--r--src/core/crypto/aes_util.h9
-rw-r--r--src/core/crypto/ctr_encryption_layer.cpp9
-rw-r--r--src/core/crypto/ctr_encryption_layer.h9
-rw-r--r--src/core/crypto/key_manager.cpp449
-rw-r--r--src/core/crypto/key_manager.h8
-rw-r--r--src/core/crypto/partition_data_manager.cpp216
-rw-r--r--src/core/device_memory.cpp5
-rw-r--r--src/core/device_memory.h8
-rw-r--r--src/core/file_sys/bis_factory.cpp3
-rw-r--r--src/core/file_sys/bis_factory.h3
-rw-r--r--src/core/file_sys/card_image.cpp5
-rw-r--r--src/core/file_sys/card_image.h7
-rw-r--r--src/core/file_sys/content_archive.cpp12
-rw-r--r--src/core/file_sys/content_archive.h2
-rw-r--r--src/core/file_sys/control_metadata.cpp1
-rw-r--r--src/core/file_sys/control_metadata.h2
-rw-r--r--src/core/file_sys/kernel_executable.cpp3
-rw-r--r--src/core/file_sys/kernel_executable.h9
-rw-r--r--src/core/file_sys/mode.h9
-rw-r--r--src/core/file_sys/nca_metadata.cpp1
-rw-r--r--src/core/file_sys/nca_metadata.h2
-rw-r--r--src/core/file_sys/nca_patch.cpp3
-rw-r--r--src/core/file_sys/partition_filesystem.cpp8
-rw-r--r--src/core/file_sys/partition_filesystem.h8
-rw-r--r--src/core/file_sys/patch_manager.cpp7
-rw-r--r--src/core/file_sys/patch_manager.h6
-rw-r--r--src/core/file_sys/program_metadata.cpp1
-rw-r--r--src/core/file_sys/program_metadata.h2
-rw-r--r--src/core/file_sys/registered_cache.cpp161
-rw-r--r--src/core/file_sys/registered_cache.h12
-rw-r--r--src/core/file_sys/romfs.h1
-rw-r--r--src/core/file_sys/savedata_factory.cpp33
-rw-r--r--src/core/file_sys/savedata_factory.h47
-rw-r--r--src/core/file_sys/sdmc_factory.cpp2
-rw-r--r--src/core/file_sys/sdmc_factory.h2
-rw-r--r--src/core/file_sys/submission_package.cpp2
-rw-r--r--src/core/file_sys/submission_package.h6
-rw-r--r--src/core/file_sys/system_archive/mii_model.cpp18
-rw-r--r--src/core/file_sys/system_archive/ng_word.cpp42
-rw-r--r--src/core/file_sys/system_archive/time_zone_binary.cpp51
-rw-r--r--src/core/file_sys/vfs.cpp56
-rw-r--r--src/core/file_sys/vfs_libzip.cpp2
-rw-r--r--src/core/file_sys/vfs_real.cpp267
-rw-r--r--src/core/file_sys/vfs_real.h8
-rw-r--r--src/core/file_sys/vfs_vector.h13
-rw-r--r--src/core/file_sys/xts_archive.cpp26
-rw-r--r--src/core/file_sys/xts_archive.h10
-rw-r--r--src/core/frontend/applets/controller.cpp81
-rw-r--r--src/core/frontend/applets/controller.h48
-rw-r--r--src/core/frontend/emu_window.h4
-rw-r--r--src/core/frontend/framebuffer_layout.h1
-rw-r--r--src/core/hardware_interrupt_manager.cpp4
-rw-r--r--src/core/hle/ipc_helpers.h4
-rw-r--r--src/core/hle/kernel/address_arbiter.cpp2
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp46
-rw-r--r--src/core/hle/kernel/hle_ipc.h30
-rw-r--r--src/core/hle/kernel/kernel.cpp5
-rw-r--r--src/core/hle/kernel/memory/page_table.cpp23
-rw-r--r--src/core/hle/kernel/memory/system_control.cpp21
-rw-r--r--src/core/hle/kernel/memory/system_control.h5
-rw-r--r--src/core/hle/kernel/scheduler.cpp39
-rw-r--r--src/core/hle/kernel/scheduler.h2
-rw-r--r--src/core/hle/kernel/server_session.cpp6
-rw-r--r--src/core/hle/kernel/time_manager.cpp10
-rw-r--r--src/core/hle/result.h12
-rw-r--r--src/core/hle/service/acc/acc.cpp14
-rw-r--r--src/core/hle/service/acc/profile_manager.cpp65
-rw-r--r--src/core/hle/service/am/am.cpp17
-rw-r--r--src/core/hle/service/am/am.h7
-rw-r--r--src/core/hle/service/am/applets/applets.cpp71
-rw-r--r--src/core/hle/service/am/applets/applets.h19
-rw-r--r--src/core/hle/service/am/applets/controller.cpp210
-rw-r--r--src/core/hle/service/am/applets/controller.h123
-rw-r--r--src/core/hle/service/am/applets/software_keyboard.cpp60
-rw-r--r--src/core/hle/service/am/applets/software_keyboard.h1
-rw-r--r--src/core/hle/service/am/applets/web_browser.cpp19
-rw-r--r--src/core/hle/service/audio/audout_u.cpp4
-rw-r--r--src/core/hle/service/audio/hwopus.cpp2
-rw-r--r--src/core/hle/service/bcat/backend/boxcat.cpp26
-rw-r--r--src/core/hle/service/bcat/module.cpp2
-rw-r--r--src/core/hle/service/es/es.cpp2
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp88
-rw-r--r--src/core/hle/service/filesystem/filesystem.h6
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp85
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.h6
-rw-r--r--src/core/hle/service/hid/controllers/debug_pad.cpp53
-rw-r--r--src/core/hle/service/hid/controllers/keyboard.cpp17
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp361
-rw-r--r--src/core/hle/service/hid/controllers/npad.h19
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.cpp12
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.h1
-rw-r--r--src/core/hle/service/hid/hid.cpp34
-rw-r--r--src/core/hle/service/hid/hid.h2
-rw-r--r--src/core/hle/service/ldr/ldr.cpp5
-rw-r--r--src/core/hle/service/nfp/nfp.cpp8
-rw-r--r--src/core/hle/service/nifm/nifm.cpp13
-rw-r--r--src/core/hle/service/ns/ns.cpp1
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdevice.h3
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp202
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h79
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.cpp33
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.h6
-rw-r--r--src/core/hle/service/nvdrv/interface.cpp36
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue.cpp21
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp4
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.h24
-rw-r--r--src/core/hle/service/service.cpp2
-rw-r--r--src/core/hle/service/set/set.cpp2
-rw-r--r--src/core/hle/service/sm/sm.h5
-rw-r--r--src/core/hle/service/sockets/blocking_worker.h162
-rw-r--r--src/core/hle/service/sockets/bsd.cpp809
-rw-r--r--src/core/hle/service/sockets/bsd.h150
-rw-r--r--src/core/hle/service/sockets/sockets.cpp6
-rw-r--r--src/core/hle/service/sockets/sockets.h85
-rw-r--r--src/core/hle/service/sockets/sockets_translate.cpp165
-rw-r--r--src/core/hle/service/sockets/sockets_translate.h48
-rw-r--r--src/core/hle/service/time/time.cpp4
-rw-r--r--src/core/hle/service/time/time_zone_content_manager.cpp4
-rw-r--r--src/core/hle/service/time/time_zone_service.cpp4
-rw-r--r--src/core/hle/service/vi/vi.cpp28
-rw-r--r--src/core/loader/loader.cpp54
-rw-r--r--src/core/memory.cpp10
-rw-r--r--src/core/memory/cheat_engine.cpp38
-rw-r--r--src/core/memory/cheat_engine.h2
-rw-r--r--src/core/memory/dmnt_cheat_vm.cpp226
-rw-r--r--src/core/perf_stats.cpp24
-rw-r--r--src/core/perf_stats.h11
-rw-r--r--src/core/reporter.cpp9
-rw-r--r--src/core/settings.cpp55
-rw-r--r--src/core/settings.h354
-rw-r--r--src/core/telemetry_session.cpp14
-rw-r--r--src/core/telemetry_session.h5
-rw-r--r--src/core/tools/freezer.cpp42
-rw-r--r--src/core/tools/freezer.h9
-rw-r--r--src/input_common/CMakeLists.txt6
-rw-r--r--src/input_common/gcadapter/gc_adapter.cpp8
-rw-r--r--src/input_common/gcadapter/gc_adapter.h2
-rw-r--r--src/input_common/gcadapter/gc_poller.cpp40
-rw-r--r--src/input_common/main.cpp212
-rw-r--r--src/input_common/main.h132
-rw-r--r--src/input_common/motion_input.cpp181
-rw-r--r--src/input_common/motion_input.h68
-rw-r--r--src/input_common/sdl/sdl.h19
-rw-r--r--src/input_common/sdl/sdl_impl.cpp438
-rw-r--r--src/input_common/sdl/sdl_impl.h8
-rw-r--r--src/input_common/settings.cpp33
-rw-r--r--src/input_common/settings.h335
-rw-r--r--src/input_common/touch_from_button.cpp50
-rw-r--r--src/input_common/touch_from_button.h23
-rw-r--r--src/input_common/udp/client.cpp7
-rw-r--r--src/input_common/udp/udp.cpp14
-rw-r--r--src/input_common/udp/udp.h7
-rw-r--r--src/tests/core/core_timing.cpp18
-rw-r--r--src/video_core/CMakeLists.txt5
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h51
-rw-r--r--src/video_core/compatible_formats.h2
-rw-r--r--src/video_core/engines/fermi_2d.cpp10
-rw-r--r--src/video_core/engines/fermi_2d.h9
-rw-r--r--src/video_core/engines/kepler_compute.cpp17
-rw-r--r--src/video_core/engines/kepler_compute.h16
-rw-r--r--src/video_core/engines/maxwell_3d.cpp35
-rw-r--r--src/video_core/engines/maxwell_3d.h23
-rw-r--r--src/video_core/engines/maxwell_dma.cpp21
-rw-r--r--src/video_core/fence_manager.h32
-rw-r--r--src/video_core/gpu.cpp33
-rw-r--r--src/video_core/gpu.h20
-rw-r--r--src/video_core/gpu_asynch.cpp10
-rw-r--r--src/video_core/gpu_asynch.h4
-rw-r--r--src/video_core/gpu_synch.cpp8
-rw-r--r--src/video_core/gpu_synch.h6
-rw-r--r--src/video_core/host_shaders/CMakeLists.txt43
-rw-r--r--src/video_core/host_shaders/StringShaderHeader.cmake11
-rw-r--r--src/video_core/host_shaders/opengl_present.frag10
-rw-r--r--src/video_core/host_shaders/opengl_present.vert24
-rw-r--r--src/video_core/host_shaders/source_shader.h.in9
-rw-r--r--src/video_core/macro/macro_hle.cpp6
-rw-r--r--src/video_core/macro/macro_interpreter.cpp1
-rw-r--r--src/video_core/macro/macro_jit_x64.cpp10
-rw-r--r--src/video_core/memory_manager.cpp540
-rw-r--r--src/video_core/memory_manager.h189
-rw-r--r--src/video_core/query_cache.h31
-rw-r--r--src/video_core/rasterizer_interface.h7
-rw-r--r--src/video_core/renderer_base.cpp4
-rw-r--r--src/video_core/renderer_base.h17
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp5
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h3
-rw-r--r--src/video_core/renderer_opengl/gl_fence_manager.cpp12
-rw-r--r--src/video_core/renderer_opengl/gl_fence_manager.h7
-rw-r--r--src/video_core/renderer_opengl/gl_query_cache.cpp9
-rw-r--r--src/video_core/renderer_opengl/gl_query_cache.h3
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp276
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h26
-rw-r--r--src/video_core/renderer_opengl/gl_resource_manager.cpp9
-rw-r--r--src/video_core/renderer_opengl/gl_resource_manager.h3
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp66
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.h23
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.cpp67
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.h26
-rw-r--r--src/video_core/renderer_opengl/gl_shader_util.cpp15
-rw-r--r--src/video_core/renderer_opengl/gl_shader_util.h2
-rw-r--r--src/video_core/renderer_opengl/gl_state_tracker.cpp6
-rw-r--r--src/video_core/renderer_opengl/gl_state_tracker.h28
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp12
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h8
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp73
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h28
-rw-r--r--src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp14
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp25
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h19
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp21
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.h9
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp17
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h7
-rw-r--r--src/video_core/renderer_vulkan/vk_device.cpp13
-rw-r--r--src/video_core/renderer_vulkan/vk_device.h8
-rw-r--r--src/video_core/renderer_vulkan/vk_fence_manager.cpp14
-rw-r--r--src/video_core/renderer_vulkan/vk_fence_manager.h8
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp9
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.h28
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp124
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.h59
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.cpp5
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.h3
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp163
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h23
-rw-r--r--src/video_core/renderer_vulkan/vk_sampler_cache.cpp1
-rw-r--r--src/video_core/renderer_vulkan/vk_state_tracker.cpp19
-rw-r--r--src/video_core/renderer_vulkan/vk_state_tracker.h28
-rw-r--r--src/video_core/renderer_vulkan/vk_stream_buffer.cpp4
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp38
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h16
-rw-r--r--src/video_core/renderer_vulkan/wrapper.cpp4
-rw-r--r--src/video_core/renderer_vulkan/wrapper.h30
-rw-r--r--src/video_core/shader/async_shaders.cpp100
-rw-r--r--src/video_core/shader/async_shaders.h61
-rw-r--r--src/video_core/shader/control_flow.cpp30
-rw-r--r--src/video_core/shader/decode/memory.cpp9
-rw-r--r--src/video_core/shader/memory_util.cpp7
-rw-r--r--src/video_core/shader/memory_util.h6
-rw-r--r--src/video_core/texture_cache/surface_params.cpp107
-rw-r--r--src/video_core/texture_cache/surface_params.h5
-rw-r--r--src/video_core/texture_cache/texture_cache.h51
-rw-r--r--src/video_core/textures/decoders.cpp42
-rw-r--r--src/video_core/textures/decoders.h5
-rw-r--r--src/video_core/video_core.cpp35
-rw-r--r--src/web_service/CMakeLists.txt1
-rw-r--r--src/web_service/telemetry_json.cpp6
-rw-r--r--src/web_service/telemetry_json.h30
-rw-r--r--src/web_service/verify_login.cpp2
-rw-r--r--src/web_service/web_backend.cpp56
-rw-r--r--src/web_service/web_backend.h20
-rw-r--r--src/web_service/web_result.h (renamed from src/common/web_result.h)4
-rw-r--r--src/yuzu/CMakeLists.txt22
-rw-r--r--src/yuzu/applets/controller.cpp601
-rw-r--r--src/yuzu/applets/controller.h133
-rw-r--r--src/yuzu/applets/controller.ui2672
-rw-r--r--src/yuzu/applets/profile_select.cpp2
-rw-r--r--src/yuzu/bootmanager.cpp31
-rw-r--r--src/yuzu/bootmanager.h9
-rw-r--r--src/yuzu/compatdb.cpp3
-rw-r--r--src/yuzu/configuration/config.cpp306
-rw-r--r--src/yuzu/configuration/config.h8
-rw-r--r--src/yuzu/configuration/configuration_shared.cpp60
-rw-r--r--src/yuzu/configuration/configuration_shared.h13
-rw-r--r--src/yuzu/configuration/configure.ui59
-rw-r--r--src/yuzu/configuration/configure_audio.cpp13
-rw-r--r--src/yuzu/configuration/configure_cpu.cpp17
-rw-r--r--src/yuzu/configuration/configure_cpu.h1
-rw-r--r--src/yuzu/configuration/configure_cpu.ui52
-rw-r--r--src/yuzu/configuration/configure_debug.cpp3
-rw-r--r--src/yuzu/configuration/configure_debug.ui22
-rw-r--r--src/yuzu/configuration/configure_debug_controller.cpp40
-rw-r--r--src/yuzu/configuration/configure_debug_controller.h38
-rw-r--r--src/yuzu/configuration/configure_debug_controller.ui97
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp13
-rw-r--r--src/yuzu/configuration/configure_dialog.h7
-rw-r--r--src/yuzu/configuration/configure_filesystem.cpp37
-rw-r--r--src/yuzu/configuration/configure_general.cpp6
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp32
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.cpp32
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.h1
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.ui9
-rw-r--r--src/yuzu/configuration/configure_hotkeys.cpp4
-rw-r--r--src/yuzu/configuration/configure_input.cpp287
-rw-r--r--src/yuzu/configuration/configure_input.h31
-rw-r--r--src/yuzu/configuration/configure_input.ui1039
-rw-r--r--src/yuzu/configuration/configure_input_advanced.cpp171
-rw-r--r--src/yuzu/configuration/configure_input_advanced.h46
-rw-r--r--src/yuzu/configuration/configure_input_advanced.ui2688
-rw-r--r--src/yuzu/configuration/configure_input_dialog.cpp37
-rw-r--r--src/yuzu/configuration/configure_input_dialog.h38
-rw-r--r--src/yuzu/configuration/configure_input_dialog.ui57
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp799
-rw-r--r--src/yuzu/configuration/configure_input_player.h114
-rw-r--r--src/yuzu/configuration/configure_input_player.ui4019
-rw-r--r--src/yuzu/configuration/configure_input_simple.cpp152
-rw-r--r--src/yuzu/configuration/configure_input_simple.h43
-rw-r--r--src/yuzu/configuration/configure_input_simple.ui97
-rw-r--r--src/yuzu/configuration/configure_motion_touch.cpp314
-rw-r--r--src/yuzu/configuration/configure_motion_touch.h90
-rw-r--r--src/yuzu/configuration/configure_motion_touch.ui327
-rw-r--r--src/yuzu/configuration/configure_mouse_advanced.cpp90
-rw-r--r--src/yuzu/configuration/configure_mouse_advanced.h15
-rw-r--r--src/yuzu/configuration/configure_mouse_advanced.ui252
-rw-r--r--src/yuzu/configuration/configure_per_game_addons.cpp4
-rw-r--r--src/yuzu/configuration/configure_profile_manager.cpp4
-rw-r--r--src/yuzu/configuration/configure_system.cpp18
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.cpp623
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.h92
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.ui231
-rw-r--r--src/yuzu/configuration/configure_touch_widget.h62
-rw-r--r--src/yuzu/configuration/configure_ui.cpp26
-rw-r--r--src/yuzu/configuration/configure_ui.ui45
-rw-r--r--src/yuzu/debugger/profiler.cpp3
-rw-r--r--src/yuzu/game_list.cpp40
-rw-r--r--src/yuzu/game_list.h18
-rw-r--r--src/yuzu/game_list_p.h32
-rw-r--r--src/yuzu/game_list_worker.cpp34
-rw-r--r--src/yuzu/main.cpp410
-rw-r--r--src/yuzu/main.h37
-rw-r--r--src/yuzu/main.ui9
-rw-r--r--src/yuzu/uisettings.cpp1
-rw-r--r--src/yuzu/uisettings.h14
-rw-r--r--src/yuzu_cmd/config.cpp40
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp18
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.h10
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp5
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h7
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp5
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h7
-rw-r--r--src/yuzu_cmd/yuzu.cpp20
-rw-r--r--src/yuzu_tester/config.cpp22
-rw-r--r--src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp5
-rw-r--r--src/yuzu_tester/emu_window/emu_window_sdl2_hide.h6
-rw-r--r--src/yuzu_tester/yuzu.cpp8
403 files changed, 21417 insertions, 7611 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 1e977e8a8..71efbb40d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -62,6 +62,12 @@ else()
-Wno-unused-parameter
)
+ # TODO: Remove when we update to a GCC compiler that enables this
+ # by default (i.e. GCC 10 or newer).
+ if (CMAKE_CXX_COMPILER_ID STREQUAL GNU)
+ add_compile_options(-fconcepts)
+ endif()
+
if (ARCHITECTURE_x86_64)
add_compile_options("-mcx16")
endif()
diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp
index a8315c6f7..83c06c0ed 100644
--- a/src/audio_core/cubeb_sink.cpp
+++ b/src/audio_core/cubeb_sink.cpp
@@ -88,7 +88,7 @@ public:
const s16 surround_left{samples[i + 4]};
const s16 surround_right{samples[i + 5]};
// Not used in the ATSC reference implementation
- [[maybe_unused]] const s16 low_frequency_effects { samples[i + 3] };
+ [[maybe_unused]] const s16 low_frequency_effects{samples[i + 3]};
constexpr s32 clev{707}; // center mixing level coefficient
constexpr s32 slev{707}; // surround mixing level coefficient
diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp
index c805b9d21..cb33926bc 100644
--- a/src/audio_core/stream.cpp
+++ b/src/audio_core/stream.cpp
@@ -36,9 +36,10 @@ Stream::Stream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, Format fo
ReleaseCallback&& release_callback, SinkStream& sink_stream, std::string&& name_)
: sample_rate{sample_rate}, format{format}, release_callback{std::move(release_callback)},
sink_stream{sink_stream}, core_timing{core_timing}, name{std::move(name_)} {
-
- release_event = Core::Timing::CreateEvent(
- name, [this](u64, std::chrono::nanoseconds ns_late) { ReleaseActiveBuffer(ns_late); });
+ release_event =
+ Core::Timing::CreateEvent(name, [this](std::uintptr_t, std::chrono::nanoseconds ns_late) {
+ ReleaseActiveBuffer(ns_late);
+ });
}
void Stream::Play() {
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index d120c8d3d..5d54516eb 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -110,6 +110,7 @@ add_library(common STATIC
common_funcs.h
common_paths.h
common_types.h
+ concepts.h
dynamic_library.cpp
dynamic_library.h
fiber.cpp
@@ -171,7 +172,6 @@ add_library(common STATIC
virtual_buffer.h
wall_clock.cpp
wall_clock.h
- web_result.h
zstd_compression.cpp
zstd_compression.h
)
diff --git a/src/common/algorithm.h b/src/common/algorithm.h
index e21b1373c..4804a3421 100644
--- a/src/common/algorithm.h
+++ b/src/common/algorithm.h
@@ -15,7 +15,8 @@
namespace Common {
template <class ForwardIt, class T, class Compare = std::less<>>
-ForwardIt BinaryFind(ForwardIt first, ForwardIt last, const T& value, Compare comp = {}) {
+[[nodiscard]] ForwardIt BinaryFind(ForwardIt first, ForwardIt last, const T& value,
+ Compare comp = {}) {
// Note: BOTH type T and the type after ForwardIt is dereferenced
// must be implicitly convertible to BOTH Type1 and Type2, used in Compare.
// This is stricter than lower_bound requirement (see above)
diff --git a/src/common/alignment.h b/src/common/alignment.h
index ef4d6f896..5040043de 100644
--- a/src/common/alignment.h
+++ b/src/common/alignment.h
@@ -9,7 +9,7 @@
namespace Common {
template <typename T>
-constexpr T AlignUp(T value, std::size_t size) {
+[[nodiscard]] constexpr T AlignUp(T value, std::size_t size) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
auto mod{static_cast<T>(value % size)};
value -= mod;
@@ -17,31 +17,31 @@ constexpr T AlignUp(T value, std::size_t size) {
}
template <typename T>
-constexpr T AlignDown(T value, std::size_t size) {
+[[nodiscard]] constexpr T AlignDown(T value, std::size_t size) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
return static_cast<T>(value - value % size);
}
template <typename T>
-constexpr T AlignBits(T value, std::size_t align) {
+[[nodiscard]] constexpr T AlignBits(T value, std::size_t align) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
return static_cast<T>((value + ((1ULL << align) - 1)) >> align << align);
}
template <typename T>
-constexpr bool Is4KBAligned(T value) {
+[[nodiscard]] constexpr bool Is4KBAligned(T value) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
return (value & 0xFFF) == 0;
}
template <typename T>
-constexpr bool IsWordAligned(T value) {
+[[nodiscard]] constexpr bool IsWordAligned(T value) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
return (value & 0b11) == 0;
}
template <typename T>
-constexpr bool IsAligned(T value, std::size_t alignment) {
+[[nodiscard]] constexpr bool IsAligned(T value, std::size_t alignment) {
using U = typename std::make_unsigned<T>::type;
const U mask = static_cast<U>(alignment - 1);
return (value & mask) == 0;
@@ -64,7 +64,7 @@ public:
template <typename T2>
constexpr AlignmentAllocator(const AlignmentAllocator<T2, Align>&) noexcept {}
- T* allocate(size_type n) {
+ [[nodiscard]] T* allocate(size_type n) {
return static_cast<T*>(::operator new (n * sizeof(T), std::align_val_t{Align}));
}
diff --git a/src/common/assert.h b/src/common/assert.h
index 5b67c5c52..06d7b5612 100644
--- a/src/common/assert.h
+++ b/src/common/assert.h
@@ -17,11 +17,12 @@
// enough for our purposes.
template <typename Fn>
#if defined(_MSC_VER)
-__declspec(noinline, noreturn)
+[[msvc::noinline, noreturn]]
#elif defined(__GNUC__)
- __attribute__((noinline, noreturn, cold))
+[[gnu::cold, gnu::noinline, noreturn]]
#endif
- static void assert_noinline_call(const Fn& fn) {
+static void
+assert_noinline_call(const Fn& fn) {
fn();
Crash();
exit(1); // Keeps GCC's mouth shut about this actually returning
diff --git a/src/common/atomic_ops.cpp b/src/common/atomic_ops.cpp
index 1098e21ff..1612d0e67 100644
--- a/src/common/atomic_ops.cpp
+++ b/src/common/atomic_ops.cpp
@@ -14,50 +14,55 @@ namespace Common {
#if _MSC_VER
-bool AtomicCompareAndSwap(u8 volatile* pointer, u8 value, u8 expected) {
- u8 result = _InterlockedCompareExchange8((char*)pointer, value, expected);
+bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected) {
+ const u8 result =
+ _InterlockedCompareExchange8(reinterpret_cast<volatile char*>(pointer), value, expected);
return result == expected;
}
-bool AtomicCompareAndSwap(u16 volatile* pointer, u16 value, u16 expected) {
- u16 result = _InterlockedCompareExchange16((short*)pointer, value, expected);
+bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected) {
+ const u16 result =
+ _InterlockedCompareExchange16(reinterpret_cast<volatile short*>(pointer), value, expected);
return result == expected;
}
-bool AtomicCompareAndSwap(u32 volatile* pointer, u32 value, u32 expected) {
- u32 result = _InterlockedCompareExchange((long*)pointer, value, expected);
+bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected) {
+ const u32 result =
+ _InterlockedCompareExchange(reinterpret_cast<volatile long*>(pointer), value, expected);
return result == expected;
}
-bool AtomicCompareAndSwap(u64 volatile* pointer, u64 value, u64 expected) {
- u64 result = _InterlockedCompareExchange64((__int64*)pointer, value, expected);
+bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected) {
+ const u64 result = _InterlockedCompareExchange64(reinterpret_cast<volatile __int64*>(pointer),
+ value, expected);
return result == expected;
}
-bool AtomicCompareAndSwap(u64 volatile* pointer, u128 value, u128 expected) {
- return _InterlockedCompareExchange128((__int64*)pointer, value[1], value[0],
- (__int64*)expected.data()) != 0;
+bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected) {
+ return _InterlockedCompareExchange128(reinterpret_cast<volatile __int64*>(pointer), value[1],
+ value[0],
+ reinterpret_cast<__int64*>(expected.data())) != 0;
}
#else
-bool AtomicCompareAndSwap(u8 volatile* pointer, u8 value, u8 expected) {
+bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected) {
return __sync_bool_compare_and_swap(pointer, expected, value);
}
-bool AtomicCompareAndSwap(u16 volatile* pointer, u16 value, u16 expected) {
+bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected) {
return __sync_bool_compare_and_swap(pointer, expected, value);
}
-bool AtomicCompareAndSwap(u32 volatile* pointer, u32 value, u32 expected) {
+bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected) {
return __sync_bool_compare_and_swap(pointer, expected, value);
}
-bool AtomicCompareAndSwap(u64 volatile* pointer, u64 value, u64 expected) {
+bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected) {
return __sync_bool_compare_and_swap(pointer, expected, value);
}
-bool AtomicCompareAndSwap(u64 volatile* pointer, u128 value, u128 expected) {
+bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected) {
unsigned __int128 value_a;
unsigned __int128 expected_a;
std::memcpy(&value_a, value.data(), sizeof(u128));
diff --git a/src/common/atomic_ops.h b/src/common/atomic_ops.h
index e6181d521..b46888589 100644
--- a/src/common/atomic_ops.h
+++ b/src/common/atomic_ops.h
@@ -8,10 +8,10 @@
namespace Common {
-bool AtomicCompareAndSwap(u8 volatile* pointer, u8 value, u8 expected);
-bool AtomicCompareAndSwap(u16 volatile* pointer, u16 value, u16 expected);
-bool AtomicCompareAndSwap(u32 volatile* pointer, u32 value, u32 expected);
-bool AtomicCompareAndSwap(u64 volatile* pointer, u64 value, u64 expected);
-bool AtomicCompareAndSwap(u64 volatile* pointer, u128 value, u128 expected);
+[[nodiscard]] bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected);
+[[nodiscard]] bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected);
+[[nodiscard]] bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected);
+[[nodiscard]] bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected);
+[[nodiscard]] bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected);
} // namespace Common
diff --git a/src/common/bit_field.h b/src/common/bit_field.h
index 26ae6c7fc..0f0661172 100644
--- a/src/common/bit_field.h
+++ b/src/common/bit_field.h
@@ -36,13 +36,6 @@
#include "common/common_funcs.h"
#include "common/swap.h"
-// Inlining
-#ifdef _WIN32
-#define FORCE_INLINE __forceinline
-#else
-#define FORCE_INLINE inline __attribute__((always_inline))
-#endif
-
/*
* Abstract bitfield class
*
@@ -142,8 +135,8 @@ public:
* containing several bitfields can be assembled by formatting each of their values and ORing
* the results together.
*/
- static constexpr FORCE_INLINE StorageType FormatValue(const T& value) {
- return ((StorageType)value << position) & mask;
+ [[nodiscard]] static constexpr StorageType FormatValue(const T& value) {
+ return (static_cast<StorageType>(value) << position) & mask;
}
/**
@@ -151,7 +144,7 @@ public:
* (such as Value() or operator T), but this can be used to extract a value from a bitfield
* union in a constexpr context.
*/
- static constexpr FORCE_INLINE T ExtractValue(const StorageType& storage) {
+ [[nodiscard]] static constexpr T ExtractValue(const StorageType& storage) {
if constexpr (std::numeric_limits<UnderlyingType>::is_signed) {
std::size_t shift = 8 * sizeof(T) - bits;
return static_cast<T>(static_cast<UnderlyingType>(storage << (shift - position)) >>
@@ -175,7 +168,7 @@ public:
constexpr BitField(BitField&&) noexcept = default;
constexpr BitField& operator=(BitField&&) noexcept = default;
- constexpr operator T() const {
+ [[nodiscard]] constexpr operator T() const {
return Value();
}
@@ -183,11 +176,11 @@ public:
storage = static_cast<StorageType>((storage & ~mask) | FormatValue(value));
}
- constexpr T Value() const {
+ [[nodiscard]] constexpr T Value() const {
return ExtractValue(storage);
}
- constexpr explicit operator bool() const {
+ [[nodiscard]] constexpr explicit operator bool() const {
return Value() != 0;
}
diff --git a/src/common/bit_util.h b/src/common/bit_util.h
index 6f7d5a947..29f59a9a3 100644
--- a/src/common/bit_util.h
+++ b/src/common/bit_util.h
@@ -17,12 +17,12 @@ namespace Common {
/// Gets the size of a specified type T in bits.
template <typename T>
-constexpr std::size_t BitSize() {
+[[nodiscard]] constexpr std::size_t BitSize() {
return sizeof(T) * CHAR_BIT;
}
#ifdef _MSC_VER
-inline u32 CountLeadingZeroes32(u32 value) {
+[[nodiscard]] inline u32 CountLeadingZeroes32(u32 value) {
unsigned long leading_zero = 0;
if (_BitScanReverse(&leading_zero, value) != 0) {
@@ -32,7 +32,7 @@ inline u32 CountLeadingZeroes32(u32 value) {
return 32;
}
-inline u32 CountLeadingZeroes64(u64 value) {
+[[nodiscard]] inline u32 CountLeadingZeroes64(u64 value) {
unsigned long leading_zero = 0;
if (_BitScanReverse64(&leading_zero, value) != 0) {
@@ -42,7 +42,7 @@ inline u32 CountLeadingZeroes64(u64 value) {
return 64;
}
#else
-inline u32 CountLeadingZeroes32(u32 value) {
+[[nodiscard]] inline u32 CountLeadingZeroes32(u32 value) {
if (value == 0) {
return 32;
}
@@ -50,7 +50,7 @@ inline u32 CountLeadingZeroes32(u32 value) {
return static_cast<u32>(__builtin_clz(value));
}
-inline u32 CountLeadingZeroes64(u64 value) {
+[[nodiscard]] inline u32 CountLeadingZeroes64(u64 value) {
if (value == 0) {
return 64;
}
@@ -60,7 +60,7 @@ inline u32 CountLeadingZeroes64(u64 value) {
#endif
#ifdef _MSC_VER
-inline u32 CountTrailingZeroes32(u32 value) {
+[[nodiscard]] inline u32 CountTrailingZeroes32(u32 value) {
unsigned long trailing_zero = 0;
if (_BitScanForward(&trailing_zero, value) != 0) {
@@ -70,7 +70,7 @@ inline u32 CountTrailingZeroes32(u32 value) {
return 32;
}
-inline u32 CountTrailingZeroes64(u64 value) {
+[[nodiscard]] inline u32 CountTrailingZeroes64(u64 value) {
unsigned long trailing_zero = 0;
if (_BitScanForward64(&trailing_zero, value) != 0) {
@@ -80,7 +80,7 @@ inline u32 CountTrailingZeroes64(u64 value) {
return 64;
}
#else
-inline u32 CountTrailingZeroes32(u32 value) {
+[[nodiscard]] inline u32 CountTrailingZeroes32(u32 value) {
if (value == 0) {
return 32;
}
@@ -88,7 +88,7 @@ inline u32 CountTrailingZeroes32(u32 value) {
return static_cast<u32>(__builtin_ctz(value));
}
-inline u32 CountTrailingZeroes64(u64 value) {
+[[nodiscard]] inline u32 CountTrailingZeroes64(u64 value) {
if (value == 0) {
return 64;
}
@@ -99,13 +99,13 @@ inline u32 CountTrailingZeroes64(u64 value) {
#ifdef _MSC_VER
-inline u32 MostSignificantBit32(const u32 value) {
+[[nodiscard]] inline u32 MostSignificantBit32(const u32 value) {
unsigned long result;
_BitScanReverse(&result, value);
return static_cast<u32>(result);
}
-inline u32 MostSignificantBit64(const u64 value) {
+[[nodiscard]] inline u32 MostSignificantBit64(const u64 value) {
unsigned long result;
_BitScanReverse64(&result, value);
return static_cast<u32>(result);
@@ -113,30 +113,30 @@ inline u32 MostSignificantBit64(const u64 value) {
#else
-inline u32 MostSignificantBit32(const u32 value) {
+[[nodiscard]] inline u32 MostSignificantBit32(const u32 value) {
return 31U - static_cast<u32>(__builtin_clz(value));
}
-inline u32 MostSignificantBit64(const u64 value) {
+[[nodiscard]] inline u32 MostSignificantBit64(const u64 value) {
return 63U - static_cast<u32>(__builtin_clzll(value));
}
#endif
-inline u32 Log2Floor32(const u32 value) {
+[[nodiscard]] inline u32 Log2Floor32(const u32 value) {
return MostSignificantBit32(value);
}
-inline u32 Log2Ceil32(const u32 value) {
+[[nodiscard]] inline u32 Log2Ceil32(const u32 value) {
const u32 log2_f = Log2Floor32(value);
return log2_f + ((value ^ (1U << log2_f)) != 0U);
}
-inline u32 Log2Floor64(const u64 value) {
+[[nodiscard]] inline u32 Log2Floor64(const u64 value) {
return MostSignificantBit64(value);
}
-inline u32 Log2Ceil64(const u64 value) {
+[[nodiscard]] inline u32 Log2Ceil64(const u64 value) {
const u64 log2_f = static_cast<u64>(Log2Floor64(value));
return static_cast<u32>(log2_f + ((value ^ (1ULL << log2_f)) != 0ULL));
}
diff --git a/src/common/cityhash.h b/src/common/cityhash.h
index 4b94f8e18..a00804e01 100644
--- a/src/common/cityhash.h
+++ b/src/common/cityhash.h
@@ -61,42 +61,43 @@
#pragma once
+#include <cstddef>
+#include <cstdint>
#include <utility>
-#include <stdint.h>
-#include <stdlib.h> // for std::size_t.
namespace Common {
-typedef std::pair<uint64_t, uint64_t> uint128;
+using uint128 = std::pair<uint64_t, uint64_t>;
-inline uint64_t Uint128Low64(const uint128& x) {
+[[nodiscard]] inline uint64_t Uint128Low64(const uint128& x) {
return x.first;
}
-inline uint64_t Uint128High64(const uint128& x) {
+[[nodiscard]] inline uint64_t Uint128High64(const uint128& x) {
return x.second;
}
// Hash function for a byte array.
-uint64_t CityHash64(const char* buf, std::size_t len);
+[[nodiscard]] uint64_t CityHash64(const char* buf, std::size_t len);
// Hash function for a byte array. For convenience, a 64-bit seed is also
// hashed into the result.
-uint64_t CityHash64WithSeed(const char* buf, std::size_t len, uint64_t seed);
+[[nodiscard]] uint64_t CityHash64WithSeed(const char* buf, std::size_t len, uint64_t seed);
// Hash function for a byte array. For convenience, two seeds are also
// hashed into the result.
-uint64_t CityHash64WithSeeds(const char* buf, std::size_t len, uint64_t seed0, uint64_t seed1);
+[[nodiscard]] uint64_t CityHash64WithSeeds(const char* buf, std::size_t len, uint64_t seed0,
+ uint64_t seed1);
// Hash function for a byte array.
-uint128 CityHash128(const char* s, std::size_t len);
+[[nodiscard]] uint128 CityHash128(const char* s, std::size_t len);
// Hash function for a byte array. For convenience, a 128-bit seed is also
// hashed into the result.
-uint128 CityHash128WithSeed(const char* s, std::size_t len, uint128 seed);
+[[nodiscard]] uint128 CityHash128WithSeed(const char* s, std::size_t len, uint128 seed);
// Hash 128 input bits down to 64 bits of output.
// This is intended to be a reasonably good hash function.
-inline uint64_t Hash128to64(const uint128& x) {
+[[nodiscard]] inline uint64_t Hash128to64(const uint128& x) {
// Murmur-inspired hashing.
const uint64_t kMul = 0x9ddfea08eb382d69ULL;
uint64_t a = (Uint128Low64(x) ^ Uint128High64(x)) * kMul;
diff --git a/src/common/color.h b/src/common/color.h
index 3a2222077..bbcac858e 100644
--- a/src/common/color.h
+++ b/src/common/color.h
@@ -10,45 +10,45 @@
#include "common/swap.h"
#include "common/vector_math.h"
-namespace Color {
+namespace Common::Color {
/// Convert a 1-bit color component to 8 bit
-constexpr u8 Convert1To8(u8 value) {
+[[nodiscard]] constexpr u8 Convert1To8(u8 value) {
return value * 255;
}
/// Convert a 4-bit color component to 8 bit
-constexpr u8 Convert4To8(u8 value) {
+[[nodiscard]] constexpr u8 Convert4To8(u8 value) {
return (value << 4) | value;
}
/// Convert a 5-bit color component to 8 bit
-constexpr u8 Convert5To8(u8 value) {
+[[nodiscard]] constexpr u8 Convert5To8(u8 value) {
return (value << 3) | (value >> 2);
}
/// Convert a 6-bit color component to 8 bit
-constexpr u8 Convert6To8(u8 value) {
+[[nodiscard]] constexpr u8 Convert6To8(u8 value) {
return (value << 2) | (value >> 4);
}
/// Convert a 8-bit color component to 1 bit
-constexpr u8 Convert8To1(u8 value) {
+[[nodiscard]] constexpr u8 Convert8To1(u8 value) {
return value >> 7;
}
/// Convert a 8-bit color component to 4 bit
-constexpr u8 Convert8To4(u8 value) {
+[[nodiscard]] constexpr u8 Convert8To4(u8 value) {
return value >> 4;
}
/// Convert a 8-bit color component to 5 bit
-constexpr u8 Convert8To5(u8 value) {
+[[nodiscard]] constexpr u8 Convert8To5(u8 value) {
return value >> 3;
}
/// Convert a 8-bit color component to 6 bit
-constexpr u8 Convert8To6(u8 value) {
+[[nodiscard]] constexpr u8 Convert8To6(u8 value) {
return value >> 2;
}
@@ -57,7 +57,7 @@ constexpr u8 Convert8To6(u8 value) {
* @param bytes Pointer to encoded source color
* @return Result color decoded as Common::Vec4<u8>
*/
-inline Common::Vec4<u8> DecodeRGBA8(const u8* bytes) {
+[[nodiscard]] inline Common::Vec4<u8> DecodeRGBA8(const u8* bytes) {
return {bytes[3], bytes[2], bytes[1], bytes[0]};
}
@@ -66,7 +66,7 @@ inline Common::Vec4<u8> DecodeRGBA8(const u8* bytes) {
* @param bytes Pointer to encoded source color
* @return Result color decoded as Common::Vec4<u8>
*/
-inline Common::Vec4<u8> DecodeRGB8(const u8* bytes) {
+[[nodiscard]] inline Common::Vec4<u8> DecodeRGB8(const u8* bytes) {
return {bytes[2], bytes[1], bytes[0], 255};
}
@@ -75,7 +75,7 @@ inline Common::Vec4<u8> DecodeRGB8(const u8* bytes) {
* @param bytes Pointer to encoded source color
* @return Result color decoded as Common::Vec4<u8>
*/
-inline Common::Vec4<u8> DecodeRG8(const u8* bytes) {
+[[nodiscard]] inline Common::Vec4<u8> DecodeRG8(const u8* bytes) {
return {bytes[1], bytes[0], 0, 255};
}
@@ -84,7 +84,7 @@ inline Common::Vec4<u8> DecodeRG8(const u8* bytes) {
* @param bytes Pointer to encoded source color
* @return Result color decoded as Common::Vec4<u8>
*/
-inline Common::Vec4<u8> DecodeRGB565(const u8* bytes) {
+[[nodiscard]] inline Common::Vec4<u8> DecodeRGB565(const u8* bytes) {
u16_le pixel;
std::memcpy(&pixel, bytes, sizeof(pixel));
return {Convert5To8((pixel >> 11) & 0x1F), Convert6To8((pixel >> 5) & 0x3F),
@@ -96,7 +96,7 @@ inline Common::Vec4<u8> DecodeRGB565(const u8* bytes) {
* @param bytes Pointer to encoded source color
* @return Result color decoded as Common::Vec4<u8>
*/
-inline Common::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
+[[nodiscard]] inline Common::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
u16_le pixel;
std::memcpy(&pixel, bytes, sizeof(pixel));
return {Convert5To8((pixel >> 11) & 0x1F), Convert5To8((pixel >> 6) & 0x1F),
@@ -108,7 +108,7 @@ inline Common::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
* @param bytes Pointer to encoded source color
* @return Result color decoded as Common::Vec4<u8>
*/
-inline Common::Vec4<u8> DecodeRGBA4(const u8* bytes) {
+[[nodiscard]] inline Common::Vec4<u8> DecodeRGBA4(const u8* bytes) {
u16_le pixel;
std::memcpy(&pixel, bytes, sizeof(pixel));
return {Convert4To8((pixel >> 12) & 0xF), Convert4To8((pixel >> 8) & 0xF),
@@ -120,7 +120,7 @@ inline Common::Vec4<u8> DecodeRGBA4(const u8* bytes) {
* @param bytes Pointer to encoded source value
* @return Depth value as an u32
*/
-inline u32 DecodeD16(const u8* bytes) {
+[[nodiscard]] inline u32 DecodeD16(const u8* bytes) {
u16_le data;
std::memcpy(&data, bytes, sizeof(data));
return data;
@@ -131,7 +131,7 @@ inline u32 DecodeD16(const u8* bytes) {
* @param bytes Pointer to encoded source value
* @return Depth value as an u32
*/
-inline u32 DecodeD24(const u8* bytes) {
+[[nodiscard]] inline u32 DecodeD24(const u8* bytes) {
return (bytes[2] << 16) | (bytes[1] << 8) | bytes[0];
}
@@ -140,7 +140,7 @@ inline u32 DecodeD24(const u8* bytes) {
* @param bytes Pointer to encoded source values
* @return Resulting values stored as a Common::Vec2
*/
-inline Common::Vec2<u32> DecodeD24S8(const u8* bytes) {
+[[nodiscard]] inline Common::Vec2<u32> DecodeD24S8(const u8* bytes) {
return {static_cast<u32>((bytes[2] << 16) | (bytes[1] << 8) | bytes[0]), bytes[3]};
}
@@ -268,4 +268,4 @@ inline void EncodeX24S8(u8 stencil, u8* bytes) {
bytes[3] = stencil;
}
-} // namespace Color
+} // namespace Common::Color
diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h
index 88cf5250a..367b6bf6e 100644
--- a/src/common/common_funcs.h
+++ b/src/common/common_funcs.h
@@ -53,43 +53,49 @@ __declspec(dllimport) void __stdcall DebugBreak(void);
// Call directly after the command or use the error num.
// This function might change the error code.
// Defined in Misc.cpp.
-std::string GetLastErrorMsg();
+[[nodiscard]] std::string GetLastErrorMsg();
#define DECLARE_ENUM_FLAG_OPERATORS(type) \
- constexpr type operator|(type a, type b) noexcept { \
+ [[nodiscard]] constexpr type operator|(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) | static_cast<T>(b)); \
} \
- constexpr type operator&(type a, type b) noexcept { \
+ [[nodiscard]] constexpr type operator&(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \
} \
- constexpr type& operator|=(type& a, type b) noexcept { \
+ [[nodiscard]] constexpr type operator^(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
- a = static_cast<type>(static_cast<T>(a) | static_cast<T>(b)); \
+ return static_cast<type>(static_cast<T>(a) ^ static_cast<T>(b)); \
+ } \
+ constexpr type& operator|=(type& a, type b) noexcept { \
+ a = a | b; \
return a; \
} \
constexpr type& operator&=(type& a, type b) noexcept { \
- using T = std::underlying_type_t<type>; \
- a = static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \
+ a = a & b; \
+ return a; \
+ } \
+ constexpr type& operator^=(type& a, type b) noexcept { \
+ a = a ^ b; \
return a; \
} \
- constexpr type operator~(type key) noexcept { \
+ [[nodiscard]] constexpr type operator~(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(~static_cast<T>(key)); \
} \
- constexpr bool True(type key) noexcept { \
+ [[nodiscard]] constexpr bool True(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<T>(key) != 0; \
} \
- constexpr bool False(type key) noexcept { \
+ [[nodiscard]] constexpr bool False(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<T>(key) == 0; \
}
namespace Common {
-constexpr u32 MakeMagic(char a, char b, char c, char d) {
+[[nodiscard]] constexpr u32 MakeMagic(char a, char b, char c, char d) {
return u32(a) | u32(b) << 8 | u32(c) << 16 | u32(d) << 24;
}
diff --git a/src/common/common_paths.h b/src/common/common_paths.h
index 076752d3b..3c593d5f6 100644
--- a/src/common/common_paths.h
+++ b/src/common/common_paths.h
@@ -35,6 +35,7 @@
#define KEYS_DIR "keys"
#define LOAD_DIR "load"
#define DUMP_DIR "dump"
+#define SCREENSHOTS_DIR "screenshots"
#define SHADER_DIR "shader"
#define LOG_DIR "log"
diff --git a/src/common/concepts.h b/src/common/concepts.h
new file mode 100644
index 000000000..5bef3ad67
--- /dev/null
+++ b/src/common/concepts.h
@@ -0,0 +1,34 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <type_traits>
+
+namespace Common {
+
+// Check if type is like an STL container
+template <typename T>
+concept IsSTLContainer = requires(T t) {
+ typename T::value_type;
+ typename T::iterator;
+ typename T::const_iterator;
+ // TODO(ogniK): Replace below is std::same_as<void> when MSVC supports it.
+ t.begin();
+ t.end();
+ t.cbegin();
+ t.cend();
+ t.data();
+ t.size();
+};
+
+// TODO: Replace with std::derived_from when the <concepts> header
+// is available on all supported platforms.
+template <typename Derived, typename Base>
+concept DerivedFrom = requires {
+ std::is_base_of_v<Base, Derived>;
+ std::is_convertible_v<const volatile Derived*, const volatile Base*>;
+};
+
+} // namespace Common
diff --git a/src/common/detached_tasks.cpp b/src/common/detached_tasks.cpp
index f268d6021..f2b4939df 100644
--- a/src/common/detached_tasks.cpp
+++ b/src/common/detached_tasks.cpp
@@ -34,8 +34,7 @@ void DetachedTasks::AddTask(std::function<void()> task) {
std::unique_lock lock{instance->mutex};
--instance->count;
std::notify_all_at_thread_exit(instance->cv, std::move(lock));
- })
- .detach();
+ }).detach();
}
} // namespace Common
diff --git a/src/common/dynamic_library.cpp b/src/common/dynamic_library.cpp
index 7ab54e9e4..7f0a10521 100644
--- a/src/common/dynamic_library.cpp
+++ b/src/common/dynamic_library.cpp
@@ -21,7 +21,7 @@ namespace Common {
DynamicLibrary::DynamicLibrary() = default;
DynamicLibrary::DynamicLibrary(const char* filename) {
- Open(filename);
+ void(Open(filename));
}
DynamicLibrary::DynamicLibrary(DynamicLibrary&& rhs) noexcept
diff --git a/src/common/dynamic_library.h b/src/common/dynamic_library.h
index 2a06372fd..3512da940 100644
--- a/src/common/dynamic_library.h
+++ b/src/common/dynamic_library.h
@@ -33,7 +33,7 @@ public:
~DynamicLibrary();
/// Returns the specified library name with the platform-specific suffix added.
- static std::string GetUnprefixedFilename(const char* filename);
+ [[nodiscard]] static std::string GetUnprefixedFilename(const char* filename);
/// Returns the specified library name in platform-specific format.
/// Major/minor versions will not be included if set to -1.
@@ -41,28 +41,29 @@ public:
/// Windows: LIBNAME-MAJOR-MINOR.dll
/// Linux: libLIBNAME.so.MAJOR.MINOR
/// Mac: libLIBNAME.MAJOR.MINOR.dylib
- static std::string GetVersionedFilename(const char* libname, int major = -1, int minor = -1);
+ [[nodiscard]] static std::string GetVersionedFilename(const char* libname, int major = -1,
+ int minor = -1);
/// Returns true if a module is loaded, otherwise false.
- bool IsOpen() const {
+ [[nodiscard]] bool IsOpen() const {
return handle != nullptr;
}
/// Loads (or replaces) the handle with the specified library file name.
/// Returns true if the library was loaded and can be used.
- bool Open(const char* filename);
+ [[nodiscard]] bool Open(const char* filename);
/// Unloads the library, any function pointers from this library are no longer valid.
void Close();
/// Returns the address of the specified symbol (function or variable) as an untyped pointer.
/// If the specified symbol does not exist in this library, nullptr is returned.
- void* GetSymbolAddress(const char* name) const;
+ [[nodiscard]] void* GetSymbolAddress(const char* name) const;
/// Obtains the address of the specified symbol, automatically casting to the correct type.
/// Returns true if the symbol was found and assigned, otherwise false.
template <typename T>
- bool GetSymbol(const char* name, T* ptr) const {
+ [[nodiscard]] bool GetSymbol(const char* name, T* ptr) const {
*ptr = reinterpret_cast<T>(GetSymbolAddress(name));
return *ptr != nullptr;
}
diff --git a/src/common/fiber.h b/src/common/fiber.h
index dafc1100e..89dde5e36 100644
--- a/src/common/fiber.h
+++ b/src/common/fiber.h
@@ -47,7 +47,7 @@ public:
/// Yields control from Fiber 'from' to Fiber 'to'
/// Fiber 'from' must be the currently running fiber.
static void YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to);
- static std::shared_ptr<Fiber> ThreadToFiber();
+ [[nodiscard]] static std::shared_ptr<Fiber> ThreadToFiber();
void SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter);
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 45b750e1e..16c3713e0 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -74,7 +74,7 @@
// This namespace has various generic functions related to files and paths.
// The code still needs a ton of cleanup.
// REMEMBER: strdup considered harmful!
-namespace FileUtil {
+namespace Common::FS {
// Remove any ending forward slashes from directory paths
// Modifies argument.
@@ -196,7 +196,7 @@ bool CreateFullPath(const std::string& fullPath) {
int panicCounter = 100;
LOG_TRACE(Common_Filesystem, "path {}", fullPath);
- if (FileUtil::Exists(fullPath)) {
+ if (Exists(fullPath)) {
LOG_DEBUG(Common_Filesystem, "path exists {}", fullPath);
return true;
}
@@ -212,7 +212,7 @@ bool CreateFullPath(const std::string& fullPath) {
// Include the '/' so the first call is CreateDir("/") rather than CreateDir("")
std::string const subPath(fullPath.substr(0, position + 1));
- if (!FileUtil::IsDirectory(subPath) && !FileUtil::CreateDir(subPath)) {
+ if (!IsDirectory(subPath) && !CreateDir(subPath)) {
LOG_ERROR(Common, "CreateFullPath: directory creation failed");
return false;
}
@@ -231,7 +231,7 @@ bool DeleteDir(const std::string& filename) {
LOG_TRACE(Common_Filesystem, "directory {}", filename);
// check if a directory
- if (!FileUtil::IsDirectory(filename)) {
+ if (!IsDirectory(filename)) {
LOG_ERROR(Common_Filesystem, "Not a directory {}", filename);
return false;
}
@@ -371,7 +371,7 @@ u64 GetSize(FILE* f) {
bool CreateEmptyFile(const std::string& filename) {
LOG_TRACE(Common_Filesystem, "{}", filename);
- if (!FileUtil::IOFile(filename, "wb").IsOpen()) {
+ if (!IOFile(filename, "wb").IsOpen()) {
LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg());
return false;
}
@@ -488,29 +488,34 @@ bool DeleteDirRecursively(const std::string& directory, unsigned int recursion)
return false;
// Delete the outermost directory
- FileUtil::DeleteDir(directory);
+ DeleteDir(directory);
return true;
}
void CopyDir(const std::string& source_path, const std::string& dest_path) {
#ifndef _WIN32
- if (source_path == dest_path)
+ if (source_path == dest_path) {
return;
- if (!FileUtil::Exists(source_path))
+ }
+ if (!Exists(source_path)) {
return;
- if (!FileUtil::Exists(dest_path))
- FileUtil::CreateFullPath(dest_path);
+ }
+ if (!Exists(dest_path)) {
+ CreateFullPath(dest_path);
+ }
DIR* dirp = opendir(source_path.c_str());
- if (!dirp)
+ if (!dirp) {
return;
+ }
while (struct dirent* result = readdir(dirp)) {
const std::string virtualName(result->d_name);
// check for "." and ".."
if (((virtualName[0] == '.') && (virtualName[1] == '\0')) ||
- ((virtualName[0] == '.') && (virtualName[1] == '.') && (virtualName[2] == '\0')))
+ ((virtualName[0] == '.') && (virtualName[1] == '.') && (virtualName[2] == '\0'))) {
continue;
+ }
std::string source, dest;
source = source_path + virtualName;
@@ -518,11 +523,13 @@ void CopyDir(const std::string& source_path, const std::string& dest_path) {
if (IsDirectory(source)) {
source += '/';
dest += '/';
- if (!FileUtil::Exists(dest))
- FileUtil::CreateFullPath(dest);
+ if (!Exists(dest)) {
+ CreateFullPath(dest);
+ }
CopyDir(source, dest);
- } else if (!FileUtil::Exists(dest))
- FileUtil::Copy(source, dest);
+ } else if (!Exists(dest)) {
+ Copy(source, dest);
+ }
}
closedir(dirp);
#endif
@@ -538,7 +545,7 @@ std::optional<std::string> GetCurrentDir() {
if (!dir) {
#endif
LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg());
- return {};
+ return std::nullopt;
}
#ifdef _WIN32
std::string strDir = Common::UTF16ToUTF8(dir);
@@ -546,7 +553,7 @@ std::optional<std::string> GetCurrentDir() {
std::string strDir = dir;
#endif
free(dir);
- return strDir;
+ return std::move(strDir);
}
bool SetCurrentDir(const std::string& directory) {
@@ -668,7 +675,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
if (user_path.empty()) {
#ifdef _WIN32
user_path = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP;
- if (!FileUtil::IsDirectory(user_path)) {
+ if (!IsDirectory(user_path)) {
user_path = AppDataRoamingDirectory() + DIR_SEP EMU_DATA_DIR DIR_SEP;
} else {
LOG_INFO(Common_Filesystem, "Using the local user directory");
@@ -677,7 +684,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
#else
- if (FileUtil::Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) {
+ if (Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) {
user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP;
paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
@@ -695,6 +702,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP);
paths.emplace(UserPath::LoadDir, user_path + LOAD_DIR DIR_SEP);
paths.emplace(UserPath::DumpDir, user_path + DUMP_DIR DIR_SEP);
+ paths.emplace(UserPath::ScreenshotsDir, user_path + SCREENSHOTS_DIR DIR_SEP);
paths.emplace(UserPath::ShaderDir, user_path + SHADER_DIR DIR_SEP);
paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP);
paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP);
@@ -703,7 +711,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
}
if (!new_path.empty()) {
- if (!FileUtil::IsDirectory(new_path)) {
+ if (!IsDirectory(new_path)) {
LOG_ERROR(Common_Filesystem, "Invalid path specified {}", new_path);
return paths[path];
} else {
@@ -901,10 +909,10 @@ std::string SanitizePath(std::string_view path_, DirectorySeparator directory_se
return std::string(RemoveTrailingSlash(path));
}
-IOFile::IOFile() {}
+IOFile::IOFile() = default;
IOFile::IOFile(const std::string& filename, const char openmode[], int flags) {
- Open(filename, openmode, flags);
+ void(Open(filename, openmode, flags));
}
IOFile::~IOFile() {
@@ -945,17 +953,18 @@ bool IOFile::Open(const std::string& filename, const char openmode[], int flags)
}
bool IOFile::Close() {
- if (!IsOpen() || 0 != std::fclose(m_file))
+ if (!IsOpen() || 0 != std::fclose(m_file)) {
return false;
+ }
m_file = nullptr;
return true;
}
u64 IOFile::GetSize() const {
- if (IsOpen())
- return FileUtil::GetSize(m_file);
-
+ if (IsOpen()) {
+ return FS::GetSize(m_file);
+ }
return 0;
}
@@ -964,9 +973,9 @@ bool IOFile::Seek(s64 off, int origin) const {
}
u64 IOFile::Tell() const {
- if (IsOpen())
+ if (IsOpen()) {
return ftello(m_file);
-
+ }
return std::numeric_limits<u64>::max();
}
@@ -1015,4 +1024,4 @@ bool IOFile::Resize(u64 size) {
;
}
-} // namespace FileUtil
+} // namespace Common::FS
diff --git a/src/common/file_util.h b/src/common/file_util.h
index f7a0c33fa..8b587320f 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -19,7 +19,7 @@
#include "common/string_util.h"
#endif
-namespace FileUtil {
+namespace Common::FS {
// User paths for GetUserPath
enum class UserPath {
@@ -32,6 +32,7 @@ enum class UserPath {
SDMCDir,
LoadDir,
DumpDir,
+ ScreenshotsDir,
ShaderDir,
SysDataDir,
UserDir,
@@ -47,19 +48,19 @@ struct FSTEntry {
};
// Returns true if file filename exists
-bool Exists(const std::string& filename);
+[[nodiscard]] bool Exists(const std::string& filename);
// Returns true if filename is a directory
-bool IsDirectory(const std::string& filename);
+[[nodiscard]] bool IsDirectory(const std::string& filename);
// Returns the size of filename (64bit)
-u64 GetSize(const std::string& filename);
+[[nodiscard]] u64 GetSize(const std::string& filename);
// Overloaded GetSize, accepts file descriptor
-u64 GetSize(const int fd);
+[[nodiscard]] u64 GetSize(int fd);
// Overloaded GetSize, accepts FILE*
-u64 GetSize(FILE* f);
+[[nodiscard]] u64 GetSize(FILE* f);
// Returns true if successful, or path already exists.
bool CreateDir(const std::string& filename);
@@ -119,7 +120,7 @@ u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
bool DeleteDirRecursively(const std::string& directory, unsigned int recursion = 256);
// Returns the current directory
-std::optional<std::string> GetCurrentDir();
+[[nodiscard]] std::optional<std::string> GetCurrentDir();
// Create directory and copy contents (does not overwrite existing files)
void CopyDir(const std::string& source_path, const std::string& dest_path);
@@ -131,20 +132,20 @@ bool SetCurrentDir(const std::string& directory);
// directory. To be used in "multi-user" mode (that is, installed).
const std::string& GetUserPath(UserPath path, const std::string& new_path = "");
-std::string GetHactoolConfigurationPath();
+[[nodiscard]] std::string GetHactoolConfigurationPath();
-std::string GetNANDRegistrationDir(bool system = false);
+[[nodiscard]] std::string GetNANDRegistrationDir(bool system = false);
// Returns the path to where the sys file are
-std::string GetSysDirectory();
+[[nodiscard]] std::string GetSysDirectory();
#ifdef __APPLE__
-std::string GetBundleDirectory();
+[[nodiscard]] std::string GetBundleDirectory();
#endif
#ifdef _WIN32
-const std::string& GetExeDirectory();
-std::string AppDataRoamingDirectory();
+[[nodiscard]] const std::string& GetExeDirectory();
+[[nodiscard]] std::string AppDataRoamingDirectory();
#endif
std::size_t WriteStringToFile(bool text_file, const std::string& filename, std::string_view str);
@@ -163,38 +164,55 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam
// Splits the path on '/' or '\' and put the components into a vector
// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
-std::vector<std::string> SplitPathComponents(std::string_view filename);
+[[nodiscard]] std::vector<std::string> SplitPathComponents(std::string_view filename);
// Gets all of the text up to the last '/' or '\' in the path.
-std::string_view GetParentPath(std::string_view path);
+[[nodiscard]] std::string_view GetParentPath(std::string_view path);
// Gets all of the text after the first '/' or '\' in the path.
-std::string_view GetPathWithoutTop(std::string_view path);
+[[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path);
// Gets the filename of the path
-std::string_view GetFilename(std::string_view path);
+[[nodiscard]] std::string_view GetFilename(std::string_view path);
// Gets the extension of the filename
-std::string_view GetExtensionFromFilename(std::string_view name);
+[[nodiscard]] std::string_view GetExtensionFromFilename(std::string_view name);
// Removes the final '/' or '\' if one exists
-std::string_view RemoveTrailingSlash(std::string_view path);
+[[nodiscard]] std::string_view RemoveTrailingSlash(std::string_view path);
// Creates a new vector containing indices [first, last) from the original.
template <typename T>
-std::vector<T> SliceVector(const std::vector<T>& vector, std::size_t first, std::size_t last) {
- if (first >= last)
+[[nodiscard]] std::vector<T> SliceVector(const std::vector<T>& vector, std::size_t first,
+ std::size_t last) {
+ if (first >= last) {
return {};
+ }
last = std::min<std::size_t>(last, vector.size());
return std::vector<T>(vector.begin() + first, vector.begin() + first + last);
}
-enum class DirectorySeparator { ForwardSlash, BackwardSlash, PlatformDefault };
+enum class DirectorySeparator {
+ ForwardSlash,
+ BackwardSlash,
+ PlatformDefault,
+};
// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\'
// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows
-std::string SanitizePath(std::string_view path,
- DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
+[[nodiscard]] std::string SanitizePath(
+ std::string_view path,
+ DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
+
+// To deal with Windows being dumb at Unicode
+template <typename T>
+void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) {
+#ifdef _MSC_VER
+ fstream.open(Common::UTF8ToUTF16W(filename), openmode);
+#else
+ fstream.open(filename, openmode);
+#endif
+}
// simple wrapper for cstdlib file functions to
// hopefully will make error checking easier
@@ -214,7 +232,7 @@ public:
void Swap(IOFile& other) noexcept;
- bool Open(const std::string& filename, const char openmode[], int flags = 0);
+ [[nodiscard]] bool Open(const std::string& filename, const char openmode[], int flags = 0);
bool Close();
template <typename T>
@@ -255,13 +273,13 @@ public:
return WriteArray(str.data(), str.length());
}
- bool IsOpen() const {
+ [[nodiscard]] bool IsOpen() const {
return nullptr != m_file;
}
bool Seek(s64 off, int origin) const;
- u64 Tell() const;
- u64 GetSize() const;
+ [[nodiscard]] u64 Tell() const;
+ [[nodiscard]] u64 GetSize() const;
bool Resize(u64 size);
bool Flush();
@@ -277,14 +295,4 @@ private:
std::FILE* m_file = nullptr;
};
-} // namespace FileUtil
-
-// To deal with Windows being dumb at unicode:
-template <typename T>
-void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) {
-#ifdef _MSC_VER
- fstream.open(Common::UTF8ToUTF16W(filename), openmode);
-#else
- fstream.open(filename, openmode);
-#endif
-}
+} // namespace Common::FS
diff --git a/src/common/hash.h b/src/common/hash.h
index b2538f3ea..298930702 100644
--- a/src/common/hash.h
+++ b/src/common/hash.h
@@ -5,36 +5,11 @@
#pragma once
#include <cstddef>
-#include <cstring>
#include <utility>
#include <boost/functional/hash.hpp>
-#include "common/cityhash.h"
-#include "common/common_types.h"
namespace Common {
-/**
- * Computes a 64-bit hash over the specified block of data
- * @param data Block of data to compute hash over
- * @param len Length of data (in bytes) to compute hash over
- * @returns 64-bit hash value that was computed over the data block
- */
-static inline u64 ComputeHash64(const void* data, std::size_t len) {
- return CityHash64(static_cast<const char*>(data), len);
-}
-
-/**
- * Computes a 64-bit hash of a struct. In addition to being trivially copyable, it is also critical
- * that either the struct includes no padding, or that any padding is initialized to a known value
- * by memsetting the struct to 0 before filling it in.
- */
-template <typename T>
-static inline u64 ComputeStructHash64(const T& data) {
- static_assert(std::is_trivially_copyable_v<T>,
- "Type passed to ComputeStructHash64 must be trivially copyable");
- return ComputeHash64(&data, sizeof(data));
-}
-
struct PairHash {
template <class T1, class T2>
std::size_t operator()(const std::pair<T1, T2>& pair) const noexcept {
diff --git a/src/common/hex_util.cpp b/src/common/hex_util.cpp
index c2f6cf0f6..74f52dd11 100644
--- a/src/common/hex_util.cpp
+++ b/src/common/hex_util.cpp
@@ -3,21 +3,9 @@
// Refer to the license.txt file included.
#include "common/hex_util.h"
-#include "common/logging/log.h"
namespace Common {
-u8 ToHexNibble(char c1) {
- if (c1 >= 65 && c1 <= 70)
- return c1 - 55;
- if (c1 >= 97 && c1 <= 102)
- return c1 - 87;
- if (c1 >= 48 && c1 <= 57)
- return c1 - 48;
- LOG_ERROR(Common, "Invalid hex digit: 0x{:02X}", c1);
- return 0;
-}
-
std::vector<u8> HexStringToVector(std::string_view str, bool little_endian) {
std::vector<u8> out(str.size() / 2);
if (little_endian) {
@@ -30,26 +18,4 @@ std::vector<u8> HexStringToVector(std::string_view str, bool little_endian) {
return out;
}
-std::array<u8, 16> operator""_array16(const char* str, std::size_t len) {
- if (len != 32) {
- LOG_ERROR(Common,
- "Attempting to parse string to array that is not of correct size (expected=32, "
- "actual={}).",
- len);
- return {};
- }
- return HexStringToArray<16>(str);
-}
-
-std::array<u8, 32> operator""_array32(const char* str, std::size_t len) {
- if (len != 64) {
- LOG_ERROR(Common,
- "Attempting to parse string to array that is not of correct size (expected=64, "
- "actual={}).",
- len);
- return {};
- }
- return HexStringToArray<32>(str);
-}
-
} // namespace Common
diff --git a/src/common/hex_util.h b/src/common/hex_util.h
index bb4736f96..120f1a5e6 100644
--- a/src/common/hex_util.h
+++ b/src/common/hex_util.h
@@ -14,25 +14,37 @@
namespace Common {
-u8 ToHexNibble(char c1);
+[[nodiscard]] constexpr u8 ToHexNibble(char c) {
+ if (c >= 65 && c <= 70) {
+ return c - 55;
+ }
+
+ if (c >= 97 && c <= 102) {
+ return c - 87;
+ }
+
+ return c - 48;
+}
-std::vector<u8> HexStringToVector(std::string_view str, bool little_endian);
+[[nodiscard]] std::vector<u8> HexStringToVector(std::string_view str, bool little_endian);
template <std::size_t Size, bool le = false>
-std::array<u8, Size> HexStringToArray(std::string_view str) {
+[[nodiscard]] constexpr std::array<u8, Size> HexStringToArray(std::string_view str) {
std::array<u8, Size> out{};
if constexpr (le) {
- for (std::size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2)
+ for (std::size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2) {
out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
+ }
} else {
- for (std::size_t i = 0; i < 2 * Size; i += 2)
+ for (std::size_t i = 0; i < 2 * Size; i += 2) {
out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
+ }
}
return out;
}
template <typename ContiguousContainer>
-std::string HexToString(const ContiguousContainer& data, bool upper = true) {
+[[nodiscard]] std::string HexToString(const ContiguousContainer& data, bool upper = true) {
static_assert(std::is_same_v<typename ContiguousContainer::value_type, u8>,
"Underlying type within the contiguous container must be u8.");
@@ -48,7 +60,12 @@ std::string HexToString(const ContiguousContainer& data, bool upper = true) {
return out;
}
-std::array<u8, 0x10> operator"" _array16(const char* str, std::size_t len);
-std::array<u8, 0x20> operator"" _array32(const char* str, std::size_t len);
+[[nodiscard]] constexpr std::array<u8, 16> AsArray(const char (&data)[17]) {
+ return HexStringToArray<16>(data);
+}
+
+[[nodiscard]] constexpr std::array<u8, 32> AsArray(const char (&data)[65]) {
+ return HexStringToArray<32>(data);
+}
} // namespace Common
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 04bc3128f..62cfde397 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -113,19 +113,19 @@ private:
Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
const char* function, std::string message) const {
using std::chrono::duration_cast;
+ using std::chrono::microseconds;
using std::chrono::steady_clock;
- Entry entry;
- entry.timestamp =
- duration_cast<std::chrono::microseconds>(steady_clock::now() - time_origin);
- entry.log_class = log_class;
- entry.log_level = log_level;
- entry.filename = filename;
- entry.line_num = line_nr;
- entry.function = function;
- entry.message = std::move(message);
-
- return entry;
+ return {
+ .timestamp = duration_cast<microseconds>(steady_clock::now() - time_origin),
+ .log_class = log_class,
+ .log_level = log_level,
+ .filename = filename,
+ .line_num = line_nr,
+ .function = function,
+ .message = std::move(message),
+ .final_entry = false,
+ };
}
std::mutex writing_mutex;
diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h
index fc338c70d..da1c2f185 100644
--- a/src/common/logging/backend.h
+++ b/src/common/logging/backend.h
@@ -21,19 +21,13 @@ class Filter;
*/
struct Entry {
std::chrono::microseconds timestamp;
- Class log_class;
- Level log_level;
- const char* filename;
- unsigned int line_num;
+ Class log_class{};
+ Level log_level{};
+ const char* filename = nullptr;
+ unsigned int line_num = 0;
std::string function;
std::string message;
bool final_entry = false;
-
- Entry() = default;
- Entry(Entry&& o) = default;
-
- Entry& operator=(Entry&& o) = default;
- Entry& operator=(const Entry& o) = default;
};
/**
@@ -100,7 +94,7 @@ public:
void Write(const Entry& entry) override;
private:
- FileUtil::IOFile file;
+ Common::FS::IOFile file;
std::size_t bytes_written;
};
diff --git a/src/common/lz4_compression.cpp b/src/common/lz4_compression.cpp
index ade6759bb..25700015a 100644
--- a/src/common/lz4_compression.cpp
+++ b/src/common/lz4_compression.cpp
@@ -14,19 +14,19 @@ std::vector<u8> CompressDataLZ4(const u8* source, std::size_t source_size) {
ASSERT_MSG(source_size <= LZ4_MAX_INPUT_SIZE, "Source size exceeds LZ4 maximum input size");
const auto source_size_int = static_cast<int>(source_size);
- const int max_compressed_size = LZ4_compressBound(source_size_int);
+ const auto max_compressed_size = static_cast<std::size_t>(LZ4_compressBound(source_size_int));
std::vector<u8> compressed(max_compressed_size);
- const int compressed_size = LZ4_compress_default(reinterpret_cast<const char*>(source),
- reinterpret_cast<char*>(compressed.data()),
- source_size_int, max_compressed_size);
+ const int compressed_size = LZ4_compress_default(
+ reinterpret_cast<const char*>(source), reinterpret_cast<char*>(compressed.data()),
+ source_size_int, static_cast<int>(max_compressed_size));
if (compressed_size <= 0) {
// Compression failed
return {};
}
- compressed.resize(compressed_size);
+ compressed.resize(static_cast<std::size_t>(compressed_size));
return compressed;
}
@@ -38,19 +38,19 @@ std::vector<u8> CompressDataLZ4HC(const u8* source, std::size_t source_size,
compression_level = std::clamp(compression_level, LZ4HC_CLEVEL_MIN, LZ4HC_CLEVEL_MAX);
const auto source_size_int = static_cast<int>(source_size);
- const int max_compressed_size = LZ4_compressBound(source_size_int);
+ const auto max_compressed_size = static_cast<std::size_t>(LZ4_compressBound(source_size_int));
std::vector<u8> compressed(max_compressed_size);
const int compressed_size = LZ4_compress_HC(
reinterpret_cast<const char*>(source), reinterpret_cast<char*>(compressed.data()),
- source_size_int, max_compressed_size, compression_level);
+ source_size_int, static_cast<int>(max_compressed_size), compression_level);
if (compressed_size <= 0) {
// Compression failed
return {};
}
- compressed.resize(compressed_size);
+ compressed.resize(static_cast<std::size_t>(compressed_size));
return compressed;
}
diff --git a/src/common/lz4_compression.h b/src/common/lz4_compression.h
index 4c16f6e03..87a4be1b0 100644
--- a/src/common/lz4_compression.h
+++ b/src/common/lz4_compression.h
@@ -13,12 +13,12 @@ namespace Common::Compression {
/**
* Compresses a source memory region with LZ4 and returns the compressed data in a vector.
*
- * @param source the uncompressed source memory region.
- * @param source_size the size in bytes of the uncompressed source memory region.
+ * @param source The uncompressed source memory region.
+ * @param source_size The size of the uncompressed source memory region.
*
* @return the compressed data.
*/
-std::vector<u8> CompressDataLZ4(const u8* source, std::size_t source_size);
+[[nodiscard]] std::vector<u8> CompressDataLZ4(const u8* source, std::size_t source_size);
/**
* Utilizes the LZ4 subalgorithm LZ4HC with the specified compression level. Higher compression
@@ -26,23 +26,24 @@ std::vector<u8> CompressDataLZ4(const u8* source, std::size_t source_size);
* compression level has almost no impact on decompression speed. Data compressed with LZ4HC can
* also be decompressed with the default LZ4 decompression.
*
- * @param source the uncompressed source memory region.
- * @param source_size the size in bytes of the uncompressed source memory region.
- * @param compression_level the used compression level. Should be between 3 and 12.
+ * @param source The uncompressed source memory region.
+ * @param source_size The size of the uncompressed source memory region.
+ * @param compression_level The used compression level. Should be between 3 and 12.
*
* @return the compressed data.
*/
-std::vector<u8> CompressDataLZ4HC(const u8* source, std::size_t source_size, s32 compression_level);
+[[nodiscard]] std::vector<u8> CompressDataLZ4HC(const u8* source, std::size_t source_size,
+ s32 compression_level);
/**
* Utilizes the LZ4 subalgorithm LZ4HC with the highest possible compression level.
*
- * @param source the uncompressed source memory region.
- * @param source_size the size in bytes of the uncompressed source memory region.
+ * @param source The uncompressed source memory region.
+ * @param source_size The size of the uncompressed source memory region
*
* @return the compressed data.
*/
-std::vector<u8> CompressDataLZ4HCMax(const u8* source, std::size_t source_size);
+[[nodiscard]] std::vector<u8> CompressDataLZ4HCMax(const u8* source, std::size_t source_size);
/**
* Decompresses a source memory region with LZ4 and returns the uncompressed data in a vector.
@@ -52,6 +53,7 @@ std::vector<u8> CompressDataLZ4HCMax(const u8* source, std::size_t source_size);
*
* @return the decompressed data.
*/
-std::vector<u8> DecompressDataLZ4(const std::vector<u8>& compressed, std::size_t uncompressed_size);
+[[nodiscard]] std::vector<u8> DecompressDataLZ4(const std::vector<u8>& compressed,
+ std::size_t uncompressed_size);
} // namespace Common::Compression \ No newline at end of file
diff --git a/src/common/math_util.h b/src/common/math_util.h
index 83ef0201f..b35ad8507 100644
--- a/src/common/math_util.h
+++ b/src/common/math_util.h
@@ -9,7 +9,7 @@
namespace Common {
-constexpr float PI = 3.14159265f;
+constexpr float PI = 3.1415926535f;
template <class T>
struct Rectangle {
@@ -23,7 +23,7 @@ struct Rectangle {
constexpr Rectangle(T left, T top, T right, T bottom)
: left(left), top(top), right(right), bottom(bottom) {}
- T GetWidth() const {
+ [[nodiscard]] T GetWidth() const {
if constexpr (std::is_floating_point_v<T>) {
return std::abs(right - left);
} else {
@@ -31,7 +31,7 @@ struct Rectangle {
}
}
- T GetHeight() const {
+ [[nodiscard]] T GetHeight() const {
if constexpr (std::is_floating_point_v<T>) {
return std::abs(bottom - top);
} else {
@@ -39,21 +39,21 @@ struct Rectangle {
}
}
- Rectangle<T> TranslateX(const T x) const {
+ [[nodiscard]] Rectangle<T> TranslateX(const T x) const {
return Rectangle{left + x, top, right + x, bottom};
}
- Rectangle<T> TranslateY(const T y) const {
+ [[nodiscard]] Rectangle<T> TranslateY(const T y) const {
return Rectangle{left, top + y, right, bottom + y};
}
- Rectangle<T> Scale(const float s) const {
+ [[nodiscard]] Rectangle<T> Scale(const float s) const {
return Rectangle{left, top, static_cast<T>(left + GetWidth() * s),
static_cast<T>(top + GetHeight() * s)};
}
};
template <typename T>
-Rectangle(T, T, T, T)->Rectangle<T>;
+Rectangle(T, T, T, T) -> Rectangle<T>;
} // namespace Common
diff --git a/src/common/memory_detect.h b/src/common/memory_detect.h
index a73c0f3f4..0f73751c8 100644
--- a/src/common/memory_detect.h
+++ b/src/common/memory_detect.h
@@ -17,6 +17,6 @@ struct MemoryInfo {
* Gets the memory info of the host system
* @return Reference to a MemoryInfo struct with the physical and swap memory sizes in bytes
*/
-const MemoryInfo& GetMemInfo();
+[[nodiscard]] const MemoryInfo& GetMemInfo();
} // namespace Common \ No newline at end of file
diff --git a/src/common/multi_level_queue.h b/src/common/multi_level_queue.h
index 50acfdbf2..4b305bf40 100644
--- a/src/common/multi_level_queue.h
+++ b/src/common/multi_level_queue.h
@@ -223,15 +223,15 @@ public:
ListShiftForward(levels[priority], n);
}
- std::size_t depth() const {
+ [[nodiscard]] std::size_t depth() const {
return Depth;
}
- std::size_t size(u32 priority) const {
+ [[nodiscard]] std::size_t size(u32 priority) const {
return levels[priority].size();
}
- std::size_t size() const {
+ [[nodiscard]] std::size_t size() const {
u64 priorities = used_priorities;
std::size_t size = 0;
while (priorities != 0) {
@@ -242,64 +242,64 @@ public:
return size;
}
- bool empty() const {
+ [[nodiscard]] bool empty() const {
return used_priorities == 0;
}
- bool empty(u32 priority) const {
+ [[nodiscard]] bool empty(u32 priority) const {
return (used_priorities & (1ULL << priority)) == 0;
}
- u32 highest_priority_set(u32 max_priority = 0) const {
+ [[nodiscard]] u32 highest_priority_set(u32 max_priority = 0) const {
const u64 priorities =
max_priority == 0 ? used_priorities : (used_priorities & ~((1ULL << max_priority) - 1));
return priorities == 0 ? Depth : static_cast<u32>(CountTrailingZeroes64(priorities));
}
- u32 lowest_priority_set(u32 min_priority = Depth - 1) const {
+ [[nodiscard]] u32 lowest_priority_set(u32 min_priority = Depth - 1) const {
const u64 priorities = min_priority >= Depth - 1
? used_priorities
: (used_priorities & ((1ULL << (min_priority + 1)) - 1));
return priorities == 0 ? Depth : 63 - CountLeadingZeroes64(priorities);
}
- const_iterator cbegin(u32 max_prio = 0) const {
+ [[nodiscard]] const_iterator cbegin(u32 max_prio = 0) const {
const u32 priority = highest_priority_set(max_prio);
return priority == Depth ? cend()
: const_iterator{*this, levels[priority].cbegin(), priority};
}
- const_iterator begin(u32 max_prio = 0) const {
+ [[nodiscard]] const_iterator begin(u32 max_prio = 0) const {
return cbegin(max_prio);
}
- iterator begin(u32 max_prio = 0) {
+ [[nodiscard]] iterator begin(u32 max_prio = 0) {
const u32 priority = highest_priority_set(max_prio);
return priority == Depth ? end() : iterator{*this, levels[priority].begin(), priority};
}
- const_iterator cend(u32 min_prio = Depth - 1) const {
+ [[nodiscard]] const_iterator cend(u32 min_prio = Depth - 1) const {
return min_prio == Depth - 1 ? const_iterator{*this, Depth} : cbegin(min_prio + 1);
}
- const_iterator end(u32 min_prio = Depth - 1) const {
+ [[nodiscard]] const_iterator end(u32 min_prio = Depth - 1) const {
return cend(min_prio);
}
- iterator end(u32 min_prio = Depth - 1) {
+ [[nodiscard]] iterator end(u32 min_prio = Depth - 1) {
return min_prio == Depth - 1 ? iterator{*this, Depth} : begin(min_prio + 1);
}
- T& front(u32 max_priority = 0) {
+ [[nodiscard]] T& front(u32 max_priority = 0) {
const u32 priority = highest_priority_set(max_priority);
return levels[priority == Depth ? 0 : priority].front();
}
- const T& front(u32 max_priority = 0) const {
+ [[nodiscard]] const T& front(u32 max_priority = 0) const {
const u32 priority = highest_priority_set(max_priority);
return levels[priority == Depth ? 0 : priority].front();
}
- T back(u32 min_priority = Depth - 1) {
+ [[nodiscard]] T& back(u32 min_priority = Depth - 1) {
const u32 priority = lowest_priority_set(min_priority); // intended
return levels[priority == Depth ? 63 : priority].back();
}
- const T& back(u32 min_priority = Depth - 1) const {
+ [[nodiscard]] const T& back(u32 min_priority = Depth - 1) const {
const u32 priority = lowest_priority_set(min_priority); // intended
return levels[priority == Depth ? 63 : priority].back();
}
@@ -329,7 +329,8 @@ private:
in_list.splice(position, out_list, element);
}
- static const_list_iterator ListIterateTo(const std::list<T>& list, const T& element) {
+ [[nodiscard]] static const_list_iterator ListIterateTo(const std::list<T>& list,
+ const T& element) {
auto it = list.cbegin();
while (it != list.cend() && *it != element) {
++it;
diff --git a/src/common/page_table.h b/src/common/page_table.h
index 1e8bd3187..cf5eed780 100644
--- a/src/common/page_table.h
+++ b/src/common/page_table.h
@@ -36,11 +36,11 @@ struct SpecialRegion {
MemoryHookPointer handler;
- bool operator<(const SpecialRegion& other) const {
+ [[nodiscard]] bool operator<(const SpecialRegion& other) const {
return std::tie(type, handler) < std::tie(other.type, other.handler);
}
- bool operator==(const SpecialRegion& other) const {
+ [[nodiscard]] bool operator==(const SpecialRegion& other) const {
return std::tie(type, handler) == std::tie(other.type, other.handler);
}
};
diff --git a/src/common/param_package.h b/src/common/param_package.h
index 6a0a9b656..c13e45479 100644
--- a/src/common/param_package.h
+++ b/src/common/param_package.h
@@ -19,19 +19,19 @@ public:
explicit ParamPackage(const std::string& serialized);
ParamPackage(std::initializer_list<DataType::value_type> list);
ParamPackage(const ParamPackage& other) = default;
- ParamPackage(ParamPackage&& other) = default;
+ ParamPackage(ParamPackage&& other) noexcept = default;
ParamPackage& operator=(const ParamPackage& other) = default;
ParamPackage& operator=(ParamPackage&& other) = default;
- std::string Serialize() const;
- std::string Get(const std::string& key, const std::string& default_value) const;
- int Get(const std::string& key, int default_value) const;
- float Get(const std::string& key, float default_value) const;
+ [[nodiscard]] std::string Serialize() const;
+ [[nodiscard]] std::string Get(const std::string& key, const std::string& default_value) const;
+ [[nodiscard]] int Get(const std::string& key, int default_value) const;
+ [[nodiscard]] float Get(const std::string& key, float default_value) const;
void Set(const std::string& key, std::string value);
void Set(const std::string& key, int value);
void Set(const std::string& key, float value);
- bool Has(const std::string& key) const;
+ [[nodiscard]] bool Has(const std::string& key) const;
void Erase(const std::string& key);
void Clear();
diff --git a/src/common/quaternion.h b/src/common/quaternion.h
index 370198ae0..4d0871eb4 100644
--- a/src/common/quaternion.h
+++ b/src/common/quaternion.h
@@ -14,35 +14,66 @@ public:
Vec3<T> xyz;
T w{};
- Quaternion<decltype(-T{})> Inverse() const {
+ [[nodiscard]] Quaternion<decltype(-T{})> Inverse() const {
return {-xyz, w};
}
- Quaternion<decltype(T{} + T{})> operator+(const Quaternion& other) const {
+ [[nodiscard]] Quaternion<decltype(T{} + T{})> operator+(const Quaternion& other) const {
return {xyz + other.xyz, w + other.w};
}
- Quaternion<decltype(T{} - T{})> operator-(const Quaternion& other) const {
+ [[nodiscard]] Quaternion<decltype(T{} - T{})> operator-(const Quaternion& other) const {
return {xyz - other.xyz, w - other.w};
}
- Quaternion<decltype(T{} * T{} - T{} * T{})> operator*(const Quaternion& other) const {
+ [[nodiscard]] Quaternion<decltype(T{} * T{} - T{} * T{})> operator*(
+ const Quaternion& other) const {
return {xyz * other.w + other.xyz * w + Cross(xyz, other.xyz),
w * other.w - Dot(xyz, other.xyz)};
}
- Quaternion<T> Normalized() const {
+ [[nodiscard]] Quaternion<T> Normalized() const {
T length = std::sqrt(xyz.Length2() + w * w);
return {xyz / length, w / length};
}
+
+ [[nodiscard]] std::array<decltype(-T{}), 16> ToMatrix() const {
+ const T x2 = xyz[0] * xyz[0];
+ const T y2 = xyz[1] * xyz[1];
+ const T z2 = xyz[2] * xyz[2];
+
+ const T xy = xyz[0] * xyz[1];
+ const T wz = w * xyz[2];
+ const T xz = xyz[0] * xyz[2];
+ const T wy = w * xyz[1];
+ const T yz = xyz[1] * xyz[2];
+ const T wx = w * xyz[0];
+
+ return {1.0f - 2.0f * (y2 + z2),
+ 2.0f * (xy + wz),
+ 2.0f * (xz - wy),
+ 0.0f,
+ 2.0f * (xy - wz),
+ 1.0f - 2.0f * (x2 + z2),
+ 2.0f * (yz + wx),
+ 0.0f,
+ 2.0f * (xz + wy),
+ 2.0f * (yz - wx),
+ 1.0f - 2.0f * (x2 + y2),
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 1.0f};
+ }
};
template <typename T>
-auto QuaternionRotate(const Quaternion<T>& q, const Vec3<T>& v) {
+[[nodiscard]] auto QuaternionRotate(const Quaternion<T>& q, const Vec3<T>& v) {
return v + 2 * Cross(q.xyz, Cross(q.xyz, v) + v * q.w);
}
-inline Quaternion<float> MakeQuaternion(const Vec3<float>& axis, float angle) {
+[[nodiscard]] inline Quaternion<float> MakeQuaternion(const Vec3<float>& axis, float angle) {
return {axis * std::sin(angle / 2), std::cos(angle / 2)};
}
diff --git a/src/common/ring_buffer.h b/src/common/ring_buffer.h
index abe3b4dc2..138fa0131 100644
--- a/src/common/ring_buffer.h
+++ b/src/common/ring_buffer.h
@@ -91,12 +91,12 @@ public:
}
/// @returns Number of slots used
- std::size_t Size() const {
+ [[nodiscard]] std::size_t Size() const {
return m_write_index.load() - m_read_index.load();
}
/// @returns Maximum size of ring buffer
- constexpr std::size_t Capacity() const {
+ [[nodiscard]] constexpr std::size_t Capacity() const {
return capacity;
}
diff --git a/src/common/spin_lock.h b/src/common/spin_lock.h
index 1df5528c4..4f946a258 100644
--- a/src/common/spin_lock.h
+++ b/src/common/spin_lock.h
@@ -17,7 +17,7 @@ class SpinLock {
public:
void lock();
void unlock();
- bool try_lock();
+ [[nodiscard]] bool try_lock();
private:
std::atomic_flag lck = ATOMIC_FLAG_INIT;
diff --git a/src/common/string_util.h b/src/common/string_util.h
index 583fd05e6..a32c07c06 100644
--- a/src/common/string_util.h
+++ b/src/common/string_util.h
@@ -12,19 +12,19 @@
namespace Common {
/// Make a string lowercase
-std::string ToLower(std::string str);
+[[nodiscard]] std::string ToLower(std::string str);
/// Make a string uppercase
-std::string ToUpper(std::string str);
+[[nodiscard]] std::string ToUpper(std::string str);
-std::string StringFromBuffer(const std::vector<u8>& data);
+[[nodiscard]] std::string StringFromBuffer(const std::vector<u8>& data);
-std::string StripSpaces(const std::string& s);
-std::string StripQuotes(const std::string& s);
+[[nodiscard]] std::string StripSpaces(const std::string& s);
+[[nodiscard]] std::string StripQuotes(const std::string& s);
-std::string StringFromBool(bool value);
+[[nodiscard]] std::string StringFromBool(bool value);
-std::string TabsToSpaces(int tab_size, std::string in);
+[[nodiscard]] std::string TabsToSpaces(int tab_size, std::string in);
void SplitString(const std::string& str, char delim, std::vector<std::string>& output);
@@ -34,14 +34,15 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
void BuildCompleteFilename(std::string& _CompleteFilename, const std::string& _Path,
const std::string& _Filename);
-std::string ReplaceAll(std::string result, const std::string& src, const std::string& dest);
+[[nodiscard]] std::string ReplaceAll(std::string result, const std::string& src,
+ const std::string& dest);
-std::string UTF16ToUTF8(const std::u16string& input);
-std::u16string UTF8ToUTF16(const std::string& input);
+[[nodiscard]] std::string UTF16ToUTF8(const std::u16string& input);
+[[nodiscard]] std::u16string UTF8ToUTF16(const std::string& input);
#ifdef _WIN32
-std::string UTF16ToUTF8(const std::wstring& input);
-std::wstring UTF8ToUTF16W(const std::string& str);
+[[nodiscard]] std::string UTF16ToUTF8(const std::wstring& input);
+[[nodiscard]] std::wstring UTF8ToUTF16W(const std::string& str);
#endif
@@ -50,7 +51,7 @@ std::wstring UTF8ToUTF16W(const std::string& str);
* `other` for equality.
*/
template <typename InIt>
-bool ComparePartialString(InIt begin, InIt end, const char* other) {
+[[nodiscard]] bool ComparePartialString(InIt begin, InIt end, const char* other) {
for (; begin != end && *other != '\0'; ++begin, ++other) {
if (*begin != *other) {
return false;
@@ -64,26 +65,15 @@ bool ComparePartialString(InIt begin, InIt end, const char* other) {
* Creates a std::string from a fixed-size NUL-terminated char buffer. If the buffer isn't
* NUL-terminated then the string ends at max_len characters.
*/
-std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t max_len);
+[[nodiscard]] std::string StringFromFixedZeroTerminatedBuffer(const char* buffer,
+ std::size_t max_len);
/**
* Creates a UTF-16 std::u16string from a fixed-size NUL-terminated char buffer. If the buffer isn't
* null-terminated, then the string ends at the greatest multiple of two less then or equal to
* max_len_bytes.
*/
-std::u16string UTF16StringFromFixedZeroTerminatedBuffer(std::u16string_view buffer,
- std::size_t max_len);
-
-/**
- * Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's
- * intended to be used to strip a system-specific build directory from the `__FILE__` macro,
- * leaving only the path relative to the sources root.
- *
- * @param path The input file path as a null-terminated string
- * @param root The name of the root source directory as a null-terminated string. Path up to and
- * including the last occurrence of this name will be stripped
- * @return A pointer to the same string passed as `path`, but starting at the trimmed portion
- */
-const char* TrimSourcePath(const char* path, const char* root = "src");
+[[nodiscard]] std::u16string UTF16StringFromFixedZeroTerminatedBuffer(std::u16string_view buffer,
+ std::size_t max_len);
} // namespace Common
diff --git a/src/common/telemetry.cpp b/src/common/telemetry.cpp
index 16d42facd..6241d08b3 100644
--- a/src/common/telemetry.cpp
+++ b/src/common/telemetry.cpp
@@ -12,7 +12,7 @@
#include "common/x64/cpu_detect.h"
#endif
-namespace Telemetry {
+namespace Common::Telemetry {
void FieldCollection::Accept(VisitorInterface& visitor) const {
for (const auto& field : fields) {
@@ -88,4 +88,4 @@ void AppendOSInfo(FieldCollection& fc) {
#endif
}
-} // namespace Telemetry
+} // namespace Common::Telemetry
diff --git a/src/common/telemetry.h b/src/common/telemetry.h
index 854a73fae..a50c5d1de 100644
--- a/src/common/telemetry.h
+++ b/src/common/telemetry.h
@@ -10,7 +10,7 @@
#include <string>
#include "common/common_types.h"
-namespace Telemetry {
+namespace Common::Telemetry {
/// Field type, used for grouping fields together in the final submitted telemetry log
enum class FieldType : u8 {
@@ -63,30 +63,30 @@ public:
void Accept(VisitorInterface& visitor) const override;
- const std::string& GetName() const override {
+ [[nodiscard]] const std::string& GetName() const override {
return name;
}
/**
* Returns the type of the field.
*/
- FieldType GetType() const {
+ [[nodiscard]] FieldType GetType() const {
return type;
}
/**
* Returns the value of the field.
*/
- const T& GetValue() const {
+ [[nodiscard]] const T& GetValue() const {
return value;
}
- bool operator==(const Field& other) const {
+ [[nodiscard]] bool operator==(const Field& other) const {
return (type == other.type) && (name == other.name) && (value == other.value);
}
- bool operator!=(const Field& other) const {
- return !(*this == other);
+ [[nodiscard]] bool operator!=(const Field& other) const {
+ return !operator==(other);
}
private:
@@ -196,4 +196,4 @@ void AppendCPUInfo(FieldCollection& fc);
/// such as platform name, etc.
void AppendOSInfo(FieldCollection& fc);
-} // namespace Telemetry
+} // namespace Common::Telemetry
diff --git a/src/common/thread.cpp b/src/common/thread.cpp
index 8e5935e6a..d2c1ac60d 100644
--- a/src/common/thread.cpp
+++ b/src/common/thread.cpp
@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/common_funcs.h"
+#include "common/logging/log.h"
#include "common/thread.h"
#ifdef __APPLE__
#include <mach/mach.h>
@@ -19,6 +21,8 @@
#include <unistd.h>
#endif
+#include <string>
+
#ifdef __FreeBSD__
#define cpu_set_t cpuset_t
#endif
@@ -110,6 +114,14 @@ void SetCurrentThreadName(const char* name) {
pthread_set_name_np(pthread_self(), name);
#elif defined(__NetBSD__)
pthread_setname_np(pthread_self(), "%s", (void*)name);
+#elif defined(__linux__)
+ // Linux limits thread names to 15 characters and will outright reject any
+ // attempt to set a longer name with ERANGE.
+ std::string truncated(name, std::min(strlen(name), static_cast<size_t>(15)));
+ if (int e = pthread_setname_np(pthread_self(), truncated.c_str())) {
+ errno = e;
+ LOG_ERROR(Common, "Failed to set thread name to '{}': {}", truncated, GetLastErrorMsg());
+ }
#else
pthread_setname_np(pthread_self(), name);
#endif
diff --git a/src/common/thread.h b/src/common/thread.h
index 52b359413..a8c17c71a 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -4,6 +4,7 @@
#pragma once
+#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstddef>
@@ -25,13 +26,13 @@ public:
void Wait() {
std::unique_lock lk{mutex};
- condvar.wait(lk, [&] { return is_set; });
+ condvar.wait(lk, [&] { return is_set.load(); });
is_set = false;
}
bool WaitFor(const std::chrono::nanoseconds& time) {
std::unique_lock lk{mutex};
- if (!condvar.wait_for(lk, time, [this] { return is_set; }))
+ if (!condvar.wait_for(lk, time, [this] { return is_set.load(); }))
return false;
is_set = false;
return true;
@@ -40,7 +41,7 @@ public:
template <class Clock, class Duration>
bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) {
std::unique_lock lk{mutex};
- if (!condvar.wait_until(lk, time, [this] { return is_set; }))
+ if (!condvar.wait_until(lk, time, [this] { return is_set.load(); }))
return false;
is_set = false;
return true;
@@ -54,9 +55,9 @@ public:
}
private:
- bool is_set = false;
std::condition_variable condvar;
std::mutex mutex;
+ std::atomic_bool is_set{false};
};
class Barrier {
diff --git a/src/common/thread_queue_list.h b/src/common/thread_queue_list.h
index 791f99a8c..def9e5d8d 100644
--- a/src/common/thread_queue_list.h
+++ b/src/common/thread_queue_list.h
@@ -18,14 +18,14 @@ struct ThreadQueueList {
using Priority = unsigned int;
// Number of priority levels. (Valid levels are [0..NUM_QUEUES).)
- static const Priority NUM_QUEUES = N;
+ static constexpr Priority NUM_QUEUES = N;
ThreadQueueList() {
first = nullptr;
}
// Only for debugging, returns priority level.
- Priority contains(const T& uid) const {
+ [[nodiscard]] Priority contains(const T& uid) const {
for (Priority i = 0; i < NUM_QUEUES; ++i) {
const Queue& cur = queues[i];
if (std::find(cur.data.cbegin(), cur.data.cend(), uid) != cur.data.cend()) {
@@ -36,7 +36,7 @@ struct ThreadQueueList {
return -1;
}
- T get_first() const {
+ [[nodiscard]] T get_first() const {
const Queue* cur = first;
while (cur != nullptr) {
if (!cur->data.empty()) {
@@ -49,7 +49,7 @@ struct ThreadQueueList {
}
template <typename UnaryPredicate>
- T get_first_filter(UnaryPredicate filter) const {
+ [[nodiscard]] T get_first_filter(UnaryPredicate filter) const {
const Queue* cur = first;
while (cur != nullptr) {
if (!cur->data.empty()) {
@@ -129,7 +129,7 @@ struct ThreadQueueList {
first = nullptr;
}
- bool empty(Priority priority) const {
+ [[nodiscard]] bool empty(Priority priority) const {
const Queue* cur = &queues[priority];
return cur->data.empty();
}
diff --git a/src/common/threadsafe_queue.h b/src/common/threadsafe_queue.h
index 8268bbd5c..a4647314a 100644
--- a/src/common/threadsafe_queue.h
+++ b/src/common/threadsafe_queue.h
@@ -25,15 +25,15 @@ public:
delete read_ptr;
}
- std::size_t Size() const {
+ [[nodiscard]] std::size_t Size() const {
return size.load();
}
- bool Empty() const {
+ [[nodiscard]] bool Empty() const {
return Size() == 0;
}
- T& Front() const {
+ [[nodiscard]] T& Front() const {
return read_ptr->current;
}
@@ -130,15 +130,15 @@ private:
template <typename T>
class MPSCQueue {
public:
- std::size_t Size() const {
+ [[nodiscard]] std::size_t Size() const {
return spsc_queue.Size();
}
- bool Empty() const {
+ [[nodiscard]] bool Empty() const {
return spsc_queue.Empty();
}
- T& Front() const {
+ [[nodiscard]] T& Front() const {
return spsc_queue.Front();
}
diff --git a/src/common/time_zone.h b/src/common/time_zone.h
index 945daa09c..9f5939ca5 100644
--- a/src/common/time_zone.h
+++ b/src/common/time_zone.h
@@ -10,9 +10,9 @@
namespace Common::TimeZone {
/// Gets the default timezone, i.e. "GMT"
-std::string GetDefaultTimeZone();
+[[nodiscard]] std::string GetDefaultTimeZone();
/// Gets the offset of the current timezone (from the default), in seconds
-std::chrono::seconds GetCurrentOffsetSeconds();
+[[nodiscard]] std::chrono::seconds GetCurrentOffsetSeconds();
} // namespace Common::TimeZone
diff --git a/src/common/timer.h b/src/common/timer.h
index 27b521baa..8894a143d 100644
--- a/src/common/timer.h
+++ b/src/common/timer.h
@@ -19,18 +19,18 @@ public:
// The time difference is always returned in milliseconds, regardless of alternative internal
// representation
- std::chrono::milliseconds GetTimeDifference();
+ [[nodiscard]] std::chrono::milliseconds GetTimeDifference();
void AddTimeDifference();
- static std::chrono::seconds GetTimeSinceJan1970();
- static std::chrono::seconds GetLocalTimeSinceJan1970();
- static double GetDoubleTime();
+ [[nodiscard]] static std::chrono::seconds GetTimeSinceJan1970();
+ [[nodiscard]] static std::chrono::seconds GetLocalTimeSinceJan1970();
+ [[nodiscard]] static double GetDoubleTime();
- static std::string GetTimeFormatted();
- std::string GetTimeElapsedFormatted() const;
- std::chrono::milliseconds GetTimeElapsed();
+ [[nodiscard]] static std::string GetTimeFormatted();
+ [[nodiscard]] std::string GetTimeElapsedFormatted() const;
+ [[nodiscard]] std::chrono::milliseconds GetTimeElapsed();
- static std::chrono::milliseconds GetTimeMs();
+ [[nodiscard]] static std::chrono::milliseconds GetTimeMs();
private:
std::chrono::milliseconds m_LastTime;
diff --git a/src/common/uint128.h b/src/common/uint128.h
index 503cd2d0c..969259ab6 100644
--- a/src/common/uint128.h
+++ b/src/common/uint128.h
@@ -10,13 +10,13 @@
namespace Common {
// This function multiplies 2 u64 values and divides it by a u64 value.
-u64 MultiplyAndDivide64(u64 a, u64 b, u64 d);
+[[nodiscard]] u64 MultiplyAndDivide64(u64 a, u64 b, u64 d);
// This function multiplies 2 u64 values and produces a u128 value;
-u128 Multiply64Into128(u64 a, u64 b);
+[[nodiscard]] u128 Multiply64Into128(u64 a, u64 b);
// This function divides a u128 by a u32 value and produces two u64 values:
// the result of division and the remainder
-std::pair<u64, u64> Divide128On32(u128 dividend, u32 divisor);
+[[nodiscard]] std::pair<u64, u64> Divide128On32(u128 dividend, u32 divisor);
} // namespace Common
diff --git a/src/common/uuid.h b/src/common/uuid.h
index 4d3af8cec..4ab9a25f0 100644
--- a/src/common/uuid.h
+++ b/src/common/uuid.h
@@ -19,21 +19,21 @@ struct UUID {
constexpr explicit UUID(const u128& id) : uuid{id} {}
constexpr explicit UUID(const u64 lo, const u64 hi) : uuid{{lo, hi}} {}
- constexpr explicit operator bool() const {
+ [[nodiscard]] constexpr explicit operator bool() const {
return uuid[0] != INVALID_UUID[0] && uuid[1] != INVALID_UUID[1];
}
- constexpr bool operator==(const UUID& rhs) const {
+ [[nodiscard]] constexpr bool operator==(const UUID& rhs) const {
// TODO(DarkLordZach): Replace with uuid == rhs.uuid with C++20
return uuid[0] == rhs.uuid[0] && uuid[1] == rhs.uuid[1];
}
- constexpr bool operator!=(const UUID& rhs) const {
+ [[nodiscard]] constexpr bool operator!=(const UUID& rhs) const {
return !operator==(rhs);
}
// TODO(ogniK): Properly generate uuids based on RFC-4122
- static UUID Generate();
+ [[nodiscard]] static UUID Generate();
// Set the UUID to {0,0} to be considered an invalid user
constexpr void Invalidate() {
@@ -41,12 +41,12 @@ struct UUID {
}
// TODO(ogniK): Properly generate a Nintendo ID
- constexpr u64 GetNintendoID() const {
+ [[nodiscard]] constexpr u64 GetNintendoID() const {
return uuid[0];
}
- std::string Format() const;
- std::string FormatSwitch() const;
+ [[nodiscard]] std::string Format() const;
+ [[nodiscard]] std::string FormatSwitch() const;
};
static_assert(sizeof(UUID) == 16, "UUID is an invalid size!");
diff --git a/src/common/vector_math.h b/src/common/vector_math.h
index 429485329..2a0fcf541 100644
--- a/src/common/vector_math.h
+++ b/src/common/vector_math.h
@@ -52,15 +52,15 @@ public:
constexpr Vec2(const T& x_, const T& y_) : x(x_), y(y_) {}
template <typename T2>
- constexpr Vec2<T2> Cast() const {
+ [[nodiscard]] constexpr Vec2<T2> Cast() const {
return Vec2<T2>(static_cast<T2>(x), static_cast<T2>(y));
}
- static constexpr Vec2 AssignToAll(const T& f) {
+ [[nodiscard]] static constexpr Vec2 AssignToAll(const T& f) {
return Vec2{f, f};
}
- constexpr Vec2<decltype(T{} + T{})> operator+(const Vec2& other) const {
+ [[nodiscard]] constexpr Vec2<decltype(T{} + T{})> operator+(const Vec2& other) const {
return {x + other.x, y + other.y};
}
constexpr Vec2& operator+=(const Vec2& other) {
@@ -68,7 +68,7 @@ public:
y += other.y;
return *this;
}
- constexpr Vec2<decltype(T{} - T{})> operator-(const Vec2& other) const {
+ [[nodiscard]] constexpr Vec2<decltype(T{} - T{})> operator-(const Vec2& other) const {
return {x - other.x, y - other.y};
}
constexpr Vec2& operator-=(const Vec2& other) {
@@ -78,15 +78,15 @@ public:
}
template <typename U = T>
- constexpr Vec2<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
+ [[nodiscard]] constexpr Vec2<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
return {-x, -y};
}
- constexpr Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const {
+ [[nodiscard]] constexpr Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const {
return {x * other.x, y * other.y};
}
template <typename V>
- constexpr Vec2<decltype(T{} * V{})> operator*(const V& f) const {
+ [[nodiscard]] constexpr Vec2<decltype(T{} * V{})> operator*(const V& f) const {
return {x * f, y * f};
}
@@ -97,7 +97,7 @@ public:
}
template <typename V>
- constexpr Vec2<decltype(T{} / V{})> operator/(const V& f) const {
+ [[nodiscard]] constexpr Vec2<decltype(T{} / V{})> operator/(const V& f) const {
return {x / f, y / f};
}
@@ -107,18 +107,18 @@ public:
return *this;
}
- constexpr T Length2() const {
+ [[nodiscard]] constexpr T Length2() const {
return x * x + y * y;
}
// Only implemented for T=float
- float Length() const;
- float Normalize(); // returns the previous length, which is often useful
+ [[nodiscard]] float Length() const;
+ [[nodiscard]] float Normalize(); // returns the previous length, which is often useful
- constexpr T& operator[](std::size_t i) {
+ [[nodiscard]] constexpr T& operator[](std::size_t i) {
return *((&x) + i);
}
- constexpr const T& operator[](std::size_t i) const {
+ [[nodiscard]] constexpr const T& operator[](std::size_t i) const {
return *((&x) + i);
}
@@ -128,46 +128,46 @@ public:
}
// Common aliases: UV (texel coordinates), ST (texture coordinates)
- constexpr T& u() {
+ [[nodiscard]] constexpr T& u() {
return x;
}
- constexpr T& v() {
+ [[nodiscard]] constexpr T& v() {
return y;
}
- constexpr T& s() {
+ [[nodiscard]] constexpr T& s() {
return x;
}
- constexpr T& t() {
+ [[nodiscard]] constexpr T& t() {
return y;
}
- constexpr const T& u() const {
+ [[nodiscard]] constexpr const T& u() const {
return x;
}
- constexpr const T& v() const {
+ [[nodiscard]] constexpr const T& v() const {
return y;
}
- constexpr const T& s() const {
+ [[nodiscard]] constexpr const T& s() const {
return x;
}
- constexpr const T& t() const {
+ [[nodiscard]] constexpr const T& t() const {
return y;
}
// swizzlers - create a subvector of specific components
- constexpr Vec2 yx() const {
+ [[nodiscard]] constexpr Vec2 yx() const {
return Vec2(y, x);
}
- constexpr Vec2 vu() const {
+ [[nodiscard]] constexpr Vec2 vu() const {
return Vec2(y, x);
}
- constexpr Vec2 ts() const {
+ [[nodiscard]] constexpr Vec2 ts() const {
return Vec2(y, x);
}
};
template <typename T, typename V>
-constexpr Vec2<T> operator*(const V& f, const Vec2<T>& vec) {
+[[nodiscard]] constexpr Vec2<T> operator*(const V& f, const Vec2<T>& vec) {
return Vec2<T>(f * vec.x, f * vec.y);
}
@@ -196,15 +196,15 @@ public:
constexpr Vec3(const T& x_, const T& y_, const T& z_) : x(x_), y(y_), z(z_) {}
template <typename T2>
- constexpr Vec3<T2> Cast() const {
+ [[nodiscard]] constexpr Vec3<T2> Cast() const {
return Vec3<T2>(static_cast<T2>(x), static_cast<T2>(y), static_cast<T2>(z));
}
- static constexpr Vec3 AssignToAll(const T& f) {
+ [[nodiscard]] static constexpr Vec3 AssignToAll(const T& f) {
return Vec3(f, f, f);
}
- constexpr Vec3<decltype(T{} + T{})> operator+(const Vec3& other) const {
+ [[nodiscard]] constexpr Vec3<decltype(T{} + T{})> operator+(const Vec3& other) const {
return {x + other.x, y + other.y, z + other.z};
}
@@ -215,7 +215,7 @@ public:
return *this;
}
- constexpr Vec3<decltype(T{} - T{})> operator-(const Vec3& other) const {
+ [[nodiscard]] constexpr Vec3<decltype(T{} - T{})> operator-(const Vec3& other) const {
return {x - other.x, y - other.y, z - other.z};
}
@@ -227,16 +227,16 @@ public:
}
template <typename U = T>
- constexpr Vec3<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
+ [[nodiscard]] constexpr Vec3<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
return {-x, -y, -z};
}
- constexpr Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const {
+ [[nodiscard]] constexpr Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const {
return {x * other.x, y * other.y, z * other.z};
}
template <typename V>
- constexpr Vec3<decltype(T{} * V{})> operator*(const V& f) const {
+ [[nodiscard]] constexpr Vec3<decltype(T{} * V{})> operator*(const V& f) const {
return {x * f, y * f, z * f};
}
@@ -246,7 +246,7 @@ public:
return *this;
}
template <typename V>
- constexpr Vec3<decltype(T{} / V{})> operator/(const V& f) const {
+ [[nodiscard]] constexpr Vec3<decltype(T{} / V{})> operator/(const V& f) const {
return {x / f, y / f, z / f};
}
@@ -256,20 +256,20 @@ public:
return *this;
}
- constexpr T Length2() const {
+ [[nodiscard]] constexpr T Length2() const {
return x * x + y * y + z * z;
}
// Only implemented for T=float
- float Length() const;
- Vec3 Normalized() const;
- float Normalize(); // returns the previous length, which is often useful
+ [[nodiscard]] float Length() const;
+ [[nodiscard]] Vec3 Normalized() const;
+ [[nodiscard]] float Normalize(); // returns the previous length, which is often useful
- constexpr T& operator[](std::size_t i) {
+ [[nodiscard]] constexpr T& operator[](std::size_t i) {
return *((&x) + i);
}
- constexpr const T& operator[](std::size_t i) const {
+ [[nodiscard]] constexpr const T& operator[](std::size_t i) const {
return *((&x) + i);
}
@@ -280,63 +280,63 @@ public:
}
// Common aliases: UVW (texel coordinates), RGB (colors), STQ (texture coordinates)
- constexpr T& u() {
+ [[nodiscard]] constexpr T& u() {
return x;
}
- constexpr T& v() {
+ [[nodiscard]] constexpr T& v() {
return y;
}
- constexpr T& w() {
+ [[nodiscard]] constexpr T& w() {
return z;
}
- constexpr T& r() {
+ [[nodiscard]] constexpr T& r() {
return x;
}
- constexpr T& g() {
+ [[nodiscard]] constexpr T& g() {
return y;
}
- constexpr T& b() {
+ [[nodiscard]] constexpr T& b() {
return z;
}
- constexpr T& s() {
+ [[nodiscard]] constexpr T& s() {
return x;
}
- constexpr T& t() {
+ [[nodiscard]] constexpr T& t() {
return y;
}
- constexpr T& q() {
+ [[nodiscard]] constexpr T& q() {
return z;
}
- constexpr const T& u() const {
+ [[nodiscard]] constexpr const T& u() const {
return x;
}
- constexpr const T& v() const {
+ [[nodiscard]] constexpr const T& v() const {
return y;
}
- constexpr const T& w() const {
+ [[nodiscard]] constexpr const T& w() const {
return z;
}
- constexpr const T& r() const {
+ [[nodiscard]] constexpr const T& r() const {
return x;
}
- constexpr const T& g() const {
+ [[nodiscard]] constexpr const T& g() const {
return y;
}
- constexpr const T& b() const {
+ [[nodiscard]] constexpr const T& b() const {
return z;
}
- constexpr const T& s() const {
+ [[nodiscard]] constexpr const T& s() const {
return x;
}
- constexpr const T& t() const {
+ [[nodiscard]] constexpr const T& t() const {
return y;
}
- constexpr const T& q() const {
+ [[nodiscard]] constexpr const T& q() const {
return z;
}
@@ -345,7 +345,7 @@ public:
// _DEFINE_SWIZZLER2 defines a single such function, DEFINE_SWIZZLER2 defines all of them for all
// component names (x<->r) and permutations (xy<->yx)
#define _DEFINE_SWIZZLER2(a, b, name) \
- constexpr Vec2<T> name() const { \
+ [[nodiscard]] constexpr Vec2<T> name() const { \
return Vec2<T>(a, b); \
}
#define DEFINE_SWIZZLER2(a, b, a2, b2, a3, b3, a4, b4) \
@@ -366,7 +366,7 @@ public:
};
template <typename T, typename V>
-constexpr Vec3<T> operator*(const V& f, const Vec3<T>& vec) {
+[[nodiscard]] constexpr Vec3<T> operator*(const V& f, const Vec3<T>& vec) {
return Vec3<T>(f * vec.x, f * vec.y, f * vec.z);
}
@@ -402,16 +402,16 @@ public:
: x(x_), y(y_), z(z_), w(w_) {}
template <typename T2>
- constexpr Vec4<T2> Cast() const {
+ [[nodiscard]] constexpr Vec4<T2> Cast() const {
return Vec4<T2>(static_cast<T2>(x), static_cast<T2>(y), static_cast<T2>(z),
static_cast<T2>(w));
}
- static constexpr Vec4 AssignToAll(const T& f) {
+ [[nodiscard]] static constexpr Vec4 AssignToAll(const T& f) {
return Vec4(f, f, f, f);
}
- constexpr Vec4<decltype(T{} + T{})> operator+(const Vec4& other) const {
+ [[nodiscard]] constexpr Vec4<decltype(T{} + T{})> operator+(const Vec4& other) const {
return {x + other.x, y + other.y, z + other.z, w + other.w};
}
@@ -423,7 +423,7 @@ public:
return *this;
}
- constexpr Vec4<decltype(T{} - T{})> operator-(const Vec4& other) const {
+ [[nodiscard]] constexpr Vec4<decltype(T{} - T{})> operator-(const Vec4& other) const {
return {x - other.x, y - other.y, z - other.z, w - other.w};
}
@@ -436,16 +436,16 @@ public:
}
template <typename U = T>
- constexpr Vec4<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
+ [[nodiscard]] constexpr Vec4<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
return {-x, -y, -z, -w};
}
- constexpr Vec4<decltype(T{} * T{})> operator*(const Vec4& other) const {
+ [[nodiscard]] constexpr Vec4<decltype(T{} * T{})> operator*(const Vec4& other) const {
return {x * other.x, y * other.y, z * other.z, w * other.w};
}
template <typename V>
- constexpr Vec4<decltype(T{} * V{})> operator*(const V& f) const {
+ [[nodiscard]] constexpr Vec4<decltype(T{} * V{})> operator*(const V& f) const {
return {x * f, y * f, z * f, w * f};
}
@@ -456,7 +456,7 @@ public:
}
template <typename V>
- constexpr Vec4<decltype(T{} / V{})> operator/(const V& f) const {
+ [[nodiscard]] constexpr Vec4<decltype(T{} / V{})> operator/(const V& f) const {
return {x / f, y / f, z / f, w / f};
}
@@ -466,15 +466,15 @@ public:
return *this;
}
- constexpr T Length2() const {
+ [[nodiscard]] constexpr T Length2() const {
return x * x + y * y + z * z + w * w;
}
- constexpr T& operator[](std::size_t i) {
+ [[nodiscard]] constexpr T& operator[](std::size_t i) {
return *((&x) + i);
}
- constexpr const T& operator[](std::size_t i) const {
+ [[nodiscard]] constexpr const T& operator[](std::size_t i) const {
return *((&x) + i);
}
@@ -486,29 +486,29 @@ public:
}
// Common alias: RGBA (colors)
- constexpr T& r() {
+ [[nodiscard]] constexpr T& r() {
return x;
}
- constexpr T& g() {
+ [[nodiscard]] constexpr T& g() {
return y;
}
- constexpr T& b() {
+ [[nodiscard]] constexpr T& b() {
return z;
}
- constexpr T& a() {
+ [[nodiscard]] constexpr T& a() {
return w;
}
- constexpr const T& r() const {
+ [[nodiscard]] constexpr const T& r() const {
return x;
}
- constexpr const T& g() const {
+ [[nodiscard]] constexpr const T& g() const {
return y;
}
- constexpr const T& b() const {
+ [[nodiscard]] constexpr const T& b() const {
return z;
}
- constexpr const T& a() const {
+ [[nodiscard]] constexpr const T& a() const {
return w;
}
@@ -520,7 +520,7 @@ public:
// DEFINE_SWIZZLER2_COMP2 defines two component functions for all component names (x<->r) and
// permutations (xy<->yx)
#define _DEFINE_SWIZZLER2(a, b, name) \
- constexpr Vec2<T> name() const { \
+ [[nodiscard]] constexpr Vec2<T> name() const { \
return Vec2<T>(a, b); \
}
#define DEFINE_SWIZZLER2_COMP1(a, a2) \
@@ -547,7 +547,7 @@ public:
#undef _DEFINE_SWIZZLER2
#define _DEFINE_SWIZZLER3(a, b, c, name) \
- constexpr Vec3<T> name() const { \
+ [[nodiscard]] constexpr Vec3<T> name() const { \
return Vec3<T>(a, b, c); \
}
#define DEFINE_SWIZZLER3_COMP1(a, a2) \
@@ -581,7 +581,7 @@ public:
};
template <typename T, typename V>
-constexpr Vec4<decltype(V{} * T{})> operator*(const V& f, const Vec4<T>& vec) {
+[[nodiscard]] constexpr Vec4<decltype(V{} * T{})> operator*(const V& f, const Vec4<T>& vec) {
return {f * vec.x, f * vec.y, f * vec.z, f * vec.w};
}
@@ -593,39 +593,41 @@ constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec2<T>& a, const Vec2<T>& b
}
template <typename T>
-constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec3<T>& a, const Vec3<T>& b) {
+[[nodiscard]] constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec3<T>& a, const Vec3<T>& b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
template <typename T>
-constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec4<T>& a, const Vec4<T>& b) {
+[[nodiscard]] constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec4<T>& a, const Vec4<T>& b) {
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
}
template <typename T>
-constexpr Vec3<decltype(T{} * T{} - T{} * T{})> Cross(const Vec3<T>& a, const Vec3<T>& b) {
+[[nodiscard]] constexpr Vec3<decltype(T{} * T{} - T{} * T{})> Cross(const Vec3<T>& a,
+ const Vec3<T>& b) {
return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
}
// linear interpolation via float: 0.0=begin, 1.0=end
template <typename X>
-constexpr decltype(X{} * float{} + X{} * float{}) Lerp(const X& begin, const X& end,
- const float t) {
+[[nodiscard]] constexpr decltype(X{} * float{} + X{} * float{}) Lerp(const X& begin, const X& end,
+ const float t) {
return begin * (1.f - t) + end * t;
}
// linear interpolation via int: 0=begin, base=end
template <typename X, int base>
-constexpr decltype((X{} * int{} + X{} * int{}) / base) LerpInt(const X& begin, const X& end,
- const int t) {
+[[nodiscard]] constexpr decltype((X{} * int{} + X{} * int{}) / base) LerpInt(const X& begin,
+ const X& end,
+ const int t) {
return (begin * (base - t) + end * t) / base;
}
// bilinear interpolation. s is for interpolating x00-x01 and x10-x11, and t is for the second
// interpolation.
template <typename X>
-constexpr auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X& x11, const float s,
- const float t) {
+[[nodiscard]] constexpr auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X& x11,
+ const float s, const float t) {
auto y0 = Lerp(x00, x01, s);
auto y1 = Lerp(x10, x11, s);
return Lerp(y0, y1, t);
@@ -633,42 +635,42 @@ constexpr auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X&
// Utility vector factories
template <typename T>
-constexpr Vec2<T> MakeVec(const T& x, const T& y) {
+[[nodiscard]] constexpr Vec2<T> MakeVec(const T& x, const T& y) {
return Vec2<T>{x, y};
}
template <typename T>
-constexpr Vec3<T> MakeVec(const T& x, const T& y, const T& z) {
+[[nodiscard]] constexpr Vec3<T> MakeVec(const T& x, const T& y, const T& z) {
return Vec3<T>{x, y, z};
}
template <typename T>
-constexpr Vec4<T> MakeVec(const T& x, const T& y, const Vec2<T>& zw) {
+[[nodiscard]] constexpr Vec4<T> MakeVec(const T& x, const T& y, const Vec2<T>& zw) {
return MakeVec(x, y, zw[0], zw[1]);
}
template <typename T>
-constexpr Vec3<T> MakeVec(const Vec2<T>& xy, const T& z) {
+[[nodiscard]] constexpr Vec3<T> MakeVec(const Vec2<T>& xy, const T& z) {
return MakeVec(xy[0], xy[1], z);
}
template <typename T>
-constexpr Vec3<T> MakeVec(const T& x, const Vec2<T>& yz) {
+[[nodiscard]] constexpr Vec3<T> MakeVec(const T& x, const Vec2<T>& yz) {
return MakeVec(x, yz[0], yz[1]);
}
template <typename T>
-constexpr Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w) {
+[[nodiscard]] constexpr Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w) {
return Vec4<T>{x, y, z, w};
}
template <typename T>
-constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const T& z, const T& w) {
+[[nodiscard]] constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const T& z, const T& w) {
return MakeVec(xy[0], xy[1], z, w);
}
template <typename T>
-constexpr Vec4<T> MakeVec(const T& x, const Vec2<T>& yz, const T& w) {
+[[nodiscard]] constexpr Vec4<T> MakeVec(const T& x, const Vec2<T>& yz, const T& w) {
return MakeVec(x, yz[0], yz[1], w);
}
@@ -676,17 +678,17 @@ constexpr Vec4<T> MakeVec(const T& x, const Vec2<T>& yz, const T& w) {
// Even if someone wanted to use an odd object like Vec2<Vec2<T>>, the compiler would error
// out soon enough due to misuse of the returned structure.
template <typename T>
-constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const Vec2<T>& zw) {
+[[nodiscard]] constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const Vec2<T>& zw) {
return MakeVec(xy[0], xy[1], zw[0], zw[1]);
}
template <typename T>
-constexpr Vec4<T> MakeVec(const Vec3<T>& xyz, const T& w) {
+[[nodiscard]] constexpr Vec4<T> MakeVec(const Vec3<T>& xyz, const T& w) {
return MakeVec(xyz[0], xyz[1], xyz[2], w);
}
template <typename T>
-constexpr Vec4<T> MakeVec(const T& x, const Vec3<T>& yzw) {
+[[nodiscard]] constexpr Vec4<T> MakeVec(const T& x, const Vec3<T>& yzw) {
return MakeVec(x, yzw[0], yzw[1], yzw[2]);
}
diff --git a/src/common/virtual_buffer.cpp b/src/common/virtual_buffer.cpp
index b426f4747..b009cb500 100644
--- a/src/common/virtual_buffer.cpp
+++ b/src/common/virtual_buffer.cpp
@@ -5,16 +5,7 @@
#ifdef _WIN32
#include <windows.h>
#else
-#include <stdio.h>
#include <sys/mman.h>
-#include <sys/types.h>
-#if defined __APPLE__ || defined __FreeBSD__ || defined __OpenBSD__
-#include <sys/sysctl.h>
-#elif defined __HAIKU__
-#include <OS.h>
-#else
-#include <sys/sysinfo.h>
-#endif
#endif
#include "common/assert.h"
@@ -38,7 +29,7 @@ void* AllocateMemoryPages(std::size_t size) {
return base;
}
-void FreeMemoryPages(void* base, std::size_t size) {
+void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) {
if (!base) {
return;
}
diff --git a/src/common/virtual_buffer.h b/src/common/virtual_buffer.h
index da064e59e..125cb42f0 100644
--- a/src/common/virtual_buffer.h
+++ b/src/common/virtual_buffer.h
@@ -30,23 +30,23 @@ public:
base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
}
- constexpr const T& operator[](std::size_t index) const {
+ [[nodiscard]] constexpr const T& operator[](std::size_t index) const {
return base_ptr[index];
}
- constexpr T& operator[](std::size_t index) {
+ [[nodiscard]] constexpr T& operator[](std::size_t index) {
return base_ptr[index];
}
- constexpr T* data() {
+ [[nodiscard]] constexpr T* data() {
return base_ptr;
}
- constexpr const T* data() const {
+ [[nodiscard]] constexpr const T* data() const {
return base_ptr;
}
- constexpr std::size_t size() const {
+ [[nodiscard]] constexpr std::size_t size() const {
return alloc_size / sizeof(T);
}
diff --git a/src/common/wall_clock.h b/src/common/wall_clock.h
index 367d72134..5db30083d 100644
--- a/src/common/wall_clock.h
+++ b/src/common/wall_clock.h
@@ -14,24 +14,24 @@ namespace Common {
class WallClock {
public:
/// Returns current wall time in nanoseconds
- virtual std::chrono::nanoseconds GetTimeNS() = 0;
+ [[nodiscard]] virtual std::chrono::nanoseconds GetTimeNS() = 0;
/// Returns current wall time in microseconds
- virtual std::chrono::microseconds GetTimeUS() = 0;
+ [[nodiscard]] virtual std::chrono::microseconds GetTimeUS() = 0;
/// Returns current wall time in milliseconds
- virtual std::chrono::milliseconds GetTimeMS() = 0;
+ [[nodiscard]] virtual std::chrono::milliseconds GetTimeMS() = 0;
/// Returns current wall time in emulated clock cycles
- virtual u64 GetClockCycles() = 0;
+ [[nodiscard]] virtual u64 GetClockCycles() = 0;
/// Returns current wall time in emulated cpu cycles
- virtual u64 GetCPUCycles() = 0;
+ [[nodiscard]] virtual u64 GetCPUCycles() = 0;
virtual void Pause(bool is_paused) = 0;
/// Tells if the wall clock, uses the host CPU's hardware clock
- bool IsNative() const {
+ [[nodiscard]] bool IsNative() const {
return is_native;
}
@@ -47,7 +47,7 @@ private:
bool is_native;
};
-std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency,
- u32 emulated_clock_frequency);
+[[nodiscard]] std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency,
+ u32 emulated_clock_frequency);
} // namespace Common
diff --git a/src/common/x64/xbyak_abi.h b/src/common/x64/xbyak_abi.h
index a5f5d4fc1..26e4bfda5 100644
--- a/src/common/x64/xbyak_abi.h
+++ b/src/common/x64/xbyak_abi.h
@@ -11,7 +11,7 @@
namespace Common::X64 {
-inline std::size_t RegToIndex(const Xbyak::Reg& reg) {
+constexpr std::size_t RegToIndex(const Xbyak::Reg& reg) {
using Kind = Xbyak::Reg::Kind;
ASSERT_MSG((reg.getKind() & (Kind::REG | Kind::XMM)) != 0,
"RegSet only support GPRs and XMM registers.");
@@ -19,17 +19,17 @@ inline std::size_t RegToIndex(const Xbyak::Reg& reg) {
return reg.getIdx() + (reg.getKind() == Kind::REG ? 0 : 16);
}
-inline Xbyak::Reg64 IndexToReg64(std::size_t reg_index) {
+constexpr Xbyak::Reg64 IndexToReg64(std::size_t reg_index) {
ASSERT(reg_index < 16);
return Xbyak::Reg64(static_cast<int>(reg_index));
}
-inline Xbyak::Xmm IndexToXmm(std::size_t reg_index) {
+constexpr Xbyak::Xmm IndexToXmm(std::size_t reg_index) {
ASSERT(reg_index >= 16 && reg_index < 32);
return Xbyak::Xmm(static_cast<int>(reg_index - 16));
}
-inline Xbyak::Reg IndexToReg(std::size_t reg_index) {
+constexpr Xbyak::Reg IndexToReg(std::size_t reg_index) {
if (reg_index < 16) {
return IndexToReg64(reg_index);
} else {
@@ -45,17 +45,17 @@ inline std::bitset<32> BuildRegSet(std::initializer_list<Xbyak::Reg> regs) {
return bits;
}
-const std::bitset<32> ABI_ALL_GPRS(0x0000FFFF);
-const std::bitset<32> ABI_ALL_XMMS(0xFFFF0000);
+constexpr inline std::bitset<32> ABI_ALL_GPRS(0x0000FFFF);
+constexpr inline std::bitset<32> ABI_ALL_XMMS(0xFFFF0000);
#ifdef _WIN32
// Microsoft x64 ABI
-const Xbyak::Reg ABI_RETURN = Xbyak::util::rax;
-const Xbyak::Reg ABI_PARAM1 = Xbyak::util::rcx;
-const Xbyak::Reg ABI_PARAM2 = Xbyak::util::rdx;
-const Xbyak::Reg ABI_PARAM3 = Xbyak::util::r8;
-const Xbyak::Reg ABI_PARAM4 = Xbyak::util::r9;
+constexpr inline Xbyak::Reg ABI_RETURN = Xbyak::util::rax;
+constexpr inline Xbyak::Reg ABI_PARAM1 = Xbyak::util::rcx;
+constexpr inline Xbyak::Reg ABI_PARAM2 = Xbyak::util::rdx;
+constexpr inline Xbyak::Reg ABI_PARAM3 = Xbyak::util::r8;
+constexpr inline Xbyak::Reg ABI_PARAM4 = Xbyak::util::r9;
const std::bitset<32> ABI_ALL_CALLER_SAVED = BuildRegSet({
// GPRs
@@ -102,11 +102,11 @@ constexpr size_t ABI_SHADOW_SPACE = 0x20;
#else
// System V x86-64 ABI
-const Xbyak::Reg ABI_RETURN = Xbyak::util::rax;
-const Xbyak::Reg ABI_PARAM1 = Xbyak::util::rdi;
-const Xbyak::Reg ABI_PARAM2 = Xbyak::util::rsi;
-const Xbyak::Reg ABI_PARAM3 = Xbyak::util::rdx;
-const Xbyak::Reg ABI_PARAM4 = Xbyak::util::rcx;
+constexpr inline Xbyak::Reg ABI_RETURN = Xbyak::util::rax;
+constexpr inline Xbyak::Reg ABI_PARAM1 = Xbyak::util::rdi;
+constexpr inline Xbyak::Reg ABI_PARAM2 = Xbyak::util::rsi;
+constexpr inline Xbyak::Reg ABI_PARAM3 = Xbyak::util::rdx;
+constexpr inline Xbyak::Reg ABI_PARAM4 = Xbyak::util::rcx;
const std::bitset<32> ABI_ALL_CALLER_SAVED = BuildRegSet({
// GPRs
diff --git a/src/common/zstd_compression.cpp b/src/common/zstd_compression.cpp
index 978526492..5f45459da 100644
--- a/src/common/zstd_compression.cpp
+++ b/src/common/zstd_compression.cpp
@@ -5,7 +5,6 @@
#include <algorithm>
#include <zstd.h>
-#include "common/assert.h"
#include "common/zstd_compression.h"
namespace Common::Compression {
diff --git a/src/common/zstd_compression.h b/src/common/zstd_compression.h
index e9de941c8..c26a30ab9 100644
--- a/src/common/zstd_compression.h
+++ b/src/common/zstd_compression.h
@@ -13,24 +13,25 @@ namespace Common::Compression {
/**
* Compresses a source memory region with Zstandard and returns the compressed data in a vector.
*
- * @param source the uncompressed source memory region.
- * @param source_size the size in bytes of the uncompressed source memory region.
- * @param compression_level the used compression level. Should be between 1 and 22.
+ * @param source The uncompressed source memory region.
+ * @param source_size The size of the uncompressed source memory region.
+ * @param compression_level The used compression level. Should be between 1 and 22.
*
* @return the compressed data.
*/
-std::vector<u8> CompressDataZSTD(const u8* source, std::size_t source_size, s32 compression_level);
+[[nodiscard]] std::vector<u8> CompressDataZSTD(const u8* source, std::size_t source_size,
+ s32 compression_level);
/**
* Compresses a source memory region with Zstandard with the default compression level and returns
* the compressed data in a vector.
*
- * @param source the uncompressed source memory region.
- * @param source_size the size in bytes of the uncompressed source memory region.
+ * @param source The uncompressed source memory region.
+ * @param source_size The size of the uncompressed source memory region.
*
* @return the compressed data.
*/
-std::vector<u8> CompressDataZSTDDefault(const u8* source, std::size_t source_size);
+[[nodiscard]] std::vector<u8> CompressDataZSTDDefault(const u8* source, std::size_t source_size);
/**
* Decompresses a source memory region with Zstandard and returns the uncompressed data in a vector.
@@ -39,6 +40,6 @@ std::vector<u8> CompressDataZSTDDefault(const u8* source, std::size_t source_siz
*
* @return the decompressed data.
*/
-std::vector<u8> DecompressDataZSTD(const std::vector<u8>& compressed);
+[[nodiscard]] std::vector<u8> DecompressDataZSTD(const std::vector<u8>& compressed);
} // namespace Common::Compression \ No newline at end of file
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index c85c9485f..d0c405ec7 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -126,6 +126,8 @@ add_library(core STATIC
file_sys/vfs_vector.h
file_sys/xts_archive.cpp
file_sys/xts_archive.h
+ frontend/applets/controller.cpp
+ frontend/applets/controller.h
frontend/applets/error.cpp
frontend/applets/error.h
frontend/applets/general_frontend.cpp
@@ -244,6 +246,8 @@ add_library(core STATIC
hle/service/am/applet_oe.h
hle/service/am/applets/applets.cpp
hle/service/am/applets/applets.h
+ hle/service/am/applets/controller.cpp
+ hle/service/am/applets/controller.h
hle/service/am/applets/error.cpp
hle/service/am/applets/error.h
hle/service/am/applets/general_backend.cpp
@@ -491,6 +495,7 @@ add_library(core STATIC
hle/service/sm/controller.h
hle/service/sm/sm.cpp
hle/service/sm/sm.h
+ hle/service/sockets/blocking_worker.h
hle/service/sockets/bsd.cpp
hle/service/sockets/bsd.h
hle/service/sockets/ethc.cpp
@@ -501,6 +506,8 @@ add_library(core STATIC
hle/service/sockets/sfdnsres.h
hle/service/sockets/sockets.cpp
hle/service/sockets/sockets.h
+ hle/service/sockets/sockets_translate.cpp
+ hle/service/sockets/sockets_translate.h
hle/service/spl/csrng.cpp
hle/service/spl/csrng.h
hle/service/spl/module.cpp
diff --git a/src/core/arm/cpu_interrupt_handler.cpp b/src/core/arm/cpu_interrupt_handler.cpp
index df0350881..9c8898700 100644
--- a/src/core/arm/cpu_interrupt_handler.cpp
+++ b/src/core/arm/cpu_interrupt_handler.cpp
@@ -7,9 +7,7 @@
namespace Core {
-CPUInterruptHandler::CPUInterruptHandler() : is_interrupted{} {
- interrupt_event = std::make_unique<Common::Event>();
-}
+CPUInterruptHandler::CPUInterruptHandler() : interrupt_event{std::make_unique<Common::Event>()} {}
CPUInterruptHandler::~CPUInterruptHandler() = default;
@@ -17,7 +15,7 @@ void CPUInterruptHandler::SetInterrupt(bool is_interrupted_) {
if (is_interrupted_) {
interrupt_event->Set();
}
- this->is_interrupted = is_interrupted_;
+ is_interrupted = is_interrupted_;
}
void CPUInterruptHandler::AwaitInterrupt() {
diff --git a/src/core/arm/cpu_interrupt_handler.h b/src/core/arm/cpu_interrupt_handler.h
index 3d062d326..71e582f79 100644
--- a/src/core/arm/cpu_interrupt_handler.h
+++ b/src/core/arm/cpu_interrupt_handler.h
@@ -4,6 +4,7 @@
#pragma once
+#include <atomic>
#include <memory>
namespace Common {
@@ -32,8 +33,8 @@ public:
void AwaitInterrupt();
private:
- bool is_interrupted{};
std::unique_ptr<Common::Event> interrupt_event;
+ std::atomic_bool is_interrupted{false};
};
} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
index 443ca72eb..b5f28a86e 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
@@ -143,7 +143,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable&
config.wall_clock_cntpct = uses_wall_clock;
// Safe optimizations
- if (Settings::values.cpu_accuracy != Settings::CPUAccuracy::Accurate) {
+ if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::DebugMode) {
if (!Settings::values.cpuopt_page_tables) {
config.page_table = nullptr;
}
@@ -170,6 +170,17 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable&
}
}
+ // Unsafe optimizations
+ if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::Unsafe) {
+ config.unsafe_optimizations = true;
+ if (Settings::values.cpuopt_unsafe_unfuse_fma) {
+ config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
+ }
+ if (Settings::values.cpuopt_unsafe_reduce_fp_error) {
+ config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;
+ }
+ }
+
return std::make_unique<Dynarmic::A32::Jit>(config);
}
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
index a63a04a25..ce9968724 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
@@ -195,7 +195,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable&
config.wall_clock_cntpct = uses_wall_clock;
// Safe optimizations
- if (Settings::values.cpu_accuracy != Settings::CPUAccuracy::Accurate) {
+ if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::DebugMode) {
if (!Settings::values.cpuopt_page_tables) {
config.page_table = nullptr;
}
@@ -222,6 +222,17 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable&
}
}
+ // Unsafe optimizations
+ if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::Unsafe) {
+ config.unsafe_optimizations = true;
+ if (Settings::values.cpuopt_unsafe_unfuse_fma) {
+ config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
+ }
+ if (Settings::values.cpuopt_unsafe_reduce_fp_error) {
+ config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;
+ }
+ }
+
return std::make_shared<Dynarmic::A64::Jit>(config);
}
diff --git a/src/core/core.cpp b/src/core/core.cpp
index e598c0e2b..df81e8e2c 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -113,7 +113,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName());
}
- if (FileUtil::IsDirectory(path))
+ if (Common::FS::IsDirectory(path))
return vfs->OpenFile(path + "/" + "main", FileSys::Mode::Read);
return vfs->OpenFile(path, FileSys::Mode::Read);
@@ -146,7 +146,7 @@ struct System::Impl {
ResultStatus Init(System& system, Frontend::EmuWindow& emu_window) {
LOG_DEBUG(HW_Memory, "initialized OK");
- device_memory = std::make_unique<Core::DeviceMemory>(system);
+ device_memory = std::make_unique<Core::DeviceMemory>();
is_multicore = Settings::values.use_multi_core.GetValue();
is_async_gpu = is_multicore || Settings::values.use_asynchronous_gpu_emulation.GetValue();
@@ -188,7 +188,6 @@ struct System::Impl {
if (!gpu_core) {
return ResultStatus::ErrorVideoCore;
}
- gpu_core->Renderer().Rasterizer().SetupDirtyFlags();
is_powered_on = true;
exit_lock = false;
@@ -269,14 +268,14 @@ struct System::Impl {
// Log last frame performance stats if game was loded
if (perf_stats) {
const auto perf_results = GetAndResetPerfStats();
- telemetry_session->AddField(Telemetry::FieldType::Performance,
- "Shutdown_EmulationSpeed",
+ constexpr auto performance = Common::Telemetry::FieldType::Performance;
+
+ telemetry_session->AddField(performance, "Shutdown_EmulationSpeed",
perf_results.emulation_speed * 100.0);
- telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Framerate",
- perf_results.game_fps);
- telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime",
+ telemetry_session->AddField(performance, "Shutdown_Framerate", perf_results.game_fps);
+ telemetry_session->AddField(performance, "Shutdown_Frametime",
perf_results.frametime * 1000.0);
- telemetry_session->AddField(Telemetry::FieldType::Performance, "Mean_Frametime_MS",
+ telemetry_session->AddField(performance, "Mean_Frametime_MS",
perf_stats->GetMeanFrametime());
}
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index b5feb3f24..e6c8461a5 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -7,14 +7,14 @@
#include <string>
#include <tuple>
-#include "common/assert.h"
#include "common/microprofile.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
+#include "core/hardware_properties.h"
namespace Core::Timing {
-constexpr u64 MAX_SLICE_LENGTH = 4000;
+constexpr s64 MAX_SLICE_LENGTH = 4000;
std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callback) {
return std::make_shared<EventType>(std::move(callback), std::move(name));
@@ -23,7 +23,7 @@ std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callbac
struct CoreTiming::Event {
u64 time;
u64 fifo_order;
- u64 userdata;
+ std::uintptr_t user_data;
std::weak_ptr<EventType> type;
// Sort by time, unless the times are the same, in which case sort by
@@ -37,10 +37,8 @@ struct CoreTiming::Event {
}
};
-CoreTiming::CoreTiming() {
- clock =
- Common::CreateBestMatchingClock(Core::Hardware::BASE_CLOCK_RATE, Core::Hardware::CNTFREQ);
-}
+CoreTiming::CoreTiming()
+ : clock{Common::CreateBestMatchingClock(Hardware::BASE_CLOCK_RATE, Hardware::CNTFREQ)} {}
CoreTiming::~CoreTiming() = default;
@@ -58,7 +56,7 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
event_fifo_id = 0;
shutting_down = false;
ticks = 0;
- const auto empty_timed_callback = [](u64, std::chrono::nanoseconds) {};
+ const auto empty_timed_callback = [](std::uintptr_t, std::chrono::nanoseconds) {};
ev_lost = CreateEvent("_lost_event", empty_timed_callback);
if (is_multicore) {
timer_thread = std::make_unique<std::thread>(ThreadEntry, std::ref(*this));
@@ -107,22 +105,24 @@ bool CoreTiming::HasPendingEvents() const {
}
void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
- const std::shared_ptr<EventType>& event_type, u64 userdata) {
+ const std::shared_ptr<EventType>& event_type,
+ std::uintptr_t user_data) {
{
std::scoped_lock scope{basic_lock};
const u64 timeout = static_cast<u64>((GetGlobalTimeNs() + ns_into_future).count());
- event_queue.emplace_back(Event{timeout, event_fifo_id++, userdata, event_type});
+ event_queue.emplace_back(Event{timeout, event_fifo_id++, user_data, event_type});
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
}
event.Set();
}
-void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u64 userdata) {
+void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
+ std::uintptr_t user_data) {
std::scoped_lock scope{basic_lock};
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
- return e.type.lock().get() == event_type.get() && e.userdata == userdata;
+ return e.type.lock().get() == event_type.get() && e.user_data == user_data;
});
// Removing random items breaks the invariant so we have to re-establish it.
@@ -134,7 +134,7 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u
void CoreTiming::AddTicks(u64 ticks) {
this->ticks += ticks;
- downcount -= ticks;
+ downcount -= static_cast<s64>(ticks);
}
void CoreTiming::Idle() {
@@ -197,7 +197,7 @@ std::optional<s64> CoreTiming::Advance() {
if (const auto event_type{evt.type.lock()}) {
event_type->callback(
- evt.userdata, std::chrono::nanoseconds{static_cast<s64>(global_timer - evt.time)});
+ evt.user_data, std::chrono::nanoseconds{static_cast<s64>(global_timer - evt.time)});
}
basic_lock.lock();
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index 120c74e46..b0b6036e4 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -22,7 +22,8 @@
namespace Core::Timing {
/// A callback that may be scheduled for a particular core timing event.
-using TimedCallback = std::function<void(u64 userdata, std::chrono::nanoseconds ns_late)>;
+using TimedCallback =
+ std::function<void(std::uintptr_t user_data, std::chrono::nanoseconds ns_late)>;
/// Contains the characteristics of a particular event.
struct EventType {
@@ -94,9 +95,9 @@ public:
/// Schedules an event in core timing
void ScheduleEvent(std::chrono::nanoseconds ns_into_future,
- const std::shared_ptr<EventType>& event_type, u64 userdata = 0);
+ const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data = 0);
- void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u64 userdata);
+ void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data);
/// We only permit one event of each type in the queue at a time.
void RemoveEvent(const std::shared_ptr<EventType>& event_type);
diff --git a/src/core/core_timing_util.cpp b/src/core/core_timing_util.cpp
index aefc63663..8ce8e602e 100644
--- a/src/core/core_timing_util.cpp
+++ b/src/core/core_timing_util.cpp
@@ -8,6 +8,7 @@
#include <limits>
#include "common/logging/log.h"
#include "common/uint128.h"
+#include "core/hardware_properties.h"
namespace Core::Timing {
diff --git a/src/core/core_timing_util.h b/src/core/core_timing_util.h
index 2ed979e14..e4a046bf9 100644
--- a/src/core/core_timing_util.h
+++ b/src/core/core_timing_util.h
@@ -6,7 +6,6 @@
#include <chrono>
#include "common/common_types.h"
-#include "core/hardware_properties.h"
namespace Core::Timing {
diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp
index 32afcf3ae..688b99eba 100644
--- a/src/core/cpu_manager.cpp
+++ b/src/core/cpu_manager.cpp
@@ -41,9 +41,9 @@ void CpuManager::Shutdown() {
running_mode = false;
Pause(false);
if (is_multicore) {
- for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- core_data[core].host_thread->join();
- core_data[core].host_thread.reset();
+ for (auto& data : core_data) {
+ data.host_thread->join();
+ data.host_thread.reset();
}
} else {
core_data[0].host_thread->join();
@@ -52,15 +52,15 @@ void CpuManager::Shutdown() {
}
std::function<void(void*)> CpuManager::GetGuestThreadStartFunc() {
- return std::function<void(void*)>(GuestThreadFunction);
+ return GuestThreadFunction;
}
std::function<void(void*)> CpuManager::GetIdleThreadStartFunc() {
- return std::function<void(void*)>(IdleThreadFunction);
+ return IdleThreadFunction;
}
std::function<void(void*)> CpuManager::GetSuspendThreadStartFunc() {
- return std::function<void(void*)>(SuspendThreadFunction);
+ return SuspendThreadFunction;
}
void CpuManager::GuestThreadFunction(void* cpu_manager_) {
@@ -166,25 +166,23 @@ void CpuManager::MultiCorePause(bool paused) {
bool all_not_barrier = false;
while (!all_not_barrier) {
all_not_barrier = true;
- for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- all_not_barrier &=
- !core_data[core].is_running.load() && core_data[core].initialized.load();
+ for (const auto& data : core_data) {
+ all_not_barrier &= !data.is_running.load() && data.initialized.load();
}
}
- for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- core_data[core].enter_barrier->Set();
+ for (auto& data : core_data) {
+ data.enter_barrier->Set();
}
if (paused_state.load()) {
bool all_barrier = false;
while (!all_barrier) {
all_barrier = true;
- for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- all_barrier &=
- core_data[core].is_paused.load() && core_data[core].initialized.load();
+ for (const auto& data : core_data) {
+ all_barrier &= data.is_paused.load() && data.initialized.load();
}
}
- for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- core_data[core].exit_barrier->Set();
+ for (auto& data : core_data) {
+ data.exit_barrier->Set();
}
}
} else {
@@ -192,9 +190,8 @@ void CpuManager::MultiCorePause(bool paused) {
bool all_barrier = false;
while (!all_barrier) {
all_barrier = true;
- for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- all_barrier &=
- core_data[core].is_paused.load() && core_data[core].initialized.load();
+ for (const auto& data : core_data) {
+ all_barrier &= data.is_paused.load() && data.initialized.load();
}
}
/// Don't release the barrier
@@ -331,7 +328,7 @@ void CpuManager::RunThread(std::size_t core) {
system.RegisterCoreThread(core);
std::string name;
if (is_multicore) {
- name = "yuzu:CoreCPUThread_" + std::to_string(core);
+ name = "yuzu:CPUCore_" + std::to_string(core);
} else {
name = "yuzu:CPUThread";
}
diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp
index 4be76bb43..6a9734812 100644
--- a/src/core/crypto/aes_util.cpp
+++ b/src/core/crypto/aes_util.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
#include <mbedtls/cipher.h>
#include "common/assert.h"
#include "common/logging/log.h"
@@ -10,8 +11,10 @@
namespace Core::Crypto {
namespace {
-std::vector<u8> CalculateNintendoTweak(std::size_t sector_id) {
- std::vector<u8> out(0x10);
+using NintendoTweak = std::array<u8, 16>;
+
+NintendoTweak CalculateNintendoTweak(std::size_t sector_id) {
+ NintendoTweak out{};
for (std::size_t i = 0xF; i <= 0xF; --i) {
out[i] = sector_id & 0xFF;
sector_id >>= 8;
@@ -64,13 +67,6 @@ AESCipher<Key, KeySize>::~AESCipher() {
}
template <typename Key, std::size_t KeySize>
-void AESCipher<Key, KeySize>::SetIV(std::vector<u8> iv) {
- ASSERT_MSG((mbedtls_cipher_set_iv(&ctx->encryption_context, iv.data(), iv.size()) ||
- mbedtls_cipher_set_iv(&ctx->decryption_context, iv.data(), iv.size())) == 0,
- "Failed to set IV on mbedtls ciphers.");
-}
-
-template <typename Key, std::size_t KeySize>
void AESCipher<Key, KeySize>::Transcode(const u8* src, std::size_t size, u8* dest, Op op) const {
auto* const context = op == Op::Encrypt ? &ctx->encryption_context : &ctx->decryption_context;
@@ -120,10 +116,17 @@ void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, std::size_t size, u8*
for (std::size_t i = 0; i < size; i += sector_size) {
SetIV(CalculateNintendoTweak(sector_id++));
- Transcode<u8, u8>(src + i, sector_size, dest + i, op);
+ Transcode(src + i, sector_size, dest + i, op);
}
}
+template <typename Key, std::size_t KeySize>
+void AESCipher<Key, KeySize>::SetIVImpl(const u8* data, std::size_t size) {
+ ASSERT_MSG((mbedtls_cipher_set_iv(&ctx->encryption_context, data, size) ||
+ mbedtls_cipher_set_iv(&ctx->decryption_context, data, size)) == 0,
+ "Failed to set IV on mbedtls ciphers.");
+}
+
template class AESCipher<Key128>;
template class AESCipher<Key256>;
} // namespace Core::Crypto
diff --git a/src/core/crypto/aes_util.h b/src/core/crypto/aes_util.h
index edc4ab910..e2a304186 100644
--- a/src/core/crypto/aes_util.h
+++ b/src/core/crypto/aes_util.h
@@ -6,7 +6,6 @@
#include <memory>
#include <type_traits>
-#include <vector>
#include "common/common_types.h"
#include "core/file_sys/vfs.h"
@@ -32,10 +31,12 @@ class AESCipher {
public:
AESCipher(Key key, Mode mode);
-
~AESCipher();
- void SetIV(std::vector<u8> iv);
+ template <typename ContiguousContainer>
+ void SetIV(const ContiguousContainer& container) {
+ SetIVImpl(std::data(container), std::size(container));
+ }
template <typename Source, typename Dest>
void Transcode(const Source* src, std::size_t size, Dest* dest, Op op) const {
@@ -59,6 +60,8 @@ public:
std::size_t sector_size, Op op);
private:
+ void SetIVImpl(const u8* data, std::size_t size);
+
std::unique_ptr<CipherContext> ctx;
};
} // namespace Core::Crypto
diff --git a/src/core/crypto/ctr_encryption_layer.cpp b/src/core/crypto/ctr_encryption_layer.cpp
index 902841c77..5c84bb0a4 100644
--- a/src/core/crypto/ctr_encryption_layer.cpp
+++ b/src/core/crypto/ctr_encryption_layer.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <cstring>
#include "common/assert.h"
#include "core/crypto/ctr_encryption_layer.h"
@@ -10,8 +11,7 @@ namespace Core::Crypto {
CTREncryptionLayer::CTREncryptionLayer(FileSys::VirtualFile base_, Key128 key_,
std::size_t base_offset)
- : EncryptionLayer(std::move(base_)), base_offset(base_offset), cipher(key_, Mode::CTR),
- iv(16, 0) {}
+ : EncryptionLayer(std::move(base_)), base_offset(base_offset), cipher(key_, Mode::CTR) {}
std::size_t CTREncryptionLayer::Read(u8* data, std::size_t length, std::size_t offset) const {
if (length == 0)
@@ -39,9 +39,8 @@ std::size_t CTREncryptionLayer::Read(u8* data, std::size_t length, std::size_t o
return read + Read(data + read, length - read, offset + read);
}
-void CTREncryptionLayer::SetIV(const std::vector<u8>& iv_) {
- const auto length = std::min(iv_.size(), iv.size());
- iv.assign(iv_.cbegin(), iv_.cbegin() + length);
+void CTREncryptionLayer::SetIV(const IVData& iv_) {
+ iv = iv_;
}
void CTREncryptionLayer::UpdateIV(std::size_t offset) const {
diff --git a/src/core/crypto/ctr_encryption_layer.h b/src/core/crypto/ctr_encryption_layer.h
index a7bf810f4..a2429f001 100644
--- a/src/core/crypto/ctr_encryption_layer.h
+++ b/src/core/crypto/ctr_encryption_layer.h
@@ -4,7 +4,8 @@
#pragma once
-#include <vector>
+#include <array>
+
#include "core/crypto/aes_util.h"
#include "core/crypto/encryption_layer.h"
#include "core/crypto/key_manager.h"
@@ -14,18 +15,20 @@ namespace Core::Crypto {
// Sits on top of a VirtualFile and provides CTR-mode AES decription.
class CTREncryptionLayer : public EncryptionLayer {
public:
+ using IVData = std::array<u8, 16>;
+
CTREncryptionLayer(FileSys::VirtualFile base, Key128 key, std::size_t base_offset);
std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
- void SetIV(const std::vector<u8>& iv);
+ void SetIV(const IVData& iv);
private:
std::size_t base_offset;
// Must be mutable as operations modify cipher contexts.
mutable AESCipher<Key128> cipher;
- mutable std::vector<u8> iv;
+ mutable IVData iv{};
void UpdateIV(std::size_t offset) const;
};
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index f87fe0abc..dc591c730 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -36,18 +36,86 @@
#include "core/settings.h"
namespace Core::Crypto {
+namespace {
constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
constexpr u64 FULL_TICKET_SIZE = 0x400;
-using namespace Common;
+using Common::AsArray;
-const std::array<SHA256Hash, 2> eticket_source_hashes{
- "B71DB271DC338DF380AA2C4335EF8873B1AFD408E80B3582D8719FC81C5E511C"_array32, // eticket_rsa_kek_source
- "E8965A187D30E57869F562D04383C996DE487BBA5761363D2D4D32391866A85C"_array32, // eticket_rsa_kekek_source
+// clang-format off
+constexpr std::array eticket_source_hashes{
+ AsArray("B71DB271DC338DF380AA2C4335EF8873B1AFD408E80B3582D8719FC81C5E511C"), // eticket_rsa_kek_source
+ AsArray("E8965A187D30E57869F562D04383C996DE487BBA5761363D2D4D32391866A85C"), // eticket_rsa_kekek_source
};
+// clang-format on
-const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{
+constexpr std::array<std::pair<std::string_view, KeyIndex<S128KeyType>>, 30> s128_file_id{{
+ {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}},
+ {"eticket_rsa_kek_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKek), 0}},
+ {"eticket_rsa_kekek_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKekek), 0}},
+ {"rsa_kek_mask_0", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Mask0), 0}},
+ {"rsa_kek_seed_3", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Seed3), 0}},
+ {"rsa_oaep_kek_generation_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::RSAOaepKekGeneration), 0}},
+ {"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek), 0}},
+ {"aes_kek_generation_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration), 0}},
+ {"aes_key_generation_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration), 0}},
+ {"package2_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Package2), 0}},
+ {"master_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Master), 0}},
+ {"header_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek), 0}},
+ {"key_area_key_application_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
+ static_cast<u64>(KeyAreaKeyType::Application)}},
+ {"key_area_key_ocean_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
+ static_cast<u64>(KeyAreaKeyType::Ocean)}},
+ {"key_area_key_system_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
+ static_cast<u64>(KeyAreaKeyType::System)}},
+ {"titlekek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Titlekek), 0}},
+ {"keyblob_mac_key_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC), 0}},
+ {"tsec_key", {S128KeyType::TSEC, 0, 0}},
+ {"secure_boot_key", {S128KeyType::SecureBoot, 0, 0}},
+ {"sd_seed", {S128KeyType::SDSeed, 0, 0}},
+ {"bis_key_0_crypt", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Crypto)}},
+ {"bis_key_0_tweak", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Tweak)}},
+ {"bis_key_1_crypt", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Crypto)}},
+ {"bis_key_1_tweak", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Tweak)}},
+ {"bis_key_2_crypt", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Crypto)}},
+ {"bis_key_2_tweak", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Tweak)}},
+ {"bis_key_3_crypt", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Crypto)}},
+ {"bis_key_3_tweak", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Tweak)}},
+ {"header_kek", {S128KeyType::HeaderKek, 0, 0}},
+ {"sd_card_kek", {S128KeyType::SDKek, 0, 0}},
+}};
+
+auto Find128ByName(std::string_view name) {
+ return std::find_if(s128_file_id.begin(), s128_file_id.end(),
+ [&name](const auto& pair) { return pair.first == name; });
+}
+
+constexpr std::array<std::pair<std::string_view, KeyIndex<S256KeyType>>, 6> s256_file_id{{
+ {"header_key", {S256KeyType::Header, 0, 0}},
+ {"sd_card_save_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save), 0}},
+ {"sd_card_nca_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA), 0}},
+ {"header_key_source", {S256KeyType::HeaderSource, 0, 0}},
+ {"sd_card_save_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::Save), 0}},
+ {"sd_card_nca_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::NCA), 0}},
+}};
+
+auto Find256ByName(std::string_view name) {
+ return std::find_if(s256_file_id.begin(), s256_file_id.end(),
+ [&name](const auto& pair) { return pair.first == name; });
+}
+
+using KeyArray = std::array<std::pair<std::pair<S128KeyType, u64>, std::string_view>, 7>;
+constexpr KeyArray KEYS_VARIABLE_LENGTH{{
{{S128KeyType::Master, 0}, "master_key_"},
{{S128KeyType::Package1, 0}, "package1_key_"},
{{S128KeyType::Package2, 0}, "package2_key_"},
@@ -55,14 +123,13 @@ const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{
{{S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob)}, "keyblob_key_source_"},
{{S128KeyType::Keyblob, 0}, "keyblob_key_"},
{{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"},
-};
+}};
-namespace {
template <std::size_t Size>
bool IsAllZeroArray(const std::array<u8, Size>& array) {
return std::all_of(array.begin(), array.end(), [](const auto& elem) { return elem == 0; });
}
-} // namespace
+} // Anonymous namespace
u64 GetSignatureTypeDataSize(SignatureType type) {
switch (type) {
@@ -94,13 +161,13 @@ u64 GetSignatureTypePaddingSize(SignatureType type) {
}
SignatureType Ticket::GetSignatureType() const {
- if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
+ if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) {
return ticket->sig_type;
}
- if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
+ if (const auto* ticket = std::get_if<RSA2048Ticket>(&data)) {
return ticket->sig_type;
}
- if (auto ticket = std::get_if<ECDSATicket>(&data)) {
+ if (const auto* ticket = std::get_if<ECDSATicket>(&data)) {
return ticket->sig_type;
}
@@ -108,13 +175,13 @@ SignatureType Ticket::GetSignatureType() const {
}
TicketData& Ticket::GetData() {
- if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
+ if (auto* ticket = std::get_if<RSA4096Ticket>(&data)) {
return ticket->data;
}
- if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
+ if (auto* ticket = std::get_if<RSA2048Ticket>(&data)) {
return ticket->data;
}
- if (auto ticket = std::get_if<ECDSATicket>(&data)) {
+ if (auto* ticket = std::get_if<ECDSATicket>(&data)) {
return ticket->data;
}
@@ -122,13 +189,13 @@ TicketData& Ticket::GetData() {
}
const TicketData& Ticket::GetData() const {
- if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
+ if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) {
return ticket->data;
}
- if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
+ if (const auto* ticket = std::get_if<RSA2048Ticket>(&data)) {
return ticket->data;
}
- if (auto ticket = std::get_if<ECDSATicket>(&data)) {
+ if (const auto* ticket = std::get_if<ECDSATicket>(&data)) {
return ticket->data;
}
@@ -231,8 +298,9 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) {
}
RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const {
- if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek))
+ if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) {
return {};
+ }
const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek);
@@ -259,27 +327,30 @@ Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source)
}
std::optional<Key128> DeriveSDSeed() {
- const FileUtil::IOFile save_43(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
- "/system/save/8000000000000043",
- "rb+");
- if (!save_43.IsOpen())
- return {};
+ const Common::FS::IOFile save_43(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
+ "/system/save/8000000000000043",
+ "rb+");
+ if (!save_43.IsOpen()) {
+ return std::nullopt;
+ }
- const FileUtil::IOFile sd_private(
- FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "/Nintendo/Contents/private", "rb+");
- if (!sd_private.IsOpen())
- return {};
+ const Common::FS::IOFile sd_private(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir) +
+ "/Nintendo/Contents/private",
+ "rb+");
+ if (!sd_private.IsOpen()) {
+ return std::nullopt;
+ }
std::array<u8, 0x10> private_seed{};
if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != private_seed.size()) {
- return {};
+ return std::nullopt;
}
std::array<u8, 0x10> buffer{};
std::size_t offset = 0;
for (; offset + 0x10 < save_43.GetSize(); ++offset) {
if (!save_43.Seek(offset, SEEK_SET)) {
- return {};
+ return std::nullopt;
}
save_43.ReadBytes(buffer.data(), buffer.size());
@@ -289,23 +360,26 @@ std::optional<Key128> DeriveSDSeed() {
}
if (!save_43.Seek(offset + 0x10, SEEK_SET)) {
- return {};
+ return std::nullopt;
}
Key128 seed{};
if (save_43.ReadBytes(seed.data(), seed.size()) != seed.size()) {
- return {};
+ return std::nullopt;
}
return seed;
}
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys) {
- if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek)))
+ if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek))) {
return Loader::ResultStatus::ErrorMissingSDKEKSource;
- if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)))
+ }
+ if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration))) {
return Loader::ResultStatus::ErrorMissingAESKEKGenerationSource;
- if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)))
+ }
+ if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration))) {
return Loader::ResultStatus::ErrorMissingAESKeyGenerationSource;
+ }
const auto sd_kek_source =
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek));
@@ -318,14 +392,17 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
GenerateKeyEncryptionKey(sd_kek_source, master_00, aes_kek_gen, aes_key_gen);
keys.SetKey(S128KeyType::SDKek, sd_kek);
- if (!keys.HasKey(S128KeyType::SDSeed))
+ if (!keys.HasKey(S128KeyType::SDSeed)) {
return Loader::ResultStatus::ErrorMissingSDSeed;
+ }
const auto sd_seed = keys.GetKey(S128KeyType::SDSeed);
- if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save)))
+ if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save))) {
return Loader::ResultStatus::ErrorMissingSDSaveKeySource;
- if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA)))
+ }
+ if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA))) {
return Loader::ResultStatus::ErrorMissingSDNCAKeySource;
+ }
std::array<Key256, 2> sd_key_sources{
keys.GetKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save)),
@@ -334,8 +411,9 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
// Combine sources and seed
for (auto& source : sd_key_sources) {
- for (std::size_t i = 0; i < source.size(); ++i)
+ for (std::size_t i = 0; i < source.size(); ++i) {
source[i] ^= sd_seed[i & 0xF];
+ }
}
AESCipher<Key128> cipher(sd_kek, Mode::ECB);
@@ -353,9 +431,10 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
return Loader::ResultStatus::Success;
}
-std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save) {
- if (!ticket_save.IsOpen())
+std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save) {
+ if (!ticket_save.IsOpen()) {
return {};
+ }
std::vector<u8> buffer(ticket_save.GetSize());
if (ticket_save.ReadBytes(buffer.data(), buffer.size()) != buffer.size()) {
@@ -415,7 +494,7 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
offset = i + 1;
break;
} else if (data[i] != 0x0) {
- return {};
+ return std::nullopt;
}
}
@@ -425,16 +504,18 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
const RSAKeyPair<2048>& key) {
const auto issuer = ticket.GetData().issuer;
- if (IsAllZeroArray(issuer))
- return {};
+ if (IsAllZeroArray(issuer)) {
+ return std::nullopt;
+ }
if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') {
LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority.");
}
Key128 rights_id = ticket.GetData().rights_id;
- if (rights_id == Key128{})
- return {};
+ if (rights_id == Key128{}) {
+ return std::nullopt;
+ }
if (!std::any_of(ticket.GetData().title_key_common_pad.begin(),
ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) {
@@ -466,15 +547,17 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
std::array<u8, 0xDF> m_2;
std::memcpy(m_2.data(), rsa_step.data() + 0x21, m_2.size());
- if (m_0 != 0)
- return {};
+ if (m_0 != 0) {
+ return std::nullopt;
+ }
m_1 = m_1 ^ MGF1<0x20>(m_2);
m_2 = m_2 ^ MGF1<0xDF>(m_1);
const auto offset = FindTicketOffset(m_2);
- if (!offset)
- return {};
+ if (!offset) {
+ return std::nullopt;
+ }
ASSERT(*offset > 0);
Key128 key_temp{};
@@ -485,8 +568,8 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
KeyManager::KeyManager() {
// Initialize keys
- const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
- const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);
+ const std::string hactool_keys_dir = Common::FS::GetHactoolConfigurationPath();
+ const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir);
if (Settings::values.use_dev_keys) {
dev_mode = true;
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false);
@@ -504,34 +587,39 @@ KeyManager::KeyManager() {
}
static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) {
- if (base.size() < begin + length)
+ if (base.size() < begin + length) {
return false;
+ }
return std::all_of(base.begin() + begin, base.begin() + begin + length,
[](u8 c) { return std::isxdigit(c); });
}
void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
std::ifstream file;
- OpenFStream(file, filename, std::ios_base::in);
- if (!file.is_open())
+ Common::FS::OpenFStream(file, filename, std::ios_base::in);
+ if (!file.is_open()) {
return;
+ }
std::string line;
while (std::getline(file, line)) {
std::vector<std::string> out;
std::stringstream stream(line);
std::string item;
- while (std::getline(stream, item, '='))
+ while (std::getline(stream, item, '=')) {
out.push_back(std::move(item));
+ }
- if (out.size() != 2)
+ if (out.size() != 2) {
continue;
+ }
out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end());
out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end());
- if (out[0].compare(0, 1, "#") == 0)
+ if (out[0].compare(0, 1, "#") == 0) {
continue;
+ }
if (is_title_keys) {
auto rights_id_raw = Common::HexStringToArray<16>(out[0]);
@@ -541,24 +629,26 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
s128_keys[{S128KeyType::Titlekey, rights_id[1], rights_id[0]}] = key;
} else {
out[0] = Common::ToLower(out[0]);
- if (s128_file_id.find(out[0]) != s128_file_id.end()) {
- const auto index = s128_file_id.at(out[0]);
- Key128 key = Common::HexStringToArray<16>(out[1]);
+ if (const auto iter128 = Find128ByName(out[0]); iter128 != s128_file_id.end()) {
+ const auto& index = iter128->second;
+ const Key128 key = Common::HexStringToArray<16>(out[1]);
s128_keys[{index.type, index.field1, index.field2}] = key;
- } else if (s256_file_id.find(out[0]) != s256_file_id.end()) {
- const auto index = s256_file_id.at(out[0]);
- Key256 key = Common::HexStringToArray<32>(out[1]);
+ } else if (const auto iter256 = Find256ByName(out[0]); iter256 != s256_file_id.end()) {
+ const auto& index = iter256->second;
+ const Key256 key = Common::HexStringToArray<32>(out[1]);
s256_keys[{index.type, index.field1, index.field2}] = key;
} else if (out[0].compare(0, 8, "keyblob_") == 0 &&
out[0].compare(0, 9, "keyblob_k") != 0) {
- if (!ValidCryptoRevisionString(out[0], 8, 2))
+ if (!ValidCryptoRevisionString(out[0], 8, 2)) {
continue;
+ }
const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16);
keyblobs[index] = Common::HexStringToArray<0x90>(out[1]);
} else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) {
- if (!ValidCryptoRevisionString(out[0], 18, 2))
+ if (!ValidCryptoRevisionString(out[0], 18, 2)) {
continue;
+ }
const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16);
encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
@@ -566,8 +656,9 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
eticket_extended_kek = Common::HexStringToArray<576>(out[1]);
} else {
for (const auto& kv : KEYS_VARIABLE_LENGTH) {
- if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2))
+ if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) {
continue;
+ }
if (out[0].compare(0, kv.second.size(), kv.second) == 0) {
const auto index =
std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16);
@@ -602,10 +693,11 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
void KeyManager::AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2,
const std::string& filename, bool title) {
- if (FileUtil::Exists(dir1 + DIR_SEP + filename))
+ if (Common::FS::Exists(dir1 + DIR_SEP + filename)) {
LoadFromFile(dir1 + DIR_SEP + filename, title);
- else if (FileUtil::Exists(dir2 + DIR_SEP + filename))
+ } else if (Common::FS::Exists(dir2 + DIR_SEP + filename)) {
LoadFromFile(dir2 + DIR_SEP + filename, title);
+ }
}
bool KeyManager::BaseDeriveNecessary() const {
@@ -613,8 +705,9 @@ bool KeyManager::BaseDeriveNecessary() const {
return !HasKey(key_type, index1, index2);
};
- if (check_key_existence(S256KeyType::Header))
+ if (check_key_existence(S256KeyType::Header)) {
return true;
+ }
for (size_t i = 0; i < CURRENT_CRYPTO_REVISION; ++i) {
if (check_key_existence(S128KeyType::Master, i) ||
@@ -639,14 +732,16 @@ bool KeyManager::HasKey(S256KeyType id, u64 field1, u64 field2) const {
}
Key128 KeyManager::GetKey(S128KeyType id, u64 field1, u64 field2) const {
- if (!HasKey(id, field1, field2))
+ if (!HasKey(id, field1, field2)) {
return {};
+ }
return s128_keys.at({id, field1, field2});
}
Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const {
- if (!HasKey(id, field1, field2))
+ if (!HasKey(id, field1, field2)) {
return {};
+ }
return s256_keys.at({id, field1, field2});
}
@@ -668,7 +763,7 @@ Key256 KeyManager::GetBISKey(u8 partition_id) const {
template <size_t Size>
void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
const std::array<u8, Size>& key) {
- const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);
+ const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir);
std::string filename = "title.keys_autogenerated";
if (category == KeyCategory::Standard) {
filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated";
@@ -677,9 +772,9 @@ void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
}
const auto path = yuzu_keys_dir + DIR_SEP + filename;
- const auto add_info_text = !FileUtil::Exists(path);
- FileUtil::CreateFullPath(path);
- FileUtil::IOFile file{path, "a"};
+ const auto add_info_text = !Common::FS::Exists(path);
+ Common::FS::CreateFullPath(path);
+ Common::FS::IOFile file{path, "a"};
if (!file.IsOpen()) {
return;
}
@@ -712,8 +807,7 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
}
const auto iter2 = std::find_if(
- s128_file_id.begin(), s128_file_id.end(),
- [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) {
+ s128_file_id.begin(), s128_file_id.end(), [&id, &field1, &field2](const auto& elem) {
return std::tie(elem.second.type, elem.second.field1, elem.second.field2) ==
std::tie(id, field1, field2);
});
@@ -723,9 +817,11 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
// Variable cases
if (id == S128KeyType::KeyArea) {
- static constexpr std::array<const char*, 3> kak_names = {"key_area_key_application_{:02X}",
- "key_area_key_ocean_{:02X}",
- "key_area_key_system_{:02X}"};
+ static constexpr std::array<const char*, 3> kak_names = {
+ "key_area_key_application_{:02X}",
+ "key_area_key_ocean_{:02X}",
+ "key_area_key_system_{:02X}",
+ };
WriteKeyToFile(category, fmt::format(kak_names.at(field2), field1), key);
} else if (id == S128KeyType::Master) {
WriteKeyToFile(category, fmt::format("master_key_{:02X}", field1), key);
@@ -751,8 +847,7 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
return;
}
const auto iter = std::find_if(
- s256_file_id.begin(), s256_file_id.end(),
- [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S256KeyType>> elem) {
+ s256_file_id.begin(), s256_file_id.end(), [&id, &field1, &field2](const auto& elem) {
return std::tie(elem.second.type, elem.second.field1, elem.second.field2) ==
std::tie(id, field1, field2);
});
@@ -763,29 +858,31 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
}
bool KeyManager::KeyFileExists(bool title) {
- const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
- const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);
+ const std::string hactool_keys_dir = Common::FS::GetHactoolConfigurationPath();
+ const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir);
if (title) {
- return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "title.keys") ||
- FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "title.keys");
+ return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "title.keys") ||
+ Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "title.keys");
}
if (Settings::values.use_dev_keys) {
- return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "dev.keys") ||
- FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "dev.keys");
+ return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "dev.keys") ||
+ Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "dev.keys");
}
- return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "prod.keys") ||
- FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys");
+ return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "prod.keys") ||
+ Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys");
}
void KeyManager::DeriveSDSeedLazy() {
- if (HasKey(S128KeyType::SDSeed))
+ if (HasKey(S128KeyType::SDSeed)) {
return;
+ }
const auto res = DeriveSDSeed();
- if (res)
+ if (res) {
SetKey(S128KeyType::SDSeed, *res);
+ }
}
static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) {
@@ -797,11 +894,13 @@ static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) {
}
void KeyManager::DeriveBase() {
- if (!BaseDeriveNecessary())
+ if (!BaseDeriveNecessary()) {
return;
+ }
- if (!HasKey(S128KeyType::SecureBoot) || !HasKey(S128KeyType::TSEC))
+ if (!HasKey(S128KeyType::SecureBoot) || !HasKey(S128KeyType::TSEC)) {
return;
+ }
const auto has_bis = [this](u64 id) {
return HasKey(S128KeyType::BIS, id, static_cast<u64>(BISKeyType::Crypto)) &&
@@ -818,10 +917,11 @@ void KeyManager::DeriveBase() {
static_cast<u64>(BISKeyType::Tweak));
};
- if (has_bis(2) && !has_bis(3))
+ if (has_bis(2) && !has_bis(3)) {
copy_bis(2, 3);
- else if (has_bis(3) && !has_bis(2))
+ } else if (has_bis(3) && !has_bis(2)) {
copy_bis(3, 2);
+ }
std::bitset<32> revisions(0xFFFFFFFF);
for (size_t i = 0; i < revisions.size(); ++i) {
@@ -831,15 +931,17 @@ void KeyManager::DeriveBase() {
}
}
- if (!revisions.any())
+ if (!revisions.any()) {
return;
+ }
const auto sbk = GetKey(S128KeyType::SecureBoot);
const auto tsec = GetKey(S128KeyType::TSEC);
for (size_t i = 0; i < revisions.size(); ++i) {
- if (!revisions[i])
+ if (!revisions[i]) {
continue;
+ }
// Derive keyblob key
const auto key = DeriveKeyblobKey(
@@ -848,16 +950,18 @@ void KeyManager::DeriveBase() {
SetKey(S128KeyType::Keyblob, key, i);
// Derive keyblob MAC key
- if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)))
+ if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC))) {
continue;
+ }
const auto mac_key = DeriveKeyblobMACKey(
key, GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)));
SetKey(S128KeyType::KeyblobMAC, mac_key, i);
Key128 cmac = CalculateCMAC(encrypted_keyblobs[i].data() + 0x10, 0xA0, mac_key);
- if (std::memcmp(cmac.data(), encrypted_keyblobs[i].data(), cmac.size()) != 0)
+ if (std::memcmp(cmac.data(), encrypted_keyblobs[i].data(), cmac.size()) != 0) {
continue;
+ }
// Decrypt keyblob
if (keyblobs[i] == std::array<u8, 0x90>{}) {
@@ -881,16 +985,19 @@ void KeyManager::DeriveBase() {
revisions.set();
for (size_t i = 0; i < revisions.size(); ++i) {
- if (!HasKey(S128KeyType::Master, i))
+ if (!HasKey(S128KeyType::Master, i)) {
revisions.reset(i);
+ }
}
- if (!revisions.any())
+ if (!revisions.any()) {
return;
+ }
for (size_t i = 0; i < revisions.size(); ++i) {
- if (!revisions[i])
+ if (!revisions[i]) {
continue;
+ }
// Derive general purpose keys
DeriveGeneralPurposeKeys(i);
@@ -920,16 +1027,19 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
const auto es = Core::System::GetInstance().GetContentProvider().GetEntry(
0x0100000000000033, FileSys::ContentRecordType::Program);
- if (es == nullptr)
+ if (es == nullptr) {
return;
+ }
const auto exefs = es->GetExeFS();
- if (exefs == nullptr)
+ if (exefs == nullptr) {
return;
+ }
const auto main = exefs->GetFile("main");
- if (main == nullptr)
+ if (main == nullptr) {
return;
+ }
const auto bytes = main->ReadAllBytes();
@@ -939,16 +1049,19 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
const auto seed3 = data.GetRSAKekSeed3();
const auto mask0 = data.GetRSAKekMask0();
- if (eticket_kek != Key128{})
+ if (eticket_kek != Key128{}) {
SetKey(S128KeyType::Source, eticket_kek, static_cast<size_t>(SourceKeyType::ETicketKek));
+ }
if (eticket_kekek != Key128{}) {
SetKey(S128KeyType::Source, eticket_kekek,
static_cast<size_t>(SourceKeyType::ETicketKekek));
}
- if (seed3 != Key128{})
+ if (seed3 != Key128{}) {
SetKey(S128KeyType::RSAKek, seed3, static_cast<size_t>(RSAKekType::Seed3));
- if (mask0 != Key128{})
+ }
+ if (mask0 != Key128{}) {
SetKey(S128KeyType::RSAKek, mask0, static_cast<size_t>(RSAKekType::Mask0));
+ }
if (eticket_kek == Key128{} || eticket_kekek == Key128{} || seed3 == Key128{} ||
mask0 == Key128{}) {
return;
@@ -974,8 +1087,9 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
AESCipher<Key128> es_kek(temp_kekek, Mode::ECB);
es_kek.Transcode(eticket_kek.data(), eticket_kek.size(), eticket_final.data(), Op::Decrypt);
- if (eticket_final == Key128{})
+ if (eticket_final == Key128{}) {
return;
+ }
SetKey(S128KeyType::ETicketRSAKek, eticket_final);
@@ -990,18 +1104,20 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
void KeyManager::PopulateTickets() {
const auto rsa_key = GetETicketRSAKey();
- if (rsa_key == RSAKeyPair<2048>{})
+ if (rsa_key == RSAKeyPair<2048>{}) {
return;
+ }
- if (!common_tickets.empty() && !personal_tickets.empty())
+ if (!common_tickets.empty() && !personal_tickets.empty()) {
return;
+ }
- const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
- "/system/save/80000000000000e1",
- "rb+");
- const FileUtil::IOFile save2(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
- "/system/save/80000000000000e2",
- "rb+");
+ const Common::FS::IOFile save1(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
+ "/system/save/80000000000000e1",
+ "rb+");
+ const Common::FS::IOFile save2(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
+ "/system/save/80000000000000e2",
+ "rb+");
const auto blob2 = GetTicketblob(save2);
auto res = GetTicketblob(save1);
@@ -1011,8 +1127,10 @@ void KeyManager::PopulateTickets() {
for (std::size_t i = 0; i < res.size(); ++i) {
const auto common = i < idx;
const auto pair = ParseTicket(res[i], rsa_key);
- if (!pair)
+ if (!pair) {
continue;
+ }
+
const auto& [rid, key] = *pair;
u128 rights_id;
std::memcpy(rights_id.data(), rid.data(), rid.size());
@@ -1041,27 +1159,33 @@ void KeyManager::SynthesizeTickets() {
}
void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) {
- if (key == Key128{})
+ if (key == Key128{}) {
return;
+ }
SetKey(id, key, field1, field2);
}
void KeyManager::SetKeyWrapped(S256KeyType id, Key256 key, u64 field1, u64 field2) {
- if (key == Key256{})
+ if (key == Key256{}) {
return;
+ }
+
SetKey(id, key, field1, field2);
}
void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
- if (!BaseDeriveNecessary())
+ if (!BaseDeriveNecessary()) {
return;
+ }
- if (!data.HasBoot0())
+ if (!data.HasBoot0()) {
return;
+ }
for (size_t i = 0; i < encrypted_keyblobs.size(); ++i) {
- if (encrypted_keyblobs[i] != std::array<u8, 0xB0>{})
+ if (encrypted_keyblobs[i] != std::array<u8, 0xB0>{}) {
continue;
+ }
encrypted_keyblobs[i] = data.GetEncryptedKeyblob(i);
WriteKeyToFile<0xB0>(KeyCategory::Console, fmt::format("encrypted_keyblob_{:02X}", i),
encrypted_keyblobs[i]);
@@ -1083,8 +1207,9 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
static_cast<u64>(SourceKeyType::Keyblob), i);
}
- if (data.HasFuses())
+ if (data.HasFuses()) {
SetKeyWrapped(S128KeyType::SecureBoot, data.GetSecureBootKey());
+ }
DeriveBase();
@@ -1098,8 +1223,9 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
const auto masters = data.GetTZMasterKeys(latest_master);
for (size_t i = 0; i < masters.size(); ++i) {
- if (masters[i] != Key128{} && !HasKey(S128KeyType::Master, i))
+ if (masters[i] != Key128{} && !HasKey(S128KeyType::Master, i)) {
SetKey(S128KeyType::Master, masters[i], i);
+ }
}
DeriveBase();
@@ -1109,8 +1235,9 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
std::array<Key128, 0x20> package2_keys{};
for (size_t i = 0; i < package2_keys.size(); ++i) {
- if (HasKey(S128KeyType::Package2, i))
+ if (HasKey(S128KeyType::Package2, i)) {
package2_keys[i] = GetKey(S128KeyType::Package2, i);
+ }
}
data.DecryptPackage2(package2_keys, Package2Type::NormalMain);
@@ -1148,12 +1275,15 @@ const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const {
bool KeyManager::AddTicketCommon(Ticket raw) {
const auto rsa_key = GetETicketRSAKey();
- if (rsa_key == RSAKeyPair<2048>{})
+ if (rsa_key == RSAKeyPair<2048>{}) {
return false;
+ }
const auto pair = ParseTicket(raw, rsa_key);
- if (!pair)
+ if (!pair) {
return false;
+ }
+
const auto& [rid, key] = *pair;
u128 rights_id;
std::memcpy(rights_id.data(), rid.data(), rid.size());
@@ -1164,12 +1294,15 @@ bool KeyManager::AddTicketCommon(Ticket raw) {
bool KeyManager::AddTicketPersonalized(Ticket raw) {
const auto rsa_key = GetETicketRSAKey();
- if (rsa_key == RSAKeyPair<2048>{})
+ if (rsa_key == RSAKeyPair<2048>{}) {
return false;
+ }
const auto pair = ParseTicket(raw, rsa_key);
- if (!pair)
+ if (!pair) {
return false;
+ }
+
const auto& [rid, key] = *pair;
u128 rights_id;
std::memcpy(rights_id.data(), rid.data(), rid.size());
@@ -1177,58 +1310,4 @@ bool KeyManager::AddTicketPersonalized(Ticket raw) {
SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
return true;
}
-
-const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
- {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}},
- {"eticket_rsa_kek_source",
- {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKek), 0}},
- {"eticket_rsa_kekek_source",
- {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKekek), 0}},
- {"rsa_kek_mask_0", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Mask0), 0}},
- {"rsa_kek_seed_3", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Seed3), 0}},
- {"rsa_oaep_kek_generation_source",
- {S128KeyType::Source, static_cast<u64>(SourceKeyType::RSAOaepKekGeneration), 0}},
- {"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek), 0}},
- {"aes_kek_generation_source",
- {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration), 0}},
- {"aes_key_generation_source",
- {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration), 0}},
- {"package2_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Package2), 0}},
- {"master_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Master), 0}},
- {"header_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek), 0}},
- {"key_area_key_application_source",
- {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
- static_cast<u64>(KeyAreaKeyType::Application)}},
- {"key_area_key_ocean_source",
- {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
- static_cast<u64>(KeyAreaKeyType::Ocean)}},
- {"key_area_key_system_source",
- {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
- static_cast<u64>(KeyAreaKeyType::System)}},
- {"titlekek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Titlekek), 0}},
- {"keyblob_mac_key_source",
- {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC), 0}},
- {"tsec_key", {S128KeyType::TSEC, 0, 0}},
- {"secure_boot_key", {S128KeyType::SecureBoot, 0, 0}},
- {"sd_seed", {S128KeyType::SDSeed, 0, 0}},
- {"bis_key_0_crypt", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Crypto)}},
- {"bis_key_0_tweak", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Tweak)}},
- {"bis_key_1_crypt", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Crypto)}},
- {"bis_key_1_tweak", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Tweak)}},
- {"bis_key_2_crypt", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Crypto)}},
- {"bis_key_2_tweak", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Tweak)}},
- {"bis_key_3_crypt", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Crypto)}},
- {"bis_key_3_tweak", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Tweak)}},
- {"header_kek", {S128KeyType::HeaderKek, 0, 0}},
- {"sd_card_kek", {S128KeyType::SDKek, 0, 0}},
-};
-
-const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = {
- {"header_key", {S256KeyType::Header, 0, 0}},
- {"sd_card_save_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save), 0}},
- {"sd_card_nca_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA), 0}},
- {"header_key_source", {S256KeyType::HeaderSource, 0, 0}},
- {"sd_card_save_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::Save), 0}},
- {"sd_card_nca_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::NCA), 0}},
-};
} // namespace Core::Crypto
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index 9269a73f2..321b75323 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -10,14 +10,13 @@
#include <string>
#include <variant>
-#include <boost/container/flat_map.hpp>
#include <fmt/format.h>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/crypto/partition_data_manager.h"
#include "core/file_sys/vfs_types.h"
-namespace FileUtil {
+namespace Common::FS {
class IOFile;
}
@@ -293,9 +292,6 @@ private:
void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
-
- static const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> s128_file_id;
- static const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> s256_file_id;
};
Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed);
@@ -308,7 +304,7 @@ std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblo
std::optional<Key128> DeriveSDSeed();
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys);
-std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save);
+std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save);
// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority
// (offset 0x140-0x144 is zero)
diff --git a/src/core/crypto/partition_data_manager.cpp b/src/core/crypto/partition_data_manager.cpp
index 7ed71ac3a..5f1c86a09 100644
--- a/src/core/crypto/partition_data_manager.cpp
+++ b/src/core/crypto/partition_data_manager.cpp
@@ -26,8 +26,9 @@
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_offset.h"
#include "core/file_sys/vfs_vector.h"
+#include "core/loader/loader.h"
-using namespace Common;
+using Common::AsArray;
namespace Core::Crypto {
@@ -47,105 +48,123 @@ struct Package2Header {
};
static_assert(sizeof(Package2Header) == 0x200, "Package2Header has incorrect size.");
-const std::array<SHA256Hash, 0x10> source_hashes{
- "B24BD293259DBC7AC5D63F88E60C59792498E6FC5443402C7FFE87EE8B61A3F0"_array32, // keyblob_mac_key_source
- "7944862A3A5C31C6720595EFD302245ABD1B54CCDCF33000557681E65C5664A4"_array32, // master_key_source
- "21E2DF100FC9E094DB51B47B9B1D6E94ED379DB8B547955BEF8FE08D8DD35603"_array32, // package2_key_source
- "FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"_array32, // aes_kek_generation_source
- "FBD10056999EDC7ACDB96098E47E2C3606230270D23281E671F0F389FC5BC585"_array32, // aes_key_generation_source
- "C48B619827986C7F4E3081D59DB2B460C84312650E9A8E6B458E53E8CBCA4E87"_array32, // titlekek_source
- "04AD66143C726B2A139FB6B21128B46F56C553B2B3887110304298D8D0092D9E"_array32, // key_area_key_application_source
- "FD434000C8FF2B26F8E9A9D2D2C12F6BE5773CBB9DC86300E1BD99F8EA33A417"_array32, // key_area_key_ocean_source
- "1F17B1FD51AD1C2379B58F152CA4912EC2106441E51722F38700D5937A1162F7"_array32, // key_area_key_system_source
- "6B2ED877C2C52334AC51E59ABFA7EC457F4A7D01E46291E9F2EAA45F011D24B7"_array32, // sd_card_kek_source
- "D482743563D3EA5DCDC3B74E97C9AC8A342164FA041A1DC80F17F6D31E4BC01C"_array32, // sd_card_save_key_source
- "2E751CECF7D93A2B957BD5FFCB082FD038CC2853219DD3092C6DAB9838F5A7CC"_array32, // sd_card_nca_key_source
- "1888CAED5551B3EDE01499E87CE0D86827F80820EFB275921055AA4E2ABDFFC2"_array32, // header_kek_source
- "8F783E46852DF6BE0BA4E19273C4ADBAEE16380043E1B8C418C4089A8BD64AA6"_array32, // header_key_source
- "D1757E52F1AE55FA882EC690BC6F954AC46A83DC22F277F8806BD55577C6EED7"_array32, // rsa_kek_seed3
- "FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"_array32, // rsa_kek_mask0
+// clang-format off
+constexpr std::array source_hashes{
+ AsArray("B24BD293259DBC7AC5D63F88E60C59792498E6FC5443402C7FFE87EE8B61A3F0"), // keyblob_mac_key_source
+ AsArray("7944862A3A5C31C6720595EFD302245ABD1B54CCDCF33000557681E65C5664A4"), // master_key_source
+ AsArray("21E2DF100FC9E094DB51B47B9B1D6E94ED379DB8B547955BEF8FE08D8DD35603"), // package2_key_source
+ AsArray("FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"), // aes_kek_generation_source
+ AsArray("FBD10056999EDC7ACDB96098E47E2C3606230270D23281E671F0F389FC5BC585"), // aes_key_generation_source
+ AsArray("C48B619827986C7F4E3081D59DB2B460C84312650E9A8E6B458E53E8CBCA4E87"), // titlekek_source
+ AsArray("04AD66143C726B2A139FB6B21128B46F56C553B2B3887110304298D8D0092D9E"), // key_area_key_application_source
+ AsArray("FD434000C8FF2B26F8E9A9D2D2C12F6BE5773CBB9DC86300E1BD99F8EA33A417"), // key_area_key_ocean_source
+ AsArray("1F17B1FD51AD1C2379B58F152CA4912EC2106441E51722F38700D5937A1162F7"), // key_area_key_system_source
+ AsArray("6B2ED877C2C52334AC51E59ABFA7EC457F4A7D01E46291E9F2EAA45F011D24B7"), // sd_card_kek_source
+ AsArray("D482743563D3EA5DCDC3B74E97C9AC8A342164FA041A1DC80F17F6D31E4BC01C"), // sd_card_save_key_source
+ AsArray("2E751CECF7D93A2B957BD5FFCB082FD038CC2853219DD3092C6DAB9838F5A7CC"), // sd_card_nca_key_source
+ AsArray("1888CAED5551B3EDE01499E87CE0D86827F80820EFB275921055AA4E2ABDFFC2"), // header_kek_source
+ AsArray("8F783E46852DF6BE0BA4E19273C4ADBAEE16380043E1B8C418C4089A8BD64AA6"), // header_key_source
+ AsArray("D1757E52F1AE55FA882EC690BC6F954AC46A83DC22F277F8806BD55577C6EED7"), // rsa_kek_seed3
+ AsArray("FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"), // rsa_kek_mask0
};
-
-const std::array<SHA256Hash, 0x20> keyblob_source_hashes{
- "8A06FE274AC491436791FDB388BCDD3AB9943BD4DEF8094418CDAC150FD73786"_array32, // keyblob_key_source_00
- "2D5CAEB2521FEF70B47E17D6D0F11F8CE2C1E442A979AD8035832C4E9FBCCC4B"_array32, // keyblob_key_source_01
- "61C5005E713BAE780641683AF43E5F5C0E03671117F702F401282847D2FC6064"_array32, // keyblob_key_source_02
- "8E9795928E1C4428E1B78F0BE724D7294D6934689C11B190943923B9D5B85903"_array32, // keyblob_key_source_03
- "95FA33AF95AFF9D9B61D164655B32710ED8D615D46C7D6CC3CC70481B686B402"_array32, // keyblob_key_source_04
- "3F5BE7B3C8B1ABD8C10B4B703D44766BA08730562C172A4FE0D6B866B3E2DB3E"_array32, // keyblob_key_source_05
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_06
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_07
-
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_08
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_09
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0A
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0B
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0C
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0D
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0E
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0F
-
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_10
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_11
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_12
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_13
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_14
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_15
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_16
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_17
-
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_18
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_19
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1A
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1B
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1C
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1D
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1E
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1F
+// clang-format on
+
+// clang-format off
+constexpr std::array keyblob_source_hashes{
+ AsArray("8A06FE274AC491436791FDB388BCDD3AB9943BD4DEF8094418CDAC150FD73786"), // keyblob_key_source_00
+ AsArray("2D5CAEB2521FEF70B47E17D6D0F11F8CE2C1E442A979AD8035832C4E9FBCCC4B"), // keyblob_key_source_01
+ AsArray("61C5005E713BAE780641683AF43E5F5C0E03671117F702F401282847D2FC6064"), // keyblob_key_source_02
+ AsArray("8E9795928E1C4428E1B78F0BE724D7294D6934689C11B190943923B9D5B85903"), // keyblob_key_source_03
+ AsArray("95FA33AF95AFF9D9B61D164655B32710ED8D615D46C7D6CC3CC70481B686B402"), // keyblob_key_source_04
+ AsArray("3F5BE7B3C8B1ABD8C10B4B703D44766BA08730562C172A4FE0D6B866B3E2DB3E"), // keyblob_key_source_05
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_06
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_07
+
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_08
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_09
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_0A
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_0B
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_0C
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_0D
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_0E
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_0F
+
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_10
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_11
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_12
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_13
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_14
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_15
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_16
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_17
+
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_18
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_19
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_1A
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_1B
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_1C
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_1D
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_1E
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // keyblob_key_source_1F
};
-
-const std::array<SHA256Hash, 0x20> master_key_hashes{
- "0EE359BE3C864BB0782E1D70A718A0342C551EED28C369754F9C4F691BECF7CA"_array32, // master_key_00
- "4FE707B7E4ABDAF727C894AAF13B1351BFE2AC90D875F73B2E20FA94B9CC661E"_array32, // master_key_01
- "79277C0237A2252EC3DFAC1F7C359C2B3D121E9DB15BB9AB4C2B4408D2F3AE09"_array32, // master_key_02
- "4F36C565D13325F65EE134073C6A578FFCB0008E02D69400836844EAB7432754"_array32, // master_key_03
- "75FF1D95D26113550EE6FCC20ACB58E97EDEB3A2FF52543ED5AEC63BDCC3DA50"_array32, // master_key_04
- "EBE2BCD6704673EC0F88A187BB2AD9F1CC82B718C389425941BDC194DC46B0DD"_array32, // master_key_05
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_06
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_07
-
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_08
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_09
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0A
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0B
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0C
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0D
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0E
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0F
-
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_10
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_11
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_12
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_13
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_14
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_15
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_16
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_17
-
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_18
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_19
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1A
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1B
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1C
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1D
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1E
- "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1F
+// clang-format on
+
+// clang-format off
+constexpr std::array master_key_hashes{
+ AsArray("0EE359BE3C864BB0782E1D70A718A0342C551EED28C369754F9C4F691BECF7CA"), // master_key_00
+ AsArray("4FE707B7E4ABDAF727C894AAF13B1351BFE2AC90D875F73B2E20FA94B9CC661E"), // master_key_01
+ AsArray("79277C0237A2252EC3DFAC1F7C359C2B3D121E9DB15BB9AB4C2B4408D2F3AE09"), // master_key_02
+ AsArray("4F36C565D13325F65EE134073C6A578FFCB0008E02D69400836844EAB7432754"), // master_key_03
+ AsArray("75FF1D95D26113550EE6FCC20ACB58E97EDEB3A2FF52543ED5AEC63BDCC3DA50"), // master_key_04
+ AsArray("EBE2BCD6704673EC0F88A187BB2AD9F1CC82B718C389425941BDC194DC46B0DD"), // master_key_05
+ AsArray("9497E6779F5D840F2BBA1DE4E95BA1D6F21EFC94717D5AE5CA37D7EC5BD37A19"), // master_key_06
+ AsArray("4EC96B8CB01B8DCE382149443430B2B6EBCB2983348AFA04A25E53609DABEDF6"), // master_key_07
+
+ AsArray("2998E2E23609BC2675FF062A2D64AF5B1B78DFF463B24119D64A1B64F01B2D51"), // master_key_08
+ AsArray("9D486A98067C44B37CF173D3BF577891EB6081FF6B4A166347D9DBBF7025076B"), // master_key_09
+ AsArray("4EC5A237A75A083A9C5F6CF615601522A7F822D06BD4BA32612C9CEBBB29BD45"), // master_key_0A
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_0B
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_0C
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_0D
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_0E
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_0F
+
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_10
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_11
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_12
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_13
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_14
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_15
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_16
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_17
+
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_18
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_19
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_1A
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_1B
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_1C
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_1D
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_1E
+ AsArray("0000000000000000000000000000000000000000000000000000000000000000"), // master_key_1F
};
+// clang-format on
+
+static constexpr u8 CalculateMaxKeyblobSourceHash() {
+ const auto is_zero = [](const auto& data) {
+ // TODO: Replace with std::all_of whenever mingw decides to update their
+ // libraries to include the constexpr variant of it.
+ for (const auto element : data) {
+ if (element != 0) {
+ return false;
+ }
+ }
+ return true;
+ };
-static u8 CalculateMaxKeyblobSourceHash() {
for (s8 i = 0x1F; i >= 0; --i) {
- if (keyblob_source_hashes[i] != SHA256Hash{})
+ if (!is_zero(keyblob_source_hashes[i])) {
return static_cast<u8>(i + 1);
+ }
}
return 0;
@@ -346,12 +365,11 @@ FileSys::VirtualFile PartitionDataManager::GetPackage2Raw(Package2Type type) con
}
static bool AttemptDecrypt(const std::array<u8, 16>& key, Package2Header& header) {
- const std::vector<u8> iv(header.header_ctr.begin(), header.header_ctr.end());
Package2Header temp = header;
AESCipher<Key128> cipher(key, Mode::CTR);
- cipher.SetIV(iv);
- cipher.Transcode(&temp.header_ctr, sizeof(Package2Header) - 0x100, &temp.header_ctr,
- Op::Decrypt);
+ cipher.SetIV(header.header_ctr);
+ cipher.Transcode(&temp.header_ctr, sizeof(Package2Header) - sizeof(Package2Header::signature),
+ &temp.header_ctr, Op::Decrypt);
if (temp.magic == Common::MakeMagic('P', 'K', '2', '1')) {
header = temp;
return true;
@@ -388,7 +406,7 @@ void PartitionDataManager::DecryptPackage2(const std::array<Key128, 0x20>& packa
auto c = a->ReadAllBytes();
AESCipher<Key128> cipher(package2_keys[revision], Mode::CTR);
- cipher.SetIV({header.section_ctr[1].begin(), header.section_ctr[1].end()});
+ cipher.SetIV(header.section_ctr[1]);
cipher.Transcode(c.data(), c.size(), c.data(), Op::Decrypt);
const auto ini_file = std::make_shared<FileSys::VectorVfsFile>(c);
diff --git a/src/core/device_memory.cpp b/src/core/device_memory.cpp
index 51097ced3..0c4b440ed 100644
--- a/src/core/device_memory.cpp
+++ b/src/core/device_memory.cpp
@@ -2,14 +2,11 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "core/core.h"
#include "core/device_memory.h"
-#include "core/memory.h"
namespace Core {
-DeviceMemory::DeviceMemory(System& system) : buffer{DramMemoryMap::Size}, system{system} {}
-
+DeviceMemory::DeviceMemory() : buffer{DramMemoryMap::Size} {}
DeviceMemory::~DeviceMemory() = default;
} // namespace Core
diff --git a/src/core/device_memory.h b/src/core/device_memory.h
index 9efa088d0..5b1ae28f3 100644
--- a/src/core/device_memory.h
+++ b/src/core/device_memory.h
@@ -4,14 +4,11 @@
#pragma once
-#include "common/assert.h"
-#include "common/common_funcs.h"
+#include "common/common_types.h"
#include "common/virtual_buffer.h"
namespace Core {
-class System;
-
namespace DramMemoryMap {
enum : u64 {
Base = 0x80000000ULL,
@@ -26,7 +23,7 @@ enum : u64 {
class DeviceMemory : NonCopyable {
public:
- explicit DeviceMemory(Core::System& system);
+ explicit DeviceMemory();
~DeviceMemory();
template <typename T>
@@ -45,7 +42,6 @@ public:
private:
Common::VirtualBuffer<u8> buffer;
- Core::System& system;
};
} // namespace Core
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
index 285277ef8..e04a54c3c 100644
--- a/src/core/file_sys/bis_factory.cpp
+++ b/src/core/file_sys/bis_factory.cpp
@@ -8,7 +8,6 @@
#include "core/file_sys/bis_factory.h"
#include "core/file_sys/mode.h"
#include "core/file_sys/registered_cache.h"
-#include "core/settings.h"
namespace FileSys {
@@ -86,7 +85,7 @@ VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id) const {
auto& keys = Core::Crypto::KeyManager::Instance();
Core::Crypto::PartitionDataManager pdm{
Core::System::GetInstance().GetFilesystem()->OpenDirectory(
- FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), Mode::Read)};
+ Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir), Mode::Read)};
keys.PopulateFromPartitionData(pdm);
switch (id) {
diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h
index 8f0451c98..438d3f8d8 100644
--- a/src/core/file_sys/bis_factory.h
+++ b/src/core/file_sys/bis_factory.h
@@ -6,7 +6,8 @@
#include <memory>
-#include "core/file_sys/vfs.h"
+#include "common/common_types.h"
+#include "core/file_sys/vfs_types.h"
namespace FileSys {
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index 664a47e7f..956da68f7 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -8,11 +8,11 @@
#include <fmt/ostream.h>
#include "common/logging/log.h"
+#include "core/crypto/key_manager.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/partition_filesystem.h"
-#include "core/file_sys/romfs.h"
#include "core/file_sys/submission_package.h"
#include "core/file_sys/vfs_concat.h"
#include "core/file_sys/vfs_offset.h"
@@ -31,7 +31,8 @@ constexpr std::array partition_names{
XCI::XCI(VirtualFile file_)
: file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA},
- partitions(partition_names.size()), partitions_raw(partition_names.size()) {
+ partitions(partition_names.size()),
+ partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} {
if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
status = Loader::ResultStatus::ErrorBadXCIHeader;
return;
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index e1b136426..2d0a0f285 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -9,9 +9,12 @@
#include <vector>
#include "common/common_types.h"
#include "common/swap.h"
-#include "core/crypto/key_manager.h"
#include "core/file_sys/vfs.h"
+namespace Core::Crypto {
+class KeyManager;
+}
+
namespace Loader {
enum class ResultStatus : u16;
}
@@ -140,6 +143,6 @@ private:
u64 update_normal_partition_end;
- Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
+ Core::Crypto::KeyManager& keys;
};
} // namespace FileSys
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 473245d5a..426fb6bb5 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -10,10 +10,10 @@
#include "common/logging/log.h"
#include "core/crypto/aes_util.h"
#include "core/crypto/ctr_encryption_layer.h"
+#include "core/crypto/key_manager.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_patch.h"
#include "core/file_sys/partition_filesystem.h"
-#include "core/file_sys/romfs.h"
#include "core/file_sys/vfs_offset.h"
#include "core/loader/loader.h"
@@ -119,7 +119,8 @@ static bool IsValidNCA(const NCAHeader& header) {
}
NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
- : file(std::move(file_)), bktr_base_romfs(std::move(bktr_base_romfs_)) {
+ : file(std::move(file_)),
+ bktr_base_romfs(std::move(bktr_base_romfs_)), keys{Core::Crypto::KeyManager::Instance()} {
if (file == nullptr) {
status = Loader::ResultStatus::ErrorNullFile;
return;
@@ -495,9 +496,10 @@ VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 s
auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(std::move(in), *key,
starting_offset);
- std::vector<u8> iv(16);
- for (u8 i = 0; i < 8; ++i)
- iv[i] = s_header.raw.section_ctr[0x8 - i - 1];
+ Core::Crypto::CTREncryptionLayer::IVData iv{};
+ for (std::size_t i = 0; i < 8; ++i) {
+ iv[i] = s_header.raw.section_ctr[8 - i - 1];
+ }
out->SetIV(iv);
return std::static_pointer_cast<VfsFile>(out);
}
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index d25cbcf91..69292232a 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -158,7 +158,7 @@ private:
bool encrypted = false;
bool is_update = false;
- Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
+ Core::Crypto::KeyManager& keys;
};
} // namespace FileSys
diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp
index 63cd2eead..b0a130345 100644
--- a/src/core/file_sys/control_metadata.cpp
+++ b/src/core/file_sys/control_metadata.cpp
@@ -5,6 +5,7 @@
#include "common/string_util.h"
#include "common/swap.h"
#include "core/file_sys/control_metadata.h"
+#include "core/file_sys/vfs.h"
namespace FileSys {
diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h
index e37b2fadf..9ab86e35b 100644
--- a/src/core/file_sys/control_metadata.h
+++ b/src/core/file_sys/control_metadata.h
@@ -10,7 +10,7 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_types.h"
namespace FileSys {
diff --git a/src/core/file_sys/kernel_executable.cpp b/src/core/file_sys/kernel_executable.cpp
index 76313679d..ef93ef3ed 100644
--- a/src/core/file_sys/kernel_executable.cpp
+++ b/src/core/file_sys/kernel_executable.cpp
@@ -2,9 +2,12 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cstring>
+
#include "common/string_util.h"
#include "core/file_sys/kernel_executable.h"
#include "core/file_sys/vfs_offset.h"
+#include "core/loader/loader.h"
namespace FileSys {
diff --git a/src/core/file_sys/kernel_executable.h b/src/core/file_sys/kernel_executable.h
index 324a57384..044c554d3 100644
--- a/src/core/file_sys/kernel_executable.h
+++ b/src/core/file_sys/kernel_executable.h
@@ -4,10 +4,17 @@
#pragma once
+#include <array>
+#include <vector>
+
#include "common/common_funcs.h"
+#include "common/common_types.h"
#include "common/swap.h"
#include "core/file_sys/vfs_types.h"
-#include "core/loader/loader.h"
+
+namespace Loader {
+enum class ResultStatus : u16;
+}
namespace FileSys {
diff --git a/src/core/file_sys/mode.h b/src/core/file_sys/mode.h
index c95205668..2b4f21073 100644
--- a/src/core/file_sys/mode.h
+++ b/src/core/file_sys/mode.h
@@ -4,6 +4,7 @@
#pragma once
+#include "common/common_funcs.h"
#include "common/common_types.h"
namespace FileSys {
@@ -11,13 +12,11 @@ namespace FileSys {
enum class Mode : u32 {
Read = 1,
Write = 2,
- ReadWrite = 3,
+ ReadWrite = Read | Write,
Append = 4,
- WriteAppend = 6,
+ WriteAppend = Write | Append,
};
-inline u32 operator&(Mode lhs, Mode rhs) {
- return static_cast<u32>(lhs) & static_cast<u32>(rhs);
-}
+DECLARE_ENUM_FLAG_OPERATORS(Mode)
} // namespace FileSys
diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp
index 93d0df6b9..2d1476e3a 100644
--- a/src/core/file_sys/nca_metadata.cpp
+++ b/src/core/file_sys/nca_metadata.cpp
@@ -7,6 +7,7 @@
#include "common/logging/log.h"
#include "common/swap.h"
#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/vfs.h"
namespace FileSys {
diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h
index 1f82fff0a..53535e5f5 100644
--- a/src/core/file_sys/nca_metadata.h
+++ b/src/core/file_sys/nca_metadata.h
@@ -10,7 +10,7 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_types.h"
namespace FileSys {
class CNMT;
diff --git a/src/core/file_sys/nca_patch.cpp b/src/core/file_sys/nca_patch.cpp
index 0090cc6c4..fe7375e84 100644
--- a/src/core/file_sys/nca_patch.cpp
+++ b/src/core/file_sys/nca_patch.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <algorithm>
+#include <array>
#include <cstddef>
#include <cstring>
@@ -66,7 +67,7 @@ std::size_t BKTR::Read(u8* data, std::size_t length, std::size_t offset) const {
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(key, Core::Crypto::Mode::CTR);
// Calculate AES IV
- std::vector<u8> iv(16);
+ std::array<u8, 16> iv{};
auto subsection_ctr = subsection.ctr;
auto offset_iv = section_offset + base_offset;
for (std::size_t i = 0; i < section_ctr.size(); ++i)
diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp
index 846986736..48a2ed4d4 100644
--- a/src/core/file_sys/partition_filesystem.cpp
+++ b/src/core/file_sys/partition_filesystem.cpp
@@ -21,7 +21,7 @@ bool PartitionFilesystem::Header::HasValidMagicValue() const {
magic == Common::MakeMagic('P', 'F', 'S', '0');
}
-PartitionFilesystem::PartitionFilesystem(std::shared_ptr<VfsFile> file) {
+PartitionFilesystem::PartitionFilesystem(VirtualFile file) {
// At least be as large as the header
if (file->GetSize() < sizeof(Header)) {
status = Loader::ResultStatus::ErrorBadPFSHeader;
@@ -89,11 +89,11 @@ std::map<std::string, u64> PartitionFilesystem::GetFileSizes() const {
return sizes;
}
-std::vector<std::shared_ptr<VfsFile>> PartitionFilesystem::GetFiles() const {
+std::vector<VirtualFile> PartitionFilesystem::GetFiles() const {
return pfs_files;
}
-std::vector<std::shared_ptr<VfsDirectory>> PartitionFilesystem::GetSubdirectories() const {
+std::vector<VirtualDir> PartitionFilesystem::GetSubdirectories() const {
return {};
}
@@ -101,7 +101,7 @@ std::string PartitionFilesystem::GetName() const {
return is_hfs ? "HFS0" : "PFS0";
}
-std::shared_ptr<VfsDirectory> PartitionFilesystem::GetParentDirectory() const {
+VirtualDir PartitionFilesystem::GetParentDirectory() const {
// TODO(DarkLordZach): Add support for nested containers.
return nullptr;
}
diff --git a/src/core/file_sys/partition_filesystem.h b/src/core/file_sys/partition_filesystem.h
index 279193b19..0f831148e 100644
--- a/src/core/file_sys/partition_filesystem.h
+++ b/src/core/file_sys/partition_filesystem.h
@@ -24,7 +24,7 @@ namespace FileSys {
*/
class PartitionFilesystem : public ReadOnlyVfsDirectory {
public:
- explicit PartitionFilesystem(std::shared_ptr<VfsFile> file);
+ explicit PartitionFilesystem(VirtualFile file);
~PartitionFilesystem() override;
Loader::ResultStatus GetStatus() const;
@@ -32,10 +32,10 @@ public:
std::map<std::string, u64> GetFileOffsets() const;
std::map<std::string, u64> GetFileSizes() const;
- std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
- std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
+ std::vector<VirtualFile> GetFiles() const override;
+ std::vector<VirtualDir> GetSubdirectories() const override;
std::string GetName() const override;
- std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
+ VirtualDir GetParentDirectory() const override;
void PrintDebugInfo() const;
private:
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index c47ff863e..c228d253e 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -49,8 +49,7 @@ std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
}
-std::shared_ptr<VfsDirectory> FindSubdirectoryCaseless(const std::shared_ptr<VfsDirectory> dir,
- std::string_view name) {
+VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name) {
#ifdef _WIN32
return dir->GetSubdirectory(name);
#else
@@ -288,8 +287,8 @@ std::optional<std::vector<Core::Memory::CheatEntry>> ReadCheatFileFromFolder(
}
Core::Memory::TextCheatParser parser;
- return parser.Parse(
- system, std::string_view(reinterpret_cast<const char* const>(data.data()), data.size()));
+ return parser.Parse(system,
+ std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
}
} // Anonymous namespace
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index f4cb918dd..532f4995f 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -6,10 +6,11 @@
#include <map>
#include <memory>
+#include <optional>
#include <string>
#include "common/common_types.h"
#include "core/file_sys/nca_metadata.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_types.h"
#include "core/memory/dmnt_cheat_types.h"
namespace Core {
@@ -31,8 +32,7 @@ std::string FormatTitleVersion(u32 version,
// Returns a directory with name matching name case-insensitive. Returns nullptr if directory
// doesn't have a directory with name.
-std::shared_ptr<VfsDirectory> FindSubdirectoryCaseless(const std::shared_ptr<VfsDirectory> dir,
- std::string_view name);
+VirtualDir FindSubdirectoryCaseless(VirtualDir dir, std::string_view name);
// A centralized class to manage patches to games.
class PatchManager {
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index 43169bf9f..9cf49bf44 100644
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.cpp
@@ -7,6 +7,7 @@
#include "common/logging/log.h"
#include "core/file_sys/program_metadata.h"
+#include "core/file_sys/vfs.h"
#include "core/loader/loader.h"
namespace FileSys {
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
index 35069972b..455532567 100644
--- a/src/core/file_sys/program_metadata.h
+++ b/src/core/file_sys/program_metadata.h
@@ -9,7 +9,7 @@
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/swap.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_types.h"
namespace Loader {
enum class ResultStatus : u16;
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index 37351c561..da01002d5 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -257,8 +257,7 @@ std::vector<NcaID> PlaceholderCache::List() const {
for (const auto& sdir : dir->GetSubdirectories()) {
for (const auto& file : sdir->GetFiles()) {
const auto name = file->GetName();
- if (name.length() == 36 && name[32] == '.' && name[33] == 'n' && name[34] == 'c' &&
- name[35] == 'a') {
+ if (name.length() == 36 && name.ends_with(".nca")) {
out.push_back(Common::HexStringToArray<0x10>(name.substr(0, 32)));
}
}
@@ -344,15 +343,18 @@ VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
static std::optional<NcaID> CheckMapForContentRecord(const std::map<u64, CNMT>& map, u64 title_id,
ContentRecordType type) {
- if (map.find(title_id) == map.end())
- return {};
-
- const auto& cnmt = map.at(title_id);
+ const auto cmnt_iter = map.find(title_id);
+ if (cmnt_iter == map.cend()) {
+ return std::nullopt;
+ }
- const auto iter = std::find_if(cnmt.GetContentRecords().begin(), cnmt.GetContentRecords().end(),
+ const auto& cnmt = cmnt_iter->second;
+ const auto& content_records = cnmt.GetContentRecords();
+ const auto iter = std::find_if(content_records.cbegin(), content_records.cend(),
[type](const ContentRecord& rec) { return rec.type == type; });
- if (iter == cnmt.GetContentRecords().end())
- return {};
+ if (iter == content_records.cend()) {
+ return std::nullopt;
+ }
return std::make_optional(iter->nca_id);
}
@@ -467,14 +469,16 @@ VirtualFile RegisteredCache::GetEntryUnparsed(u64 title_id, ContentRecordType ty
std::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
const auto meta_iter = meta.find(title_id);
- if (meta_iter != meta.end())
+ if (meta_iter != meta.cend()) {
return meta_iter->second.GetTitleVersion();
+ }
const auto yuzu_meta_iter = yuzu_meta.find(title_id);
- if (yuzu_meta_iter != yuzu_meta.end())
+ if (yuzu_meta_iter != yuzu_meta.cend()) {
return yuzu_meta_iter->second.GetTitleVersion();
+ }
- return {};
+ return std::nullopt;
}
VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const {
@@ -547,56 +551,6 @@ InstallResult RegisteredCache::InstallEntry(const XCI& xci, bool overwrite_if_ex
return InstallEntry(*xci.GetSecurePartitionNSP(), overwrite_if_exists, copy);
}
-bool RegisteredCache::RemoveExistingEntry(u64 title_id) {
- const auto delete_nca = [this](const NcaID& id) {
- const auto path = GetRelativePathFromNcaID(id, false, true, false);
-
- if (dir->GetFileRelative(path) == nullptr) {
- return false;
- }
-
- Core::Crypto::SHA256Hash hash{};
- mbedtls_sha256_ret(id.data(), id.size(), hash.data(), 0);
- const auto dirname = fmt::format("000000{:02X}", hash[0]);
-
- const auto dir2 = GetOrCreateDirectoryRelative(dir, dirname);
-
- const auto res = dir2->DeleteFile(fmt::format("{}.nca", Common::HexToString(id, false)));
-
- return res;
- };
-
- // If an entry exists in the registered cache, remove it
- if (HasEntry(title_id, ContentRecordType::Meta)) {
- LOG_INFO(Loader,
- "Previously installed entry (v{}) for title_id={:016X} detected! "
- "Attempting to remove...",
- GetEntryVersion(title_id).value_or(0), title_id);
- // Get all the ncas associated with the current CNMT and delete them
- const auto meta_old_id =
- GetNcaIDFromMetadata(title_id, ContentRecordType::Meta).value_or(NcaID{});
- const auto program_id =
- GetNcaIDFromMetadata(title_id, ContentRecordType::Program).value_or(NcaID{});
- const auto data_id =
- GetNcaIDFromMetadata(title_id, ContentRecordType::Data).value_or(NcaID{});
- const auto control_id =
- GetNcaIDFromMetadata(title_id, ContentRecordType::Control).value_or(NcaID{});
- const auto html_id =
- GetNcaIDFromMetadata(title_id, ContentRecordType::HtmlDocument).value_or(NcaID{});
- const auto legal_id =
- GetNcaIDFromMetadata(title_id, ContentRecordType::LegalInformation).value_or(NcaID{});
-
- delete_nca(meta_old_id);
- delete_nca(program_id);
- delete_nca(data_id);
- delete_nca(control_id);
- delete_nca(html_id);
- delete_nca(legal_id);
- return true;
- }
- return false;
-}
-
InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_exists,
const VfsCopyFunction& copy) {
const auto ncas = nsp.GetNCAsCollapsed();
@@ -666,25 +620,25 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex
InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type,
bool overwrite_if_exists, const VfsCopyFunction& copy) {
- CNMTHeader header{
- nca.GetTitleId(), // Title ID
- 0, // Ignore/Default title version
- type, // Type
- {}, // Padding
- 0x10, // Default table offset
- 1, // 1 Content Entry
- 0, // No Meta Entries
- {}, // Padding
- {}, // Reserved 1
- 0, // Is committed
- 0, // Required download system version
- {}, // Reserved 2
+ const CNMTHeader header{
+ .title_id = nca.GetTitleId(),
+ .title_version = 0,
+ .type = type,
+ .reserved = {},
+ .table_offset = 0x10,
+ .number_content_entries = 1,
+ .number_meta_entries = 0,
+ .attributes = 0,
+ .reserved2 = {},
+ .is_committed = 0,
+ .required_download_system_version = 0,
+ .reserved3 = {},
};
- OptionalHeader opt_header{0, 0};
+ const OptionalHeader opt_header{0, 0};
ContentRecord c_rec{{}, {}, {}, GetCRTypeFromNCAType(nca.GetType()), {}};
const auto& data = nca.GetBaseFile()->ReadBytes(0x100000);
mbedtls_sha256_ret(data.data(), data.size(), c_rec.hash.data(), 0);
- memcpy(&c_rec.nca_id, &c_rec.hash, 16);
+ std::memcpy(&c_rec.nca_id, &c_rec.hash, 16);
const CNMT new_cnmt(header, opt_header, {c_rec}, {});
if (!RawInstallYuzuMeta(new_cnmt)) {
return InstallResult::ErrorMetaFailed;
@@ -692,6 +646,57 @@ InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type,
return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id);
}
+bool RegisteredCache::RemoveExistingEntry(u64 title_id) const {
+ const auto delete_nca = [this](const NcaID& id) {
+ const auto path = GetRelativePathFromNcaID(id, false, true, false);
+
+ const bool isFile = dir->GetFileRelative(path) != nullptr;
+ const bool isDir = dir->GetDirectoryRelative(path) != nullptr;
+
+ if (isFile) {
+ return dir->DeleteFile(path);
+ } else if (isDir) {
+ return dir->DeleteSubdirectoryRecursive(path);
+ }
+
+ return false;
+ };
+
+ // If an entry exists in the registered cache, remove it
+ if (HasEntry(title_id, ContentRecordType::Meta)) {
+ LOG_INFO(Loader,
+ "Previously installed entry (v{}) for title_id={:016X} detected! "
+ "Attempting to remove...",
+ GetEntryVersion(title_id).value_or(0), title_id);
+
+ // Get all the ncas associated with the current CNMT and delete them
+ const auto meta_old_id =
+ GetNcaIDFromMetadata(title_id, ContentRecordType::Meta).value_or(NcaID{});
+ const auto program_id =
+ GetNcaIDFromMetadata(title_id, ContentRecordType::Program).value_or(NcaID{});
+ const auto data_id =
+ GetNcaIDFromMetadata(title_id, ContentRecordType::Data).value_or(NcaID{});
+ const auto control_id =
+ GetNcaIDFromMetadata(title_id, ContentRecordType::Control).value_or(NcaID{});
+ const auto html_id =
+ GetNcaIDFromMetadata(title_id, ContentRecordType::HtmlDocument).value_or(NcaID{});
+ const auto legal_id =
+ GetNcaIDFromMetadata(title_id, ContentRecordType::LegalInformation).value_or(NcaID{});
+
+ const auto deleted_meta = delete_nca(meta_old_id);
+ const auto deleted_program = delete_nca(program_id);
+ const auto deleted_data = delete_nca(data_id);
+ const auto deleted_control = delete_nca(control_id);
+ const auto deleted_html = delete_nca(html_id);
+ const auto deleted_legal = delete_nca(legal_id);
+
+ return deleted_meta && (deleted_meta || deleted_program || deleted_data ||
+ deleted_control || deleted_html || deleted_legal);
+ }
+
+ return false;
+}
+
InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy,
bool overwrite_if_exists,
std::optional<NcaID> override_id) {
@@ -722,7 +727,7 @@ InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFuncti
LOG_WARNING(Loader, "Overwriting existing NCA...");
VirtualDir c_dir;
{ c_dir = dir->GetFileRelative(path)->GetContainingDirectory(); }
- c_dir->DeleteFile(FileUtil::GetFilename(path));
+ c_dir->DeleteFile(Common::FS::GetFilename(path));
}
auto out = dir->CreateFileRelative(path);
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index 29cf0d40c..5b414b0f0 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -133,9 +133,9 @@ public:
// Parsing function defines the conversion from raw file to NCA. If there are other steps
// besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom
// parsing function.
- explicit RegisteredCache(VirtualDir dir,
- ContentProviderParsingFunction parsing_function =
- [](const VirtualFile& file, const NcaID& id) { return file; });
+ explicit RegisteredCache(
+ VirtualDir dir, ContentProviderParsingFunction parsing_function =
+ [](const VirtualFile& file, const NcaID& id) { return file; });
~RegisteredCache() override;
void Refresh() override;
@@ -155,9 +155,6 @@ public:
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
std::optional<u64> title_id = {}) const override;
- // Removes an existing entry based on title id
- bool RemoveExistingEntry(u64 title_id);
-
// Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure
// there is a meta NCA and all of them are accessible.
InstallResult InstallEntry(const XCI& xci, bool overwrite_if_exists = false,
@@ -172,6 +169,9 @@ public:
InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false,
const VfsCopyFunction& copy = &VfsRawCopy);
+ // Removes an existing entry based on title id
+ bool RemoveExistingEntry(u64 title_id) const;
+
private:
template <typename T>
void IterateAllMetadata(std::vector<T>& out,
diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h
index 2fd07ed04..82e683782 100644
--- a/src/core/file_sys/romfs.h
+++ b/src/core/file_sys/romfs.h
@@ -4,7 +4,6 @@
#pragma once
-#include <array>
#include "core/file_sys/vfs.h"
namespace FileSys {
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index adfd2c1a4..ba4efee3a 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -17,23 +17,23 @@ constexpr char SAVE_DATA_SIZE_FILENAME[] = ".yuzu_save_size";
namespace {
-void PrintSaveDataDescriptorWarnings(SaveDataDescriptor meta) {
+void PrintSaveDataAttributeWarnings(SaveDataAttribute meta) {
if (meta.type == SaveDataType::SystemSaveData || meta.type == SaveDataType::SaveData) {
if (meta.zero_1 != 0) {
LOG_WARNING(Service_FS,
- "Possibly incorrect SaveDataDescriptor, type is "
+ "Possibly incorrect SaveDataAttribute, type is "
"SystemSaveData||SaveData but offset 0x28 is non-zero ({:016X}).",
meta.zero_1);
}
if (meta.zero_2 != 0) {
LOG_WARNING(Service_FS,
- "Possibly incorrect SaveDataDescriptor, type is "
+ "Possibly incorrect SaveDataAttribute, type is "
"SystemSaveData||SaveData but offset 0x30 is non-zero ({:016X}).",
meta.zero_2);
}
if (meta.zero_3 != 0) {
LOG_WARNING(Service_FS,
- "Possibly incorrect SaveDataDescriptor, type is "
+ "Possibly incorrect SaveDataAttribute, type is "
"SystemSaveData||SaveData but offset 0x38 is non-zero ({:016X}).",
meta.zero_3);
}
@@ -41,33 +41,32 @@ void PrintSaveDataDescriptorWarnings(SaveDataDescriptor meta) {
if (meta.type == SaveDataType::SystemSaveData && meta.title_id != 0) {
LOG_WARNING(Service_FS,
- "Possibly incorrect SaveDataDescriptor, type is SystemSaveData but title_id is "
+ "Possibly incorrect SaveDataAttribute, type is SystemSaveData but title_id is "
"non-zero ({:016X}).",
meta.title_id);
}
if (meta.type == SaveDataType::DeviceSaveData && meta.user_id != u128{0, 0}) {
LOG_WARNING(Service_FS,
- "Possibly incorrect SaveDataDescriptor, type is DeviceSaveData but user_id is "
+ "Possibly incorrect SaveDataAttribute, type is DeviceSaveData but user_id is "
"non-zero ({:016X}{:016X})",
meta.user_id[1], meta.user_id[0]);
}
}
-bool ShouldSaveDataBeAutomaticallyCreated(SaveDataSpaceId space, const SaveDataDescriptor& desc) {
- return desc.type == SaveDataType::CacheStorage || desc.type == SaveDataType::TemporaryStorage ||
+bool ShouldSaveDataBeAutomaticallyCreated(SaveDataSpaceId space, const SaveDataAttribute& attr) {
+ return attr.type == SaveDataType::CacheStorage || attr.type == SaveDataType::TemporaryStorage ||
(space == SaveDataSpaceId::NandUser && ///< Normal Save Data -- Current Title & User
- (desc.type == SaveDataType::SaveData || desc.type == SaveDataType::DeviceSaveData) &&
- desc.title_id == 0 && desc.save_id == 0);
+ (attr.type == SaveDataType::SaveData || attr.type == SaveDataType::DeviceSaveData) &&
+ attr.title_id == 0 && attr.save_id == 0);
}
} // Anonymous namespace
-std::string SaveDataDescriptor::DebugInfo() const {
- return fmt::format("[type={:02X}, title_id={:016X}, user_id={:016X}{:016X}, "
- "save_id={:016X}, "
+std::string SaveDataAttribute::DebugInfo() const {
+ return fmt::format("[title_id={:016X}, user_id={:016X}{:016X}, save_id={:016X}, type={:02X}, "
"rank={}, index={}]",
- static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id,
+ title_id, user_id[1], user_id[0], save_id, static_cast<u8>(type),
static_cast<u8>(rank), index);
}
@@ -80,8 +79,8 @@ SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save
SaveDataFactory::~SaveDataFactory() = default;
ResultVal<VirtualDir> SaveDataFactory::Create(SaveDataSpaceId space,
- const SaveDataDescriptor& meta) const {
- PrintSaveDataDescriptorWarnings(meta);
+ const SaveDataAttribute& meta) const {
+ PrintSaveDataAttributeWarnings(meta);
const auto save_directory =
GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id);
@@ -98,7 +97,7 @@ ResultVal<VirtualDir> SaveDataFactory::Create(SaveDataSpaceId space,
}
ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space,
- const SaveDataDescriptor& meta) const {
+ const SaveDataAttribute& meta) const {
const auto save_directory =
GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id);
diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h
index 991e57aa1..6625bbbd8 100644
--- a/src/core/file_sys/savedata_factory.h
+++ b/src/core/file_sys/savedata_factory.h
@@ -21,6 +21,7 @@ enum class SaveDataSpaceId : u8 {
TemporaryStorage = 3,
SdCardUser = 4,
ProperSystem = 100,
+ SafeMode = 101,
};
enum class SaveDataType : u8 {
@@ -30,28 +31,50 @@ enum class SaveDataType : u8 {
DeviceSaveData = 3,
TemporaryStorage = 4,
CacheStorage = 5,
+ SystemBcat = 6,
};
enum class SaveDataRank : u8 {
- Primary,
- Secondary,
+ Primary = 0,
+ Secondary = 1,
};
-struct SaveDataDescriptor {
- u64_le title_id;
+enum class SaveDataFlags : u32 {
+ None = (0 << 0),
+ KeepAfterResettingSystemSaveData = (1 << 0),
+ KeepAfterRefurbishment = (1 << 1),
+ KeepAfterResettingSystemSaveDataWithoutUserSaveData = (1 << 2),
+ NeedsSecureDelete = (1 << 3),
+};
+
+struct SaveDataAttribute {
+ u64 title_id;
u128 user_id;
- u64_le save_id;
+ u64 save_id;
SaveDataType type;
SaveDataRank rank;
- u16_le index;
+ u16 index;
INSERT_PADDING_BYTES(4);
- u64_le zero_1;
- u64_le zero_2;
- u64_le zero_3;
+ u64 zero_1;
+ u64 zero_2;
+ u64 zero_3;
std::string DebugInfo() const;
};
-static_assert(sizeof(SaveDataDescriptor) == 0x40, "SaveDataDescriptor has incorrect size.");
+static_assert(sizeof(SaveDataAttribute) == 0x40, "SaveDataAttribute has incorrect size.");
+
+struct SaveDataExtraData {
+ SaveDataAttribute attr;
+ u64 owner_id;
+ s64 timestamp;
+ SaveDataFlags flags;
+ INSERT_PADDING_BYTES(4);
+ s64 available_size;
+ s64 journal_size;
+ s64 commit_id;
+ std::array<u8, 0x190> unused;
+};
+static_assert(sizeof(SaveDataExtraData) == 0x200, "SaveDataExtraData has incorrect size.");
struct SaveDataSize {
u64 normal;
@@ -64,8 +87,8 @@ public:
explicit SaveDataFactory(VirtualDir dir);
~SaveDataFactory();
- ResultVal<VirtualDir> Create(SaveDataSpaceId space, const SaveDataDescriptor& meta) const;
- ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataDescriptor& meta) const;
+ ResultVal<VirtualDir> Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const;
+ ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const;
VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const;
diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp
index 6f732e4d8..cb56d8f2d 100644
--- a/src/core/file_sys/sdmc_factory.cpp
+++ b/src/core/file_sys/sdmc_factory.cpp
@@ -5,8 +5,8 @@
#include <memory>
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/sdmc_factory.h"
+#include "core/file_sys/vfs.h"
#include "core/file_sys/xts_archive.h"
-#include "core/settings.h"
namespace FileSys {
diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h
index 42dc4e08a..2bb92ba93 100644
--- a/src/core/file_sys/sdmc_factory.h
+++ b/src/core/file_sys/sdmc_factory.h
@@ -5,7 +5,7 @@
#pragma once
#include <memory>
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_types.h"
#include "core/hle/result.h"
namespace FileSys {
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index 175a8266a..b9ce93b7c 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -54,7 +54,7 @@ void SetTicketKeys(const std::vector<VirtualFile>& files) {
NSP::NSP(VirtualFile file_)
: file(std::move(file_)), status{Loader::ResultStatus::Success},
- pfs(std::make_shared<PartitionFilesystem>(file)) {
+ pfs(std::make_shared<PartitionFilesystem>(file)), keys{Core::Crypto::KeyManager::Instance()} {
if (pfs->GetStatus() != Loader::ResultStatus::Success) {
status = pfs->GetStatus();
return;
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h
index cf89de6a9..2db5e46b8 100644
--- a/src/core/file_sys/submission_package.h
+++ b/src/core/file_sys/submission_package.h
@@ -10,6 +10,10 @@
#include "common/common_types.h"
#include "core/file_sys/vfs.h"
+namespace Core::Crypto {
+class KeyManager;
+}
+
namespace Loader {
enum class ResultStatus : u16;
}
@@ -73,7 +77,7 @@ private:
std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> ncas;
std::vector<VirtualFile> ticket_files;
- Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
+ Core::Crypto::KeyManager& keys;
VirtualFile romfs;
VirtualDir exefs;
diff --git a/src/core/file_sys/system_archive/mii_model.cpp b/src/core/file_sys/system_archive/mii_model.cpp
index 61bb67945..d65c7d234 100644
--- a/src/core/file_sys/system_archive/mii_model.cpp
+++ b/src/core/file_sys/system_archive/mii_model.cpp
@@ -27,18 +27,12 @@ VirtualDir MiiModel() {
auto out = std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{},
std::vector<VirtualDir>{}, "data");
- out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_LINEAR.size()>>(
- MiiModelData::TEXTURE_LOW_LINEAR, "NXTextureLowLinear.dat"));
- out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_SRGB.size()>>(
- MiiModelData::TEXTURE_LOW_SRGB, "NXTextureLowSRGB.dat"));
- out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_LINEAR.size()>>(
- MiiModelData::TEXTURE_MID_LINEAR, "NXTextureMidLinear.dat"));
- out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_SRGB.size()>>(
- MiiModelData::TEXTURE_MID_SRGB, "NXTextureMidSRGB.dat"));
- out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_HIGH.size()>>(
- MiiModelData::SHAPE_HIGH, "ShapeHigh.dat"));
- out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_MID.size()>>(
- MiiModelData::SHAPE_MID, "ShapeMid.dat"));
+ out->AddFile(MakeArrayFile(MiiModelData::TEXTURE_LOW_LINEAR, "NXTextureLowLinear.dat"));
+ out->AddFile(MakeArrayFile(MiiModelData::TEXTURE_LOW_SRGB, "NXTextureLowSRGB.dat"));
+ out->AddFile(MakeArrayFile(MiiModelData::TEXTURE_MID_LINEAR, "NXTextureMidLinear.dat"));
+ out->AddFile(MakeArrayFile(MiiModelData::TEXTURE_MID_SRGB, "NXTextureMidSRGB.dat"));
+ out->AddFile(MakeArrayFile(MiiModelData::SHAPE_HIGH, "ShapeHigh.dat"));
+ out->AddFile(MakeArrayFile(MiiModelData::SHAPE_MID, "ShapeMid.dat"));
return out;
}
diff --git a/src/core/file_sys/system_archive/ng_word.cpp b/src/core/file_sys/system_archive/ng_word.cpp
index f4443784d..100d3c5db 100644
--- a/src/core/file_sys/system_archive/ng_word.cpp
+++ b/src/core/file_sys/system_archive/ng_word.cpp
@@ -24,19 +24,18 @@ constexpr std::array<u8, 30> WORD_TXT{
} // namespace NgWord1Data
VirtualDir NgWord1() {
- std::vector<VirtualFile> files(NgWord1Data::NUMBER_WORD_TXT_FILES);
+ std::vector<VirtualFile> files;
+ files.reserve(NgWord1Data::NUMBER_WORD_TXT_FILES);
for (std::size_t i = 0; i < files.size(); ++i) {
- files[i] = std::make_shared<ArrayVfsFile<NgWord1Data::WORD_TXT.size()>>(
- NgWord1Data::WORD_TXT, fmt::format("{}.txt", i));
+ files.push_back(MakeArrayFile(NgWord1Data::WORD_TXT, fmt::format("{}.txt", i)));
}
- files.push_back(std::make_shared<ArrayVfsFile<NgWord1Data::WORD_TXT.size()>>(
- NgWord1Data::WORD_TXT, "common.txt"));
- files.push_back(std::make_shared<ArrayVfsFile<NgWord1Data::VERSION_DAT.size()>>(
- NgWord1Data::VERSION_DAT, "version.dat"));
+ files.push_back(MakeArrayFile(NgWord1Data::WORD_TXT, "common.txt"));
+ files.push_back(MakeArrayFile(NgWord1Data::VERSION_DAT, "version.dat"));
- return std::make_shared<VectorVfsDirectory>(files, std::vector<VirtualDir>{}, "data");
+ return std::make_shared<VectorVfsDirectory>(std::move(files), std::vector<VirtualDir>{},
+ "data");
}
namespace NgWord2Data {
@@ -55,27 +54,22 @@ constexpr std::array<u8, 0x2C> AC_NX_DATA{
} // namespace NgWord2Data
VirtualDir NgWord2() {
- std::vector<VirtualFile> files(NgWord2Data::NUMBER_AC_NX_FILES * 3);
+ std::vector<VirtualFile> files;
+ files.reserve(NgWord2Data::NUMBER_AC_NX_FILES * 3);
for (std::size_t i = 0; i < NgWord2Data::NUMBER_AC_NX_FILES; ++i) {
- files[3 * i] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
- NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b1_nx", i));
- files[3 * i + 1] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
- NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b2_nx", i));
- files[3 * i + 2] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
- NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_not_b_nx", i));
+ files.push_back(MakeArrayFile(NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b1_nx", i)));
+ files.push_back(MakeArrayFile(NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b2_nx", i)));
+ files.push_back(MakeArrayFile(NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_not_b_nx", i)));
}
- files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
- NgWord2Data::AC_NX_DATA, "ac_common_b1_nx"));
- files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
- NgWord2Data::AC_NX_DATA, "ac_common_b2_nx"));
- files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
- NgWord2Data::AC_NX_DATA, "ac_common_not_b_nx"));
- files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::VERSION_DAT.size()>>(
- NgWord2Data::VERSION_DAT, "version.dat"));
+ files.push_back(MakeArrayFile(NgWord2Data::AC_NX_DATA, "ac_common_b1_nx"));
+ files.push_back(MakeArrayFile(NgWord2Data::AC_NX_DATA, "ac_common_b2_nx"));
+ files.push_back(MakeArrayFile(NgWord2Data::AC_NX_DATA, "ac_common_not_b_nx"));
+ files.push_back(MakeArrayFile(NgWord2Data::VERSION_DAT, "version.dat"));
- return std::make_shared<VectorVfsDirectory>(files, std::vector<VirtualDir>{}, "data");
+ return std::make_shared<VectorVfsDirectory>(std::move(files), std::vector<VirtualDir>{},
+ "data");
}
} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/system_archive/time_zone_binary.cpp b/src/core/file_sys/system_archive/time_zone_binary.cpp
index 9806bd197..8fd005012 100644
--- a/src/core/file_sys/system_archive/time_zone_binary.cpp
+++ b/src/core/file_sys/system_archive/time_zone_binary.cpp
@@ -2,6 +2,9 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
+#include <vector>
+
#include "common/swap.h"
#include "core/file_sys/system_archive/time_zone_binary.h"
#include "core/file_sys/vfs_vector.h"
@@ -615,43 +618,49 @@ static constexpr std::array<u8, 9633> LOCATION_NAMES{
0x0a};
static VirtualFile GenerateDefaultTimeZoneFile() {
- struct {
+ struct TimeZoneInfo {
s64_be at;
- INSERT_PADDING_BYTES(7);
+ std::array<u8, 7> padding1;
std::array<char, 4> time_zone_chars;
- INSERT_PADDING_BYTES(2);
+ std::array<u8, 2> padding2;
std::array<char, 6> time_zone_name;
- } time_zone_info{};
+ };
- const VirtualFile file{std::make_shared<VectorVfsFile>(
- std::vector<u8>(sizeof(Service::Time::TimeZone::TzifHeader) + sizeof(time_zone_info)),
+ VirtualFile file{std::make_shared<VectorVfsFile>(
+ std::vector<u8>(sizeof(Service::Time::TimeZone::TzifHeader) + sizeof(TimeZoneInfo)),
"GMT")};
- Service::Time::TimeZone::TzifHeader header{};
- header.magic = 0x545a6966;
- header.version = 0x32;
- header.ttis_gmt_count = 0x1;
- header.ttis_std_count = 0x1;
- header.time_count = 0x1;
- header.type_count = 0x1;
- header.char_count = 0x4;
+ const Service::Time::TimeZone::TzifHeader header{
+ .magic = 0x545a6966,
+ .version = 0x32,
+ .ttis_gmt_count = 1,
+ .ttis_std_count = 1,
+ .time_count = 1,
+ .type_count = 1,
+ .char_count = 4,
+ };
file->WriteObject(header, 0);
- time_zone_info.at = 0xf8;
- time_zone_info.time_zone_chars = {'G', 'M', 'T', '\0'};
- time_zone_info.time_zone_name = {'\n', 'G', 'M', 'T', '0', '\n'};
+ const TimeZoneInfo time_zone_info{
+ .at = 0xf8,
+ .padding1 = {},
+ .time_zone_chars = {'G', 'M', 'T', '\0'},
+ .padding2 = {},
+ .time_zone_name = {'\n', 'G', 'M', 'T', '0', '\n'},
+ };
file->WriteObject(time_zone_info, sizeof(Service::Time::TimeZone::TzifHeader));
return file;
}
VirtualDir TimeZoneBinary() {
- const std::vector<VirtualDir> root_dirs{std::make_shared<VectorVfsDirectory>(
+ std::vector<VirtualDir> root_dirs{std::make_shared<VectorVfsDirectory>(
std::vector<VirtualFile>{GenerateDefaultTimeZoneFile()}, std::vector<VirtualDir>{},
"zoneinfo")};
- const std::vector<VirtualFile> root_files{
- std::make_shared<ArrayVfsFile<LOCATION_NAMES.size()>>(LOCATION_NAMES, "binaryList.txt")};
- return std::make_shared<VectorVfsDirectory>(root_files, root_dirs, "data");
+ std::vector<VirtualFile> root_files{MakeArrayFile(LOCATION_NAMES, "binaryList.txt")};
+
+ return std::make_shared<VectorVfsDirectory>(std::move(root_files), std::move(root_dirs),
+ "data");
}
} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp
index e33327ef0..a4c3f67c4 100644
--- a/src/core/file_sys/vfs.cpp
+++ b/src/core/file_sys/vfs.cpp
@@ -30,7 +30,7 @@ bool VfsFilesystem::IsWritable() const {
}
VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const {
- const auto path = FileUtil::SanitizePath(path_);
+ const auto path = Common::FS::SanitizePath(path_);
if (root->GetFileRelative(path) != nullptr)
return VfsEntryType::File;
if (root->GetDirectoryRelative(path) != nullptr)
@@ -40,22 +40,22 @@ VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const {
}
VirtualFile VfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
- const auto path = FileUtil::SanitizePath(path_);
+ const auto path = Common::FS::SanitizePath(path_);
return root->GetFileRelative(path);
}
VirtualFile VfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
- const auto path = FileUtil::SanitizePath(path_);
+ const auto path = Common::FS::SanitizePath(path_);
return root->CreateFileRelative(path);
}
VirtualFile VfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) {
- const auto old_path = FileUtil::SanitizePath(old_path_);
- const auto new_path = FileUtil::SanitizePath(new_path_);
+ const auto old_path = Common::FS::SanitizePath(old_path_);
+ const auto new_path = Common::FS::SanitizePath(new_path_);
// VfsDirectory impls are only required to implement copy across the current directory.
- if (FileUtil::GetParentPath(old_path) == FileUtil::GetParentPath(new_path)) {
- if (!root->Copy(FileUtil::GetFilename(old_path), FileUtil::GetFilename(new_path)))
+ if (Common::FS::GetParentPath(old_path) == Common::FS::GetParentPath(new_path)) {
+ if (!root->Copy(Common::FS::GetFilename(old_path), Common::FS::GetFilename(new_path)))
return nullptr;
return OpenFile(new_path, Mode::ReadWrite);
}
@@ -76,8 +76,8 @@ VirtualFile VfsFilesystem::CopyFile(std::string_view old_path_, std::string_view
}
VirtualFile VfsFilesystem::MoveFile(std::string_view old_path, std::string_view new_path) {
- const auto sanitized_old_path = FileUtil::SanitizePath(old_path);
- const auto sanitized_new_path = FileUtil::SanitizePath(new_path);
+ const auto sanitized_old_path = Common::FS::SanitizePath(old_path);
+ const auto sanitized_new_path = Common::FS::SanitizePath(new_path);
// Again, non-default impls are highly encouraged to provide a more optimized version of this.
auto out = CopyFile(sanitized_old_path, sanitized_new_path);
@@ -89,26 +89,26 @@ VirtualFile VfsFilesystem::MoveFile(std::string_view old_path, std::string_view
}
bool VfsFilesystem::DeleteFile(std::string_view path_) {
- const auto path = FileUtil::SanitizePath(path_);
- auto parent = OpenDirectory(FileUtil::GetParentPath(path), Mode::Write);
+ const auto path = Common::FS::SanitizePath(path_);
+ auto parent = OpenDirectory(Common::FS::GetParentPath(path), Mode::Write);
if (parent == nullptr)
return false;
- return parent->DeleteFile(FileUtil::GetFilename(path));
+ return parent->DeleteFile(Common::FS::GetFilename(path));
}
VirtualDir VfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) {
- const auto path = FileUtil::SanitizePath(path_);
+ const auto path = Common::FS::SanitizePath(path_);
return root->GetDirectoryRelative(path);
}
VirtualDir VfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {
- const auto path = FileUtil::SanitizePath(path_);
+ const auto path = Common::FS::SanitizePath(path_);
return root->CreateDirectoryRelative(path);
}
VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_view new_path_) {
- const auto old_path = FileUtil::SanitizePath(old_path_);
- const auto new_path = FileUtil::SanitizePath(new_path_);
+ const auto old_path = Common::FS::SanitizePath(old_path_);
+ const auto new_path = Common::FS::SanitizePath(new_path_);
// Non-default impls are highly encouraged to provide a more optimized version of this.
auto old_dir = OpenDirectory(old_path, Mode::Read);
@@ -139,8 +139,8 @@ VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_
}
VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path, std::string_view new_path) {
- const auto sanitized_old_path = FileUtil::SanitizePath(old_path);
- const auto sanitized_new_path = FileUtil::SanitizePath(new_path);
+ const auto sanitized_old_path = Common::FS::SanitizePath(old_path);
+ const auto sanitized_new_path = Common::FS::SanitizePath(new_path);
// Non-default impls are highly encouraged to provide a more optimized version of this.
auto out = CopyDirectory(sanitized_old_path, sanitized_new_path);
@@ -152,17 +152,17 @@ VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path, std::string_v
}
bool VfsFilesystem::DeleteDirectory(std::string_view path_) {
- const auto path = FileUtil::SanitizePath(path_);
- auto parent = OpenDirectory(FileUtil::GetParentPath(path), Mode::Write);
+ const auto path = Common::FS::SanitizePath(path_);
+ auto parent = OpenDirectory(Common::FS::GetParentPath(path), Mode::Write);
if (parent == nullptr)
return false;
- return parent->DeleteSubdirectoryRecursive(FileUtil::GetFilename(path));
+ return parent->DeleteSubdirectoryRecursive(Common::FS::GetFilename(path));
}
VfsFile::~VfsFile() = default;
std::string VfsFile::GetExtension() const {
- return std::string(FileUtil::GetExtensionFromFilename(GetName()));
+ return std::string(Common::FS::GetExtensionFromFilename(GetName()));
}
VfsDirectory::~VfsDirectory() = default;
@@ -203,7 +203,7 @@ std::string VfsFile::GetFullPath() const {
}
std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(std::string_view path) const {
- auto vec = FileUtil::SplitPathComponents(path);
+ auto vec = Common::FS::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
vec.end());
if (vec.empty()) {
@@ -239,7 +239,7 @@ std::shared_ptr<VfsFile> VfsDirectory::GetFileAbsolute(std::string_view path) co
}
std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryRelative(std::string_view path) const {
- auto vec = FileUtil::SplitPathComponents(path);
+ auto vec = Common::FS::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
vec.end());
if (vec.empty()) {
@@ -301,7 +301,7 @@ std::size_t VfsDirectory::GetSize() const {
}
std::shared_ptr<VfsFile> VfsDirectory::CreateFileRelative(std::string_view path) {
- auto vec = FileUtil::SplitPathComponents(path);
+ auto vec = Common::FS::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
vec.end());
if (vec.empty()) {
@@ -320,7 +320,7 @@ std::shared_ptr<VfsFile> VfsDirectory::CreateFileRelative(std::string_view path)
}
}
- return dir->CreateFileRelative(FileUtil::GetPathWithoutTop(path));
+ return dir->CreateFileRelative(Common::FS::GetPathWithoutTop(path));
}
std::shared_ptr<VfsFile> VfsDirectory::CreateFileAbsolute(std::string_view path) {
@@ -332,7 +332,7 @@ std::shared_ptr<VfsFile> VfsDirectory::CreateFileAbsolute(std::string_view path)
}
std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryRelative(std::string_view path) {
- auto vec = FileUtil::SplitPathComponents(path);
+ auto vec = Common::FS::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
vec.end());
if (vec.empty()) {
@@ -351,7 +351,7 @@ std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryRelative(std::string_
}
}
- return dir->CreateDirectoryRelative(FileUtil::GetPathWithoutTop(path));
+ return dir->CreateDirectoryRelative(Common::FS::GetPathWithoutTop(path));
}
std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
diff --git a/src/core/file_sys/vfs_libzip.cpp b/src/core/file_sys/vfs_libzip.cpp
index d69952940..429d7bc8b 100644
--- a/src/core/file_sys/vfs_libzip.cpp
+++ b/src/core/file_sys/vfs_libzip.cpp
@@ -49,7 +49,7 @@ VirtualDir ExtractZIP(VirtualFile file) {
if (zip_fread(file2.get(), buf.data(), buf.size()) != s64(buf.size()))
return nullptr;
- const auto parts = FileUtil::SplitPathComponents(stat.name);
+ const auto parts = Common::FS::SplitPathComponents(stat.name);
const auto new_file = std::make_shared<VectorVfsFile>(buf, parts.back());
std::shared_ptr<VectorVfsDirectory> dtrv = out;
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp
index 96ce5957c..488687ba9 100644
--- a/src/core/file_sys/vfs_real.cpp
+++ b/src/core/file_sys/vfs_real.cpp
@@ -14,24 +14,28 @@
namespace FileSys {
+namespace FS = Common::FS;
+
static std::string ModeFlagsToString(Mode mode) {
std::string mode_str;
// Calculate the correct open mode for the file.
- if (mode & Mode::Read && mode & Mode::Write) {
- if (mode & Mode::Append)
+ if (True(mode & Mode::Read) && True(mode & Mode::Write)) {
+ if (True(mode & Mode::Append)) {
mode_str = "a+";
- else
+ } else {
mode_str = "r+";
+ }
} else {
- if (mode & Mode::Read)
+ if (True(mode & Mode::Read)) {
mode_str = "r";
- else if (mode & Mode::Append)
+ } else if (True(mode & Mode::Append)) {
mode_str = "a";
- else if (mode & Mode::Write)
+ } else if (True(mode & Mode::Write)) {
mode_str = "w";
- else
+ } else {
UNREACHABLE_MSG("Invalid file open mode: {:02X}", static_cast<u8>(mode));
+ }
}
mode_str += "b";
@@ -55,78 +59,82 @@ bool RealVfsFilesystem::IsWritable() const {
}
VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const {
- const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
- if (!FileUtil::Exists(path))
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+ if (!FS::Exists(path)) {
return VfsEntryType::None;
- if (FileUtil::IsDirectory(path))
+ }
+ if (FS::IsDirectory(path)) {
return VfsEntryType::Directory;
+ }
return VfsEntryType::File;
}
VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
- const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
- if (cache.find(path) != cache.end()) {
- auto weak = cache[path];
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+
+ if (const auto weak_iter = cache.find(path); weak_iter != cache.cend()) {
+ const auto& weak = weak_iter->second;
+
if (!weak.expired()) {
return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, weak.lock(), path, perms));
}
}
- if (!FileUtil::Exists(path) && (perms & Mode::WriteAppend) != 0)
- FileUtil::CreateEmptyFile(path);
+ if (!FS::Exists(path) && True(perms & Mode::WriteAppend)) {
+ FS::CreateEmptyFile(path);
+ }
- auto backing = std::make_shared<FileUtil::IOFile>(path, ModeFlagsToString(perms).c_str());
- cache[path] = backing;
+ auto backing = std::make_shared<FS::IOFile>(path, ModeFlagsToString(perms).c_str());
+ cache.insert_or_assign(path, backing);
// Cannot use make_shared as RealVfsFile constructor is private
return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, backing, path, perms));
}
VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
- const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
- const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash);
- if (!FileUtil::Exists(path)) {
- FileUtil::CreateFullPath(path_fwd);
- if (!FileUtil::CreateEmptyFile(path))
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+ const auto path_fwd = FS::SanitizePath(path, FS::DirectorySeparator::ForwardSlash);
+ if (!FS::Exists(path)) {
+ FS::CreateFullPath(path_fwd);
+ if (!FS::CreateEmptyFile(path)) {
return nullptr;
+ }
}
return OpenFile(path, perms);
}
VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) {
- const auto old_path =
- FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault);
- const auto new_path =
- FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault);
+ const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
+ const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
- if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) ||
- FileUtil::IsDirectory(old_path) || !FileUtil::Copy(old_path, new_path))
+ if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) ||
+ !FS::Copy(old_path, new_path)) {
return nullptr;
+ }
return OpenFile(new_path, Mode::ReadWrite);
}
VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) {
- const auto old_path =
- FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault);
- const auto new_path =
- FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault);
+ const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
+ const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
+ const auto cached_file_iter = cache.find(old_path);
- if (cache.find(old_path) != cache.end()) {
- auto file = cache[old_path].lock();
+ if (cached_file_iter != cache.cend()) {
+ auto file = cached_file_iter->second.lock();
- if (!cache[old_path].expired()) {
+ if (!cached_file_iter->second.expired()) {
file->Close();
}
- if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) ||
- FileUtil::IsDirectory(old_path) || !FileUtil::Rename(old_path, new_path)) {
+ if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) ||
+ !FS::Rename(old_path, new_path)) {
return nullptr;
}
cache.erase(old_path);
file->Open(new_path, "r+b");
- cache[new_path] = file;
+ cache.insert_or_assign(new_path, std::move(file));
} else {
UNREACHABLE();
return nullptr;
@@ -136,28 +144,33 @@ VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_
}
bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
- const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
- if (cache.find(path) != cache.end()) {
- if (!cache[path].expired())
- cache[path].lock()->Close();
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+ const auto cached_iter = cache.find(path);
+
+ if (cached_iter != cache.cend()) {
+ if (!cached_iter->second.expired()) {
+ cached_iter->second.lock()->Close();
+ }
cache.erase(path);
}
- return FileUtil::Delete(path);
+
+ return FS::Delete(path);
}
VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) {
- const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
// Cannot use make_shared as RealVfsDirectory constructor is private
return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
}
VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {
- const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
- const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash);
- if (!FileUtil::Exists(path)) {
- FileUtil::CreateFullPath(path_fwd);
- if (!FileUtil::CreateDir(path))
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+ const auto path_fwd = FS::SanitizePath(path, FS::DirectorySeparator::ForwardSlash);
+ if (!FS::Exists(path)) {
+ FS::CreateFullPath(path_fwd);
+ if (!FS::CreateDir(path)) {
return nullptr;
+ }
}
// Cannot use make_shared as RealVfsDirectory constructor is private
return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
@@ -165,67 +178,75 @@ VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms
VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_,
std::string_view new_path_) {
- const auto old_path =
- FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault);
- const auto new_path =
- FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault);
- if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) ||
- !FileUtil::IsDirectory(old_path))
+ const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
+ const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
+ if (!FS::Exists(old_path) || FS::Exists(new_path) || !FS::IsDirectory(old_path)) {
return nullptr;
- FileUtil::CopyDir(old_path, new_path);
+ }
+ FS::CopyDir(old_path, new_path);
return OpenDirectory(new_path, Mode::ReadWrite);
}
VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_,
std::string_view new_path_) {
- const auto old_path =
- FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault);
- const auto new_path =
- FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault);
- if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) ||
- FileUtil::IsDirectory(old_path) || !FileUtil::Rename(old_path, new_path))
+ const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
+ const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
+
+ if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) ||
+ !FS::Rename(old_path, new_path)) {
return nullptr;
+ }
for (auto& kv : cache) {
- // Path in cache starts with old_path
- if (kv.first.rfind(old_path, 0) == 0) {
- const auto file_old_path =
- FileUtil::SanitizePath(kv.first, FileUtil::DirectorySeparator::PlatformDefault);
- const auto file_new_path =
- FileUtil::SanitizePath(new_path + DIR_SEP + kv.first.substr(old_path.size()),
- FileUtil::DirectorySeparator::PlatformDefault);
- auto cached = cache[file_old_path];
- if (!cached.expired()) {
- auto file = cached.lock();
- file->Open(file_new_path, "r+b");
- cache.erase(file_old_path);
- cache[file_new_path] = file;
- }
+ // If the path in the cache doesn't start with old_path, then bail on this file.
+ if (kv.first.rfind(old_path, 0) != 0) {
+ continue;
+ }
+
+ const auto file_old_path =
+ FS::SanitizePath(kv.first, FS::DirectorySeparator::PlatformDefault);
+ auto file_new_path = FS::SanitizePath(new_path + DIR_SEP + kv.first.substr(old_path.size()),
+ FS::DirectorySeparator::PlatformDefault);
+ const auto& cached = cache[file_old_path];
+
+ if (cached.expired()) {
+ continue;
}
+
+ auto file = cached.lock();
+ file->Open(file_new_path, "r+b");
+ cache.erase(file_old_path);
+ cache.insert_or_assign(std::move(file_new_path), std::move(file));
}
return OpenDirectory(new_path, Mode::ReadWrite);
}
bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) {
- const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+
for (auto& kv : cache) {
- // Path in cache starts with old_path
- if (kv.first.rfind(path, 0) == 0) {
- if (!cache[kv.first].expired())
- cache[kv.first].lock()->Close();
- cache.erase(kv.first);
+ // If the path in the cache doesn't start with path, then bail on this file.
+ if (kv.first.rfind(path, 0) != 0) {
+ continue;
+ }
+
+ const auto& entry = cache[kv.first];
+ if (!entry.expired()) {
+ entry.lock()->Close();
}
+
+ cache.erase(kv.first);
}
- return FileUtil::DeleteDirRecursively(path);
+
+ return FS::DeleteDirRecursively(path);
}
-RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FileUtil::IOFile> backing_,
+RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FS::IOFile> backing_,
const std::string& path_, Mode perms_)
- : base(base_), backing(std::move(backing_)), path(path_),
- parent_path(FileUtil::GetParentPath(path_)),
- path_components(FileUtil::SplitPathComponents(path_)),
- parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)),
+ : base(base_), backing(std::move(backing_)), path(path_), parent_path(FS::GetParentPath(path_)),
+ path_components(FS::SplitPathComponents(path_)),
+ parent_components(FS::SliceVector(path_components, 0, path_components.size() - 1)),
perms(perms_) {}
RealVfsFile::~RealVfsFile() = default;
@@ -247,22 +268,24 @@ std::shared_ptr<VfsDirectory> RealVfsFile::GetContainingDirectory() const {
}
bool RealVfsFile::IsWritable() const {
- return (perms & Mode::WriteAppend) != 0;
+ return True(perms & Mode::WriteAppend);
}
bool RealVfsFile::IsReadable() const {
- return (perms & Mode::ReadWrite) != 0;
+ return True(perms & Mode::ReadWrite);
}
std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
- if (!backing->Seek(offset, SEEK_SET))
+ if (!backing->Seek(static_cast<s64>(offset), SEEK_SET)) {
return 0;
+ }
return backing->ReadBytes(data, length);
}
std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
- if (!backing->Seek(offset, SEEK_SET))
+ if (!backing->Seek(static_cast<s64>(offset), SEEK_SET)) {
return 0;
+ }
return backing->WriteBytes(data, length);
}
@@ -279,16 +302,18 @@ bool RealVfsFile::Close() {
template <>
std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>() const {
- if (perms == Mode::Append)
+ if (perms == Mode::Append) {
return {};
+ }
std::vector<VirtualFile> out;
- FileUtil::ForeachDirectoryEntry(
+ FS::ForeachDirectoryEntry(
nullptr, path,
[&out, this](u64* entries_out, const std::string& directory, const std::string& filename) {
const std::string full_path = directory + DIR_SEP + filename;
- if (!FileUtil::IsDirectory(full_path))
+ if (!FS::IsDirectory(full_path)) {
out.emplace_back(base.OpenFile(full_path, perms));
+ }
return true;
});
@@ -297,16 +322,18 @@ std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>(
template <>
std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDirectory>() const {
- if (perms == Mode::Append)
+ if (perms == Mode::Append) {
return {};
+ }
std::vector<VirtualDir> out;
- FileUtil::ForeachDirectoryEntry(
+ FS::ForeachDirectoryEntry(
nullptr, path,
[&out, this](u64* entries_out, const std::string& directory, const std::string& filename) {
const std::string full_path = directory + DIR_SEP + filename;
- if (FileUtil::IsDirectory(full_path))
+ if (FS::IsDirectory(full_path)) {
out.emplace_back(base.OpenDirectory(full_path, perms));
+ }
return true;
});
@@ -314,28 +341,30 @@ std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDi
}
RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_, Mode perms_)
- : base(base_), path(FileUtil::RemoveTrailingSlash(path_)),
- parent_path(FileUtil::GetParentPath(path)),
- path_components(FileUtil::SplitPathComponents(path)),
- parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)),
+ : base(base_), path(FS::RemoveTrailingSlash(path_)), parent_path(FS::GetParentPath(path)),
+ path_components(FS::SplitPathComponents(path)),
+ parent_components(FS::SliceVector(path_components, 0, path_components.size() - 1)),
perms(perms_) {
- if (!FileUtil::Exists(path) && perms & Mode::WriteAppend)
- FileUtil::CreateDir(path);
+ if (!FS::Exists(path) && True(perms & Mode::WriteAppend)) {
+ FS::CreateDir(path);
+ }
}
RealVfsDirectory::~RealVfsDirectory() = default;
std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const {
- const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
- if (!FileUtil::Exists(full_path) || FileUtil::IsDirectory(full_path))
+ const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
+ if (!FS::Exists(full_path) || FS::IsDirectory(full_path)) {
return nullptr;
+ }
return base.OpenFile(full_path, perms);
}
std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string_view path) const {
- const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
- if (!FileUtil::Exists(full_path) || !FileUtil::IsDirectory(full_path))
+ const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
+ if (!FS::Exists(full_path) || !FS::IsDirectory(full_path)) {
return nullptr;
+ }
return base.OpenDirectory(full_path, perms);
}
@@ -348,17 +377,17 @@ std::shared_ptr<VfsDirectory> RealVfsDirectory::GetSubdirectory(std::string_view
}
std::shared_ptr<VfsFile> RealVfsDirectory::CreateFileRelative(std::string_view path) {
- const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
+ const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
return base.CreateFile(full_path, perms);
}
std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateDirectoryRelative(std::string_view path) {
- const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
+ const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
return base.CreateDirectory(full_path, perms);
}
bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
- auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(name));
+ const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(name));
return base.DeleteDirectory(full_path);
}
@@ -371,11 +400,11 @@ std::vector<std::shared_ptr<VfsDirectory>> RealVfsDirectory::GetSubdirectories()
}
bool RealVfsDirectory::IsWritable() const {
- return (perms & Mode::WriteAppend) != 0;
+ return True(perms & Mode::WriteAppend);
}
bool RealVfsDirectory::IsReadable() const {
- return (perms & Mode::ReadWrite) != 0;
+ return True(perms & Mode::ReadWrite);
}
std::string RealVfsDirectory::GetName() const {
@@ -383,8 +412,9 @@ std::string RealVfsDirectory::GetName() const {
}
std::shared_ptr<VfsDirectory> RealVfsDirectory::GetParentDirectory() const {
- if (path_components.size() <= 1)
+ if (path_components.size() <= 1) {
return nullptr;
+ }
return base.OpenDirectory(parent_path, perms);
}
@@ -421,16 +451,17 @@ std::string RealVfsDirectory::GetFullPath() const {
}
std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries() const {
- if (perms == Mode::Append)
+ if (perms == Mode::Append) {
return {};
+ }
std::map<std::string, VfsEntryType, std::less<>> out;
- FileUtil::ForeachDirectoryEntry(
+ FS::ForeachDirectoryEntry(
nullptr, path,
[&out](u64* entries_out, const std::string& directory, const std::string& filename) {
const std::string full_path = directory + DIR_SEP + filename;
- out.emplace(filename, FileUtil::IsDirectory(full_path) ? VfsEntryType::Directory
- : VfsEntryType::File);
+ out.emplace(filename,
+ FS::IsDirectory(full_path) ? VfsEntryType::Directory : VfsEntryType::File);
return true;
});
diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h
index a0a857a31..0b537b22c 100644
--- a/src/core/file_sys/vfs_real.h
+++ b/src/core/file_sys/vfs_real.h
@@ -9,7 +9,7 @@
#include "core/file_sys/mode.h"
#include "core/file_sys/vfs.h"
-namespace FileUtil {
+namespace Common::FS {
class IOFile;
}
@@ -36,7 +36,7 @@ public:
bool DeleteDirectory(std::string_view path) override;
private:
- boost::container::flat_map<std::string, std::weak_ptr<FileUtil::IOFile>> cache;
+ boost::container::flat_map<std::string, std::weak_ptr<Common::FS::IOFile>> cache;
};
// An implmentation of VfsFile that represents a file on the user's computer.
@@ -58,13 +58,13 @@ public:
bool Rename(std::string_view name) override;
private:
- RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<FileUtil::IOFile> backing,
+ RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<Common::FS::IOFile> backing,
const std::string& path, Mode perms = Mode::Read);
bool Close();
RealVfsFilesystem& base;
- std::shared_ptr<FileUtil::IOFile> backing;
+ std::shared_ptr<Common::FS::IOFile> backing;
std::string path;
std::string parent_path;
std::vector<std::string> path_components;
diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h
index ac36cb2ee..95d3da2f2 100644
--- a/src/core/file_sys/vfs_vector.h
+++ b/src/core/file_sys/vfs_vector.h
@@ -4,7 +4,11 @@
#pragma once
+#include <array>
#include <cstring>
+#include <memory>
+#include <string>
+#include <vector>
#include "core/file_sys/vfs.h"
namespace FileSys {
@@ -13,7 +17,8 @@ namespace FileSys {
template <std::size_t size>
class ArrayVfsFile : public VfsFile {
public:
- ArrayVfsFile(std::array<u8, size> data, std::string name = "", VirtualDir parent = nullptr)
+ explicit ArrayVfsFile(const std::array<u8, size>& data, std::string name = "",
+ VirtualDir parent = nullptr)
: data(data), name(std::move(name)), parent(std::move(parent)) {}
std::string GetName() const override {
@@ -61,6 +66,12 @@ private:
VirtualDir parent;
};
+template <std::size_t Size, typename... Args>
+std::shared_ptr<ArrayVfsFile<Size>> MakeArrayFile(const std::array<u8, Size>& data,
+ Args&&... args) {
+ return std::make_shared<ArrayVfsFile<Size>>(data, std::forward<Args>(args)...);
+}
+
// An implementation of VfsFile that is backed by a vector optionally supplied upon construction
class VectorVfsFile : public VfsFile {
public:
diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp
index 86e06ccb9..24c58e7ae 100644
--- a/src/core/file_sys/xts_archive.cpp
+++ b/src/core/file_sys/xts_archive.cpp
@@ -15,8 +15,9 @@
#include "common/hex_util.h"
#include "common/string_util.h"
#include "core/crypto/aes_util.h"
+#include "core/crypto/key_manager.h"
#include "core/crypto/xts_encryption_layer.h"
-#include "core/file_sys/partition_filesystem.h"
+#include "core/file_sys/content_archive.h"
#include "core/file_sys/vfs_offset.h"
#include "core/file_sys/xts_archive.h"
#include "core/loader/loader.h"
@@ -43,8 +44,10 @@ static bool CalculateHMAC256(Destination* out, const SourceKey* key, std::size_t
return true;
}
-NAX::NAX(VirtualFile file_) : header(std::make_unique<NAXHeader>()), file(std::move(file_)) {
- std::string path = FileUtil::SanitizePath(file->GetFullPath());
+NAX::NAX(VirtualFile file_)
+ : header(std::make_unique<NAXHeader>()),
+ file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
+ std::string path = Common::FS::SanitizePath(file->GetFullPath());
static const std::regex nax_path_regex("/registered/(000000[0-9A-F]{2})/([0-9A-F]{32})\\.nca",
std::regex_constants::ECMAScript |
std::regex_constants::icase);
@@ -60,7 +63,8 @@ NAX::NAX(VirtualFile file_) : header(std::make_unique<NAXHeader>()), file(std::m
}
NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id)
- : header(std::make_unique<NAXHeader>()), file(std::move(file_)) {
+ : header(std::make_unique<NAXHeader>()),
+ file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
Core::Crypto::SHA256Hash hash{};
mbedtls_sha256_ret(nca_id.data(), nca_id.size(), hash.data(), 0);
status = Parse(fmt::format("/registered/000000{:02X}/{}.nca", hash[0],
@@ -70,14 +74,18 @@ NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id)
NAX::~NAX() = default;
Loader::ResultStatus NAX::Parse(std::string_view path) {
- if (file->ReadObject(header.get()) != sizeof(NAXHeader))
+ if (file == nullptr) {
+ return Loader::ResultStatus::ErrorNullFile;
+ }
+ if (file->ReadObject(header.get()) != sizeof(NAXHeader)) {
return Loader::ResultStatus::ErrorBadNAXHeader;
-
- if (header->magic != Common::MakeMagic('N', 'A', 'X', '0'))
+ }
+ if (header->magic != Common::MakeMagic('N', 'A', 'X', '0')) {
return Loader::ResultStatus::ErrorBadNAXHeader;
-
- if (file->GetSize() < NAX_HEADER_PADDING_SIZE + header->file_size)
+ }
+ if (file->GetSize() < NAX_HEADER_PADDING_SIZE + header->file_size) {
return Loader::ResultStatus::ErrorIncorrectNAXFileSize;
+ }
keys.DeriveSDSeedLazy();
std::array<Core::Crypto::Key256, 2> sd_keys{};
diff --git a/src/core/file_sys/xts_archive.h b/src/core/file_sys/xts_archive.h
index 563531bb6..c472e226e 100644
--- a/src/core/file_sys/xts_archive.h
+++ b/src/core/file_sys/xts_archive.h
@@ -9,12 +9,16 @@
#include "common/common_types.h"
#include "common/swap.h"
#include "core/crypto/key_manager.h"
-#include "core/file_sys/content_archive.h"
#include "core/file_sys/vfs.h"
-#include "core/loader/loader.h"
+
+namespace Loader {
+enum class ResultStatus : u16;
+}
namespace FileSys {
+class NCA;
+
struct NAXHeader {
std::array<u8, 0x20> hmac;
u64_le magic;
@@ -62,6 +66,6 @@ private:
VirtualFile dec_file;
- Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
+ Core::Crypto::KeyManager& keys;
};
} // namespace FileSys
diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp
new file mode 100644
index 000000000..4505da758
--- /dev/null
+++ b/src/core/frontend/applets/controller.cpp
@@ -0,0 +1,81 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/frontend/applets/controller.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/sm/sm.h"
+
+namespace Core::Frontend {
+
+ControllerApplet::~ControllerApplet() = default;
+
+DefaultControllerApplet::~DefaultControllerApplet() = default;
+
+void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callback,
+ ControllerParameters parameters) const {
+ LOG_INFO(Service_HID, "called, deducing the best configuration based on the given parameters!");
+
+ auto& npad =
+ Core::System::GetInstance()
+ .ServiceManager()
+ .GetService<Service::HID::Hid>("hid")
+ ->GetAppletResource()
+ ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
+
+ auto& players = Settings::values.players;
+
+ const std::size_t min_supported_players =
+ parameters.enable_single_mode ? 1 : parameters.min_players;
+
+ // Disconnect Handheld first.
+ npad.DisconnectNPadAtIndex(8);
+
+ // Deduce the best configuration based on the input parameters.
+ for (std::size_t index = 0; index < players.size() - 2; ++index) {
+ // First, disconnect all controllers regardless of the value of keep_controllers_connected.
+ // This makes it easy to connect the desired controllers.
+ npad.DisconnectNPadAtIndex(index);
+
+ // Only connect the minimum number of required players.
+ if (index >= min_supported_players) {
+ continue;
+ }
+
+ // Connect controllers based on the following priority list from highest to lowest priority:
+ // Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld
+ if (parameters.allow_pro_controller) {
+ npad.AddNewControllerAt(
+ npad.MapSettingsTypeToNPad(Settings::ControllerType::ProController), index);
+ } else if (parameters.allow_dual_joycons) {
+ npad.AddNewControllerAt(
+ npad.MapSettingsTypeToNPad(Settings::ControllerType::DualJoyconDetached), index);
+ } else if (parameters.allow_left_joycon && parameters.allow_right_joycon) {
+ // Assign left joycons to even player indices and right joycons to odd player indices.
+ // We do this since Captain Toad Treasure Tracker expects a left joycon for Player 1 and
+ // a right Joycon for Player 2 in 2 Player Assist mode.
+ if (index % 2 == 0) {
+ npad.AddNewControllerAt(
+ npad.MapSettingsTypeToNPad(Settings::ControllerType::LeftJoycon), index);
+ } else {
+ npad.AddNewControllerAt(
+ npad.MapSettingsTypeToNPad(Settings::ControllerType::RightJoycon), index);
+ }
+ } else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld &&
+ !Settings::values.use_docked_mode) {
+ // We should *never* reach here under any normal circumstances.
+ npad.AddNewControllerAt(npad.MapSettingsTypeToNPad(Settings::ControllerType::Handheld),
+ index);
+ } else {
+ UNREACHABLE_MSG("Unable to add a new controller based on the given parameters!");
+ }
+ }
+
+ callback();
+}
+
+} // namespace Core::Frontend
diff --git a/src/core/frontend/applets/controller.h b/src/core/frontend/applets/controller.h
new file mode 100644
index 000000000..a227f15cd
--- /dev/null
+++ b/src/core/frontend/applets/controller.h
@@ -0,0 +1,48 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+
+#include "common/common_types.h"
+
+namespace Core::Frontend {
+
+using BorderColor = std::array<u8, 4>;
+using ExplainText = std::array<char, 0x81>;
+
+struct ControllerParameters {
+ s8 min_players{};
+ s8 max_players{};
+ bool keep_controllers_connected{};
+ bool enable_single_mode{};
+ bool enable_border_color{};
+ std::vector<BorderColor> border_colors{};
+ bool enable_explain_text{};
+ std::vector<ExplainText> explain_text{};
+ bool allow_pro_controller{};
+ bool allow_handheld{};
+ bool allow_dual_joycons{};
+ bool allow_left_joycon{};
+ bool allow_right_joycon{};
+};
+
+class ControllerApplet {
+public:
+ virtual ~ControllerApplet();
+
+ virtual void ReconfigureControllers(std::function<void()> callback,
+ ControllerParameters parameters) const = 0;
+};
+
+class DefaultControllerApplet final : public ControllerApplet {
+public:
+ ~DefaultControllerApplet() override;
+
+ void ReconfigureControllers(std::function<void()> callback,
+ ControllerParameters parameters) const override;
+};
+
+} // namespace Core::Frontend
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 13aa14934..3e8780243 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -39,7 +39,7 @@ public:
class Scoped {
public:
- explicit Scoped(GraphicsContext& context_) : context(context_) {
+ [[nodiscard]] explicit Scoped(GraphicsContext& context_) : context(context_) {
context.MakeCurrent();
}
~Scoped() {
@@ -52,7 +52,7 @@ public:
/// Calls MakeCurrent on the context and calls DoneCurrent when the scope for the returned value
/// ends
- Scoped Acquire() {
+ [[nodiscard]] Scoped Acquire() {
return Scoped{*this};
}
};
diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h
index 91ecc30ab..e2e3bbbb3 100644
--- a/src/core/frontend/framebuffer_layout.h
+++ b/src/core/frontend/framebuffer_layout.h
@@ -4,6 +4,7 @@
#pragma once
+#include "common/common_types.h"
#include "common/math_util.h"
namespace Layout {
diff --git a/src/core/hardware_interrupt_manager.cpp b/src/core/hardware_interrupt_manager.cpp
index efc1030c1..645f26e91 100644
--- a/src/core/hardware_interrupt_manager.cpp
+++ b/src/core/hardware_interrupt_manager.cpp
@@ -11,8 +11,8 @@
namespace Core::Hardware {
InterruptManager::InterruptManager(Core::System& system_in) : system(system_in) {
- gpu_interrupt_event =
- Core::Timing::CreateEvent("GPUInterrupt", [this](u64 message, std::chrono::nanoseconds) {
+ gpu_interrupt_event = Core::Timing::CreateEvent(
+ "GPUInterrupt", [this](std::uintptr_t message, std::chrono::nanoseconds) {
auto nvdrv = system.ServiceManager().GetService<Service::Nvidia::NVDRV>("nvdrv");
const u32 syncpt = static_cast<u32>(message >> 32);
const u32 value = static_cast<u32>(message);
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h
index 0dc6a4a43..1b503331f 100644
--- a/src/core/hle/ipc_helpers.h
+++ b/src/core/hle/ipc_helpers.h
@@ -229,6 +229,8 @@ inline void ResponseBuilder::Push(u32 value) {
template <typename T>
void ResponseBuilder::PushRaw(const T& value) {
+ static_assert(std::is_trivially_copyable_v<T>,
+ "It's undefined behavior to use memcpy with non-trivially copyable objects");
std::memcpy(cmdbuf + index, &value, sizeof(T));
index += (sizeof(T) + 3) / 4; // round up to word length
}
@@ -384,6 +386,8 @@ inline s32 RequestParser::Pop() {
template <typename T>
void RequestParser::PopRaw(T& value) {
+ static_assert(std::is_trivially_copyable_v<T>,
+ "It's undefined behavior to use memcpy with non-trivially copyable objects");
std::memcpy(&value, cmdbuf + index, sizeof(T));
index += (sizeof(T) + 3) / 4; // round up to word length
}
diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp
index df0debe1b..b882eaa0f 100644
--- a/src/core/hle/kernel/address_arbiter.cpp
+++ b/src/core/hle/kernel/address_arbiter.cpp
@@ -81,7 +81,7 @@ ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32
do {
current_value = monitor.ExclusiveRead32(current_core, address);
- if (current_value != value) {
+ if (current_value != static_cast<u32>(value)) {
return ERR_INVALID_STATE;
}
current_value++;
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index 9277b5d08..81f85643b 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -293,13 +293,15 @@ std::vector<u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) const {
BufferDescriptorA()[buffer_index].Size()};
if (is_buffer_a) {
- ASSERT_OR_EXECUTE_MSG(BufferDescriptorA().size() > buffer_index, { return buffer; },
- "BufferDescriptorA invalid buffer_index {}", buffer_index);
+ ASSERT_OR_EXECUTE_MSG(
+ BufferDescriptorA().size() > buffer_index, { return buffer; },
+ "BufferDescriptorA invalid buffer_index {}", buffer_index);
buffer.resize(BufferDescriptorA()[buffer_index].Size());
memory.ReadBlock(BufferDescriptorA()[buffer_index].Address(), buffer.data(), buffer.size());
} else {
- ASSERT_OR_EXECUTE_MSG(BufferDescriptorX().size() > buffer_index, { return buffer; },
- "BufferDescriptorX invalid buffer_index {}", buffer_index);
+ ASSERT_OR_EXECUTE_MSG(
+ BufferDescriptorX().size() > buffer_index, { return buffer; },
+ "BufferDescriptorX invalid buffer_index {}", buffer_index);
buffer.resize(BufferDescriptorX()[buffer_index].Size());
memory.ReadBlock(BufferDescriptorX()[buffer_index].Address(), buffer.data(), buffer.size());
}
@@ -324,16 +326,16 @@ std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size,
}
if (is_buffer_b) {
- ASSERT_OR_EXECUTE_MSG(BufferDescriptorB().size() > buffer_index &&
- BufferDescriptorB()[buffer_index].Size() >= size,
- { return 0; }, "BufferDescriptorB is invalid, index={}, size={}",
- buffer_index, size);
+ ASSERT_OR_EXECUTE_MSG(
+ BufferDescriptorB().size() > buffer_index &&
+ BufferDescriptorB()[buffer_index].Size() >= size,
+ { return 0; }, "BufferDescriptorB is invalid, index={}, size={}", buffer_index, size);
memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size);
} else {
- ASSERT_OR_EXECUTE_MSG(BufferDescriptorC().size() > buffer_index &&
- BufferDescriptorC()[buffer_index].Size() >= size,
- { return 0; }, "BufferDescriptorC is invalid, index={}, size={}",
- buffer_index, size);
+ ASSERT_OR_EXECUTE_MSG(
+ BufferDescriptorC().size() > buffer_index &&
+ BufferDescriptorC()[buffer_index].Size() >= size,
+ { return 0; }, "BufferDescriptorC is invalid, index={}, size={}", buffer_index, size);
memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size);
}
@@ -344,12 +346,14 @@ std::size_t HLERequestContext::GetReadBufferSize(std::size_t buffer_index) const
const bool is_buffer_a{BufferDescriptorA().size() > buffer_index &&
BufferDescriptorA()[buffer_index].Size()};
if (is_buffer_a) {
- ASSERT_OR_EXECUTE_MSG(BufferDescriptorA().size() > buffer_index, { return 0; },
- "BufferDescriptorA invalid buffer_index {}", buffer_index);
+ ASSERT_OR_EXECUTE_MSG(
+ BufferDescriptorA().size() > buffer_index, { return 0; },
+ "BufferDescriptorA invalid buffer_index {}", buffer_index);
return BufferDescriptorA()[buffer_index].Size();
} else {
- ASSERT_OR_EXECUTE_MSG(BufferDescriptorX().size() > buffer_index, { return 0; },
- "BufferDescriptorX invalid buffer_index {}", buffer_index);
+ ASSERT_OR_EXECUTE_MSG(
+ BufferDescriptorX().size() > buffer_index, { return 0; },
+ "BufferDescriptorX invalid buffer_index {}", buffer_index);
return BufferDescriptorX()[buffer_index].Size();
}
}
@@ -358,12 +362,14 @@ std::size_t HLERequestContext::GetWriteBufferSize(std::size_t buffer_index) cons
const bool is_buffer_b{BufferDescriptorB().size() > buffer_index &&
BufferDescriptorB()[buffer_index].Size()};
if (is_buffer_b) {
- ASSERT_OR_EXECUTE_MSG(BufferDescriptorB().size() > buffer_index, { return 0; },
- "BufferDescriptorB invalid buffer_index {}", buffer_index);
+ ASSERT_OR_EXECUTE_MSG(
+ BufferDescriptorB().size() > buffer_index, { return 0; },
+ "BufferDescriptorB invalid buffer_index {}", buffer_index);
return BufferDescriptorB()[buffer_index].Size();
} else {
- ASSERT_OR_EXECUTE_MSG(BufferDescriptorC().size() > buffer_index, { return 0; },
- "BufferDescriptorC invalid buffer_index {}", buffer_index);
+ ASSERT_OR_EXECUTE_MSG(
+ BufferDescriptorC().size() > buffer_index, { return 0; },
+ "BufferDescriptorC invalid buffer_index {}", buffer_index);
return BufferDescriptorC()[buffer_index].Size();
}
return 0;
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index b31673928..f3277b766 100644
--- a/src/core/hle/kernel/hle_ipc.h
+++ b/src/core/hle/kernel/hle_ipc.h
@@ -13,6 +13,7 @@
#include <vector>
#include <boost/container/small_vector.hpp>
#include "common/common_types.h"
+#include "common/concepts.h"
#include "common/swap.h"
#include "core/hle/ipc.h"
#include "core/hle/kernel/object.h"
@@ -193,23 +194,24 @@ public:
/* Helper function to write a buffer using the appropriate buffer descriptor
*
- * @tparam ContiguousContainer an arbitrary container that satisfies the
- * ContiguousContainer concept in the C++ standard library.
+ * @tparam T an arbitrary container that satisfies the
+ * ContiguousContainer concept in the C++ standard library or a trivially copyable type.
*
- * @param container The container to write the data of into a buffer.
+ * @param data The container/data to write into a buffer.
* @param buffer_index The buffer in particular to write to.
*/
- template <typename ContiguousContainer,
- typename = std::enable_if_t<!std::is_pointer_v<ContiguousContainer>>>
- std::size_t WriteBuffer(const ContiguousContainer& container,
- std::size_t buffer_index = 0) const {
- using ContiguousType = typename ContiguousContainer::value_type;
-
- static_assert(std::is_trivially_copyable_v<ContiguousType>,
- "Container to WriteBuffer must contain trivially copyable objects");
-
- return WriteBuffer(std::data(container), std::size(container) * sizeof(ContiguousType),
- buffer_index);
+ template <typename T, typename = std::enable_if_t<!std::is_pointer_v<T>>>
+ std::size_t WriteBuffer(const T& data, std::size_t buffer_index = 0) const {
+ if constexpr (Common::IsSTLContainer<T>) {
+ using ContiguousType = typename T::value_type;
+ static_assert(std::is_trivially_copyable_v<ContiguousType>,
+ "Container to WriteBuffer must contain trivially copyable objects");
+ return WriteBuffer(std::data(data), std::size(data) * sizeof(ContiguousType),
+ buffer_index);
+ } else {
+ static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
+ return WriteBuffer(&data, sizeof(T), buffer_index);
+ }
}
/// Helper function to get the size of the input buffer
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 8dd4a2637..f2b0fe2fd 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -145,7 +145,7 @@ struct KernelCore::Impl {
void InitializePreemption(KernelCore& kernel) {
preemption_event = Core::Timing::CreateEvent(
- "PreemptionCallback", [this, &kernel](u64, std::chrono::nanoseconds) {
+ "PreemptionCallback", [this, &kernel](std::uintptr_t, std::chrono::nanoseconds) {
{
SchedulerLock lock(kernel);
global_scheduler.PreemptThreads();
@@ -219,6 +219,7 @@ struct KernelCore::Impl {
return static_cast<u32>(system.GetCpuManager().CurrentCore());
}
}
+ std::unique_lock lock{register_thread_mutex};
const auto it = host_thread_ids.find(this_id);
if (it == host_thread_ids.end()) {
return Core::INVALID_HOST_THREAD_ID;
@@ -324,7 +325,7 @@ struct KernelCore::Impl {
std::unordered_map<std::thread::id, u32> host_thread_ids;
u32 registered_thread_ids{Core::Hardware::NUM_CPU_CORES};
std::bitset<Core::Hardware::NUM_CPU_CORES> registered_core_threads;
- std::mutex register_thread_mutex;
+ mutable std::mutex register_thread_mutex;
// Kernel memory management
std::unique_ptr<Memory::MemoryManager> memory_manager;
diff --git a/src/core/hle/kernel/memory/page_table.cpp b/src/core/hle/kernel/memory/page_table.cpp
index 5d6aac00f..a3fadb533 100644
--- a/src/core/hle/kernel/memory/page_table.cpp
+++ b/src/core/hle/kernel/memory/page_table.cpp
@@ -604,7 +604,6 @@ ResultCode PageTable::MapPages(VAddr addr, const PageLinkedList& page_linked_lis
if (const auto result{
Operate(cur_addr, node.GetNumPages(), perm, OperationType::Map, node.GetAddress())};
result.IsError()) {
- const MemoryInfo info{block_manager->FindBlock(cur_addr).GetMemoryInfo()};
const std::size_t num_pages{(addr - cur_addr) / PageSize};
ASSERT(
@@ -852,11 +851,12 @@ ResultCode PageTable::LockForDeviceAddressSpace(VAddr addr, std::size_t size) {
return result;
}
- block_manager->UpdateLock(addr, size / PageSize,
- [](MemoryBlockManager::iterator block, MemoryPermission perm) {
- block->ShareToDevice(perm);
- },
- perm);
+ block_manager->UpdateLock(
+ addr, size / PageSize,
+ [](MemoryBlockManager::iterator block, MemoryPermission perm) {
+ block->ShareToDevice(perm);
+ },
+ perm);
return RESULT_SUCCESS;
}
@@ -874,11 +874,12 @@ ResultCode PageTable::UnlockForDeviceAddressSpace(VAddr addr, std::size_t size)
return result;
}
- block_manager->UpdateLock(addr, size / PageSize,
- [](MemoryBlockManager::iterator block, MemoryPermission perm) {
- block->UnshareToDevice(perm);
- },
- perm);
+ block_manager->UpdateLock(
+ addr, size / PageSize,
+ [](MemoryBlockManager::iterator block, MemoryPermission perm) {
+ block->UnshareToDevice(perm);
+ },
+ perm);
return RESULT_SUCCESS;
}
diff --git a/src/core/hle/kernel/memory/system_control.cpp b/src/core/hle/kernel/memory/system_control.cpp
index 2f98e9c4c..11d204bc2 100644
--- a/src/core/hle/kernel/memory/system_control.cpp
+++ b/src/core/hle/kernel/memory/system_control.cpp
@@ -7,22 +7,15 @@
#include "core/hle/kernel/memory/system_control.h"
namespace Kernel::Memory::SystemControl {
-
-u64 GenerateRandomU64ForInit() {
- static std::random_device device;
- static std::mt19937 gen(device());
- static std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max());
- return distribution(gen);
-}
-
+namespace {
template <typename F>
u64 GenerateUniformRange(u64 min, u64 max, F f) {
- /* Handle the case where the difference is too large to represent. */
+ // Handle the case where the difference is too large to represent.
if (max == std::numeric_limits<u64>::max() && min == std::numeric_limits<u64>::min()) {
return f();
}
- /* Iterate until we get a value in range. */
+ // Iterate until we get a value in range.
const u64 range_size = ((max + 1) - min);
const u64 effective_max = (std::numeric_limits<u64>::max() / range_size) * range_size;
while (true) {
@@ -32,6 +25,14 @@ u64 GenerateUniformRange(u64 min, u64 max, F f) {
}
}
+u64 GenerateRandomU64ForInit() {
+ static std::random_device device;
+ static std::mt19937 gen(device());
+ static std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max());
+ return distribution(gen);
+}
+} // Anonymous namespace
+
u64 GenerateRandomRange(u64 min, u64 max) {
return GenerateUniformRange(min, max, GenerateRandomU64ForInit);
}
diff --git a/src/core/hle/kernel/memory/system_control.h b/src/core/hle/kernel/memory/system_control.h
index 3fa93111d..19cab8cbc 100644
--- a/src/core/hle/kernel/memory/system_control.h
+++ b/src/core/hle/kernel/memory/system_control.h
@@ -8,11 +8,6 @@
namespace Kernel::Memory::SystemControl {
-u64 GenerateRandomU64ForInit();
-
-template <typename F>
-u64 GenerateUniformRange(u64 min, u64 max, F f);
-
u64 GenerateRandomRange(u64 min, u64 max);
} // namespace Kernel::Memory::SystemControl
diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
index f93e5e4b0..5cbd3b912 100644
--- a/src/core/hle/kernel/scheduler.cpp
+++ b/src/core/hle/kernel/scheduler.cpp
@@ -131,7 +131,8 @@ u32 GlobalScheduler::SelectThreads() {
u32 cores_needing_context_switch{};
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
Scheduler& sched = kernel.Scheduler(core);
- ASSERT(top_threads[core] == nullptr || top_threads[core]->GetProcessorID() == core);
+ ASSERT(top_threads[core] == nullptr ||
+ static_cast<u32>(top_threads[core]->GetProcessorID()) == core);
if (update_thread(top_threads[core], sched)) {
cores_needing_context_switch |= (1ul << core);
}
@@ -663,32 +664,26 @@ void Scheduler::Reload() {
}
void Scheduler::SwitchContextStep2() {
- Thread* previous_thread = current_thread_prev.get();
- Thread* new_thread = selected_thread.get();
-
// Load context of new thread
- Process* const previous_process =
- previous_thread != nullptr ? previous_thread->GetOwnerProcess() : nullptr;
-
- if (new_thread) {
- ASSERT_MSG(new_thread->GetSchedulingStatus() == ThreadSchedStatus::Runnable,
+ if (selected_thread) {
+ ASSERT_MSG(selected_thread->GetSchedulingStatus() == ThreadSchedStatus::Runnable,
"Thread must be runnable.");
// Cancel any outstanding wakeup events for this thread
- new_thread->SetIsRunning(true);
- new_thread->last_running_ticks = system.CoreTiming().GetCPUTicks();
- new_thread->SetWasRunning(false);
+ selected_thread->SetIsRunning(true);
+ selected_thread->last_running_ticks = system.CoreTiming().GetCPUTicks();
+ selected_thread->SetWasRunning(false);
auto* const thread_owner_process = current_thread->GetOwnerProcess();
if (thread_owner_process != nullptr) {
system.Kernel().MakeCurrentProcess(thread_owner_process);
}
- if (!new_thread->IsHLEThread()) {
- Core::ARM_Interface& cpu_core = new_thread->ArmInterface();
- cpu_core.LoadContext(new_thread->GetContext32());
- cpu_core.LoadContext(new_thread->GetContext64());
- cpu_core.SetTlsAddress(new_thread->GetTLSAddress());
- cpu_core.SetTPIDR_EL0(new_thread->GetTPIDR_EL0());
+ if (!selected_thread->IsHLEThread()) {
+ Core::ARM_Interface& cpu_core = selected_thread->ArmInterface();
+ cpu_core.LoadContext(selected_thread->GetContext32());
+ cpu_core.LoadContext(selected_thread->GetContext64());
+ cpu_core.SetTlsAddress(selected_thread->GetTLSAddress());
+ cpu_core.SetTPIDR_EL0(selected_thread->GetTPIDR_EL0());
cpu_core.ChangeProcessorID(this->core_id);
cpu_core.ClearExclusiveState();
}
@@ -761,7 +756,11 @@ void Scheduler::SwitchToCurrent() {
current_thread = selected_thread;
is_context_switch_pending = false;
}
- while (!is_context_switch_pending) {
+ const auto is_switch_pending = [this] {
+ std::scoped_lock lock{guard};
+ return is_context_switch_pending;
+ };
+ do {
if (current_thread != nullptr && !current_thread->IsHLEThread()) {
current_thread->context_guard.lock();
if (!current_thread->IsRunnable()) {
@@ -780,7 +779,7 @@ void Scheduler::SwitchToCurrent() {
next_context = &idle_thread->GetHostContext();
}
Common::Fiber::YieldTo(switch_fiber, *next_context);
- }
+ } while (!is_switch_pending());
}
}
diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h
index b3b4b5169..36e3c26fb 100644
--- a/src/core/hle/kernel/scheduler.h
+++ b/src/core/hle/kernel/scheduler.h
@@ -289,7 +289,7 @@ private:
class SchedulerLock {
public:
- explicit SchedulerLock(KernelCore& kernel);
+ [[nodiscard]] explicit SchedulerLock(KernelCore& kernel);
~SchedulerLock();
protected:
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index af22f4c33..7e6391c6c 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -33,8 +33,10 @@ ResultVal<std::shared_ptr<ServerSession>> ServerSession::Create(KernelCore& kern
std::string name) {
std::shared_ptr<ServerSession> session{std::make_shared<ServerSession>(kernel)};
- session->request_event = Core::Timing::CreateEvent(
- name, [session](u64, std::chrono::nanoseconds) { session->CompleteSyncRequest(); });
+ session->request_event =
+ Core::Timing::CreateEvent(name, [session](std::uintptr_t, std::chrono::nanoseconds) {
+ session->CompleteSyncRequest();
+ });
session->name = std::move(name);
session->parent = std::move(parent);
diff --git a/src/core/hle/kernel/time_manager.cpp b/src/core/hle/kernel/time_manager.cpp
index 88b01b751..95f2446c9 100644
--- a/src/core/hle/kernel/time_manager.cpp
+++ b/src/core/hle/kernel/time_manager.cpp
@@ -16,14 +16,14 @@ namespace Kernel {
TimeManager::TimeManager(Core::System& system_) : system{system_} {
time_manager_event_type = Core::Timing::CreateEvent(
- "Kernel::TimeManagerCallback", [this](u64 thread_handle, std::chrono::nanoseconds) {
- SchedulerLock lock(system.Kernel());
- Handle proper_handle = static_cast<Handle>(thread_handle);
+ "Kernel::TimeManagerCallback",
+ [this](std::uintptr_t thread_handle, std::chrono::nanoseconds) {
+ const SchedulerLock lock(system.Kernel());
+ const auto proper_handle = static_cast<Handle>(thread_handle);
if (cancelled_events[proper_handle]) {
return;
}
- std::shared_ptr<Thread> thread =
- this->system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle);
+ auto thread = this->system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle);
thread->OnWakeUp();
});
}
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 450f61fea..b6bdbd988 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -342,8 +342,9 @@ ResultVal<std::remove_reference_t<Arg>> MakeResult(Arg&& arg) {
*/
#define CASCADE_RESULT(target, source) \
auto CONCAT2(check_result_L, __LINE__) = source; \
- if (CONCAT2(check_result_L, __LINE__).Failed()) \
+ if (CONCAT2(check_result_L, __LINE__).Failed()) { \
return CONCAT2(check_result_L, __LINE__).Code(); \
+ } \
target = std::move(*CONCAT2(check_result_L, __LINE__))
/**
@@ -351,6 +352,9 @@ ResultVal<std::remove_reference_t<Arg>> MakeResult(Arg&& arg) {
* non-success, or discarded otherwise.
*/
#define CASCADE_CODE(source) \
- auto CONCAT2(check_result_L, __LINE__) = source; \
- if (CONCAT2(check_result_L, __LINE__).IsError()) \
- return CONCAT2(check_result_L, __LINE__);
+ do { \
+ auto CONCAT2(check_result_L, __LINE__) = source; \
+ if (CONCAT2(check_result_L, __LINE__).IsError()) { \
+ return CONCAT2(check_result_L, __LINE__); \
+ } \
+ } while (false)
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index 8ac856ec3..eb54cb123 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -35,7 +35,7 @@ constexpr ResultCode ERR_INVALID_BUFFER_SIZE{ErrorModule::Account, 30};
constexpr ResultCode ERR_FAILED_SAVE_DATA{ErrorModule::Account, 100};
static std::string GetImagePath(Common::UUID uuid) {
- return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
+ return Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
}
@@ -286,9 +286,7 @@ protected:
ProfileBase profile_base{};
ProfileData data{};
if (profile_manager.GetProfileBaseAndData(user_id, profile_base, data)) {
- std::array<u8, sizeof(ProfileData)> raw_data;
- std::memcpy(raw_data.data(), &data, sizeof(ProfileData));
- ctx.WriteBuffer(raw_data);
+ ctx.WriteBuffer(data);
IPC::ResponseBuilder rb{ctx, 16};
rb.Push(RESULT_SUCCESS);
rb.PushRaw(profile_base);
@@ -320,7 +318,7 @@ protected:
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- const FileUtil::IOFile image(GetImagePath(user_id), "rb");
+ const Common::FS::IOFile image(GetImagePath(user_id), "rb");
if (!image.IsOpen()) {
LOG_WARNING(Service_ACC,
"Failed to load user provided image! Falling back to built-in backup...");
@@ -333,7 +331,7 @@ protected:
std::vector<u8> buffer(size);
image.ReadBytes(buffer.data(), buffer.size());
- ctx.WriteBuffer(buffer.data(), buffer.size());
+ ctx.WriteBuffer(buffer);
rb.Push<u32>(size);
}
@@ -342,7 +340,7 @@ protected:
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- const FileUtil::IOFile image(GetImagePath(user_id), "rb");
+ const Common::FS::IOFile image(GetImagePath(user_id), "rb");
if (!image.IsOpen()) {
LOG_WARNING(Service_ACC,
@@ -407,7 +405,7 @@ protected:
ProfileData data;
std::memcpy(&data, user_data.data(), sizeof(ProfileData));
- FileUtil::IOFile image(GetImagePath(user_id), "wb");
+ Common::FS::IOFile image(GetImagePath(user_id), "wb");
if (!image.IsOpen() || !image.Resize(image_data.size()) ||
image.WriteBytes(image_data.data(), image_data.size()) != image_data.size() ||
diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp
index eb8c81645..9b829e957 100644
--- a/src/core/hle/service/acc/profile_manager.cpp
+++ b/src/core/hle/service/acc/profile_manager.cpp
@@ -13,6 +13,7 @@
namespace Service::Account {
+namespace FS = Common::FS;
using Common::UUID;
struct UserRaw {
@@ -58,7 +59,7 @@ ProfileManager::~ProfileManager() {
/// internal management of the users profiles
std::optional<std::size_t> ProfileManager::AddToProfiles(const ProfileInfo& profile) {
if (user_count >= MAX_USERS) {
- return {};
+ return std::nullopt;
}
profiles[user_count] = profile;
return user_count++;
@@ -101,13 +102,14 @@ ResultCode ProfileManager::CreateNewUser(UUID uuid, const ProfileUsername& usern
[&uuid](const ProfileInfo& profile) { return uuid == profile.user_uuid; })) {
return ERROR_USER_ALREADY_EXISTS;
}
- ProfileInfo profile;
- profile.user_uuid = uuid;
- profile.username = username;
- profile.data = {};
- profile.creation_time = 0x0;
- profile.is_open = false;
- return AddUser(profile);
+
+ return AddUser({
+ .user_uuid = uuid,
+ .username = username,
+ .creation_time = 0,
+ .data = {},
+ .is_open = false,
+ });
}
/// Creates a new user on the system. This function allows a much simpler method of registration
@@ -126,7 +128,7 @@ ResultCode ProfileManager::CreateNewUser(UUID uuid, const std::string& username)
std::optional<UUID> ProfileManager::GetUser(std::size_t index) const {
if (index >= MAX_USERS) {
- return {};
+ return std::nullopt;
}
return profiles[index].user_uuid;
@@ -135,13 +137,13 @@ std::optional<UUID> ProfileManager::GetUser(std::size_t index) const {
/// Returns a users profile index based on their user id.
std::optional<std::size_t> ProfileManager::GetUserIndex(const UUID& uuid) const {
if (!uuid) {
- return {};
+ return std::nullopt;
}
const auto iter = std::find_if(profiles.begin(), profiles.end(),
[&uuid](const ProfileInfo& p) { return p.user_uuid == uuid; });
if (iter == profiles.end()) {
- return {};
+ return std::nullopt;
}
return static_cast<std::size_t>(std::distance(profiles.begin(), iter));
@@ -317,9 +319,8 @@ bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase&
}
void ProfileManager::ParseUserSaveFile() {
- FileUtil::IOFile save(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
- ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat",
- "rb");
+ const FS::IOFile save(
+ FS::GetUserPath(FS::UserPath::NANDDir) + ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat", "rb");
if (!save.IsOpen()) {
LOG_WARNING(Service_ACC, "Failed to load profile data from save data... Generating new "
@@ -339,7 +340,13 @@ void ProfileManager::ParseUserSaveFile() {
continue;
}
- AddUser({user.uuid, user.username, user.timestamp, user.extra_data, false});
+ AddUser({
+ .user_uuid = user.uuid,
+ .username = user.username,
+ .creation_time = user.timestamp,
+ .data = user.extra_data,
+ .is_open = false,
+ });
}
std::stable_partition(profiles.begin(), profiles.end(),
@@ -350,29 +357,31 @@ void ProfileManager::WriteUserSaveFile() {
ProfileDataRaw raw{};
for (std::size_t i = 0; i < MAX_USERS; ++i) {
- raw.users[i].username = profiles[i].username;
- raw.users[i].uuid2 = profiles[i].user_uuid;
- raw.users[i].uuid = profiles[i].user_uuid;
- raw.users[i].timestamp = profiles[i].creation_time;
- raw.users[i].extra_data = profiles[i].data;
+ raw.users[i] = {
+ .uuid = profiles[i].user_uuid,
+ .uuid2 = profiles[i].user_uuid,
+ .timestamp = profiles[i].creation_time,
+ .username = profiles[i].username,
+ .extra_data = profiles[i].data,
+ };
}
- const auto raw_path =
- FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010";
- if (FileUtil::Exists(raw_path) && !FileUtil::IsDirectory(raw_path))
- FileUtil::Delete(raw_path);
+ const auto raw_path = FS::GetUserPath(FS::UserPath::NANDDir) + "/system/save/8000000000000010";
+ if (FS::Exists(raw_path) && !FS::IsDirectory(raw_path)) {
+ FS::Delete(raw_path);
+ }
- const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
- ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat";
+ const auto path =
+ FS::GetUserPath(FS::UserPath::NANDDir) + ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat";
- if (!FileUtil::CreateFullPath(path)) {
+ if (!FS::CreateFullPath(path)) {
LOG_WARNING(Service_ACC, "Failed to create full path of profiles.dat. Create the directory "
"nand/system/save/8000000000000010/su/avators to mitigate this "
"issue.");
return;
}
- FileUtil::IOFile save(path, "wb");
+ FS::IOFile save(path, "wb");
if (!save.IsOpen()) {
LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data "
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 4e7a0bec9..7d92b25a3 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -378,7 +378,11 @@ void ISelfController::GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext&
}
void ISelfController::SetScreenShotPermission(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const auto permission = rp.PopEnum<ScreenshotPermission>();
+ LOG_DEBUG(Service_AM, "called, permission={}", permission);
+
+ screenshot_permission = permission;
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -1342,12 +1346,12 @@ void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called, uid={:016X}{:016X}", user_id[1], user_id[0]);
- FileSys::SaveDataDescriptor descriptor{};
- descriptor.title_id = system.CurrentProcess()->GetTitleID();
- descriptor.user_id = user_id;
- descriptor.type = FileSys::SaveDataType::SaveData;
+ FileSys::SaveDataAttribute attribute{};
+ attribute.title_id = system.CurrentProcess()->GetTitleID();
+ attribute.user_id = user_id;
+ attribute.type = FileSys::SaveDataType::SaveData;
const auto res = system.GetFileSystemController().CreateSaveData(
- FileSys::SaveDataSpaceId::NandUser, descriptor);
+ FileSys::SaveDataSpaceId::NandUser, attribute);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(res.Code());
@@ -1405,7 +1409,6 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) {
// Get supported languages from NACP, if possible
// Default to 0 (all languages supported)
u32 supported_languages = 0;
- FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()};
const auto res = [this] {
const auto title_id = system.CurrentProcess()->GetTitleID();
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 6cfb11b48..6e69796ec 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -149,6 +149,12 @@ private:
void GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx);
void GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx);
+ enum class ScreenshotPermission : u32 {
+ Inherit = 0,
+ Enable = 1,
+ Disable = 2,
+ };
+
Core::System& system;
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
Kernel::EventPair launchable_event;
@@ -157,6 +163,7 @@ private:
u32 idle_time_detection_extension = 0;
u64 num_fatal_sections_entered = 0;
bool is_auto_sleep_disabled = false;
+ ScreenshotPermission screenshot_permission = ScreenshotPermission::Inherit;
};
class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp
index c3261f3e6..4e0800f9a 100644
--- a/src/core/hle/service/am/applets/applets.cpp
+++ b/src/core/hle/service/am/applets/applets.cpp
@@ -5,6 +5,7 @@
#include <cstring>
#include "common/assert.h"
#include "core/core.h"
+#include "core/frontend/applets/controller.h"
#include "core/frontend/applets/error.h"
#include "core/frontend/applets/general_frontend.h"
#include "core/frontend/applets/profile_select.h"
@@ -15,6 +16,7 @@
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applets.h"
+#include "core/hle/service/am/applets/controller.h"
#include "core/hle/service/am/applets/error.h"
#include "core/hle/service/am/applets/general_backend.h"
#include "core/hle/service/am/applets/profile_select.h"
@@ -140,14 +142,14 @@ void Applet::Initialize() {
AppletFrontendSet::AppletFrontendSet() = default;
-AppletFrontendSet::AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error,
+AppletFrontendSet::AppletFrontendSet(ControllerApplet controller, ECommerceApplet e_commerce,
+ ErrorApplet error, ParentalControlsApplet parental_controls,
PhotoViewer photo_viewer, ProfileSelect profile_select,
- SoftwareKeyboard software_keyboard, WebBrowser web_browser,
- ECommerceApplet e_commerce)
- : parental_controls{std::move(parental_controls)}, error{std::move(error)},
- photo_viewer{std::move(photo_viewer)}, profile_select{std::move(profile_select)},
- software_keyboard{std::move(software_keyboard)}, web_browser{std::move(web_browser)},
- e_commerce{std::move(e_commerce)} {}
+ SoftwareKeyboard software_keyboard, WebBrowser web_browser)
+ : controller{std::move(controller)}, e_commerce{std::move(e_commerce)}, error{std::move(error)},
+ parental_controls{std::move(parental_controls)}, photo_viewer{std::move(photo_viewer)},
+ profile_select{std::move(profile_select)}, software_keyboard{std::move(software_keyboard)},
+ web_browser{std::move(web_browser)} {}
AppletFrontendSet::~AppletFrontendSet() = default;
@@ -164,20 +166,37 @@ const AppletFrontendSet& AppletManager::GetAppletFrontendSet() const {
}
void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
- if (set.parental_controls != nullptr)
- frontend.parental_controls = std::move(set.parental_controls);
- if (set.error != nullptr)
+ if (set.controller != nullptr) {
+ frontend.controller = std::move(set.controller);
+ }
+
+ if (set.e_commerce != nullptr) {
+ frontend.e_commerce = std::move(set.e_commerce);
+ }
+
+ if (set.error != nullptr) {
frontend.error = std::move(set.error);
- if (set.photo_viewer != nullptr)
+ }
+
+ if (set.parental_controls != nullptr) {
+ frontend.parental_controls = std::move(set.parental_controls);
+ }
+
+ if (set.photo_viewer != nullptr) {
frontend.photo_viewer = std::move(set.photo_viewer);
- if (set.profile_select != nullptr)
+ }
+
+ if (set.profile_select != nullptr) {
frontend.profile_select = std::move(set.profile_select);
- if (set.software_keyboard != nullptr)
+ }
+
+ if (set.software_keyboard != nullptr) {
frontend.software_keyboard = std::move(set.software_keyboard);
- if (set.web_browser != nullptr)
+ }
+
+ if (set.web_browser != nullptr) {
frontend.web_browser = std::move(set.web_browser);
- if (set.e_commerce != nullptr)
- frontend.e_commerce = std::move(set.e_commerce);
+ }
}
void AppletManager::SetDefaultAppletFrontendSet() {
@@ -186,15 +205,23 @@ void AppletManager::SetDefaultAppletFrontendSet() {
}
void AppletManager::SetDefaultAppletsIfMissing() {
- if (frontend.parental_controls == nullptr) {
- frontend.parental_controls =
- std::make_unique<Core::Frontend::DefaultParentalControlsApplet>();
+ if (frontend.controller == nullptr) {
+ frontend.controller = std::make_unique<Core::Frontend::DefaultControllerApplet>();
+ }
+
+ if (frontend.e_commerce == nullptr) {
+ frontend.e_commerce = std::make_unique<Core::Frontend::DefaultECommerceApplet>();
}
if (frontend.error == nullptr) {
frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>();
}
+ if (frontend.parental_controls == nullptr) {
+ frontend.parental_controls =
+ std::make_unique<Core::Frontend::DefaultParentalControlsApplet>();
+ }
+
if (frontend.photo_viewer == nullptr) {
frontend.photo_viewer = std::make_unique<Core::Frontend::DefaultPhotoViewerApplet>();
}
@@ -211,10 +238,6 @@ void AppletManager::SetDefaultAppletsIfMissing() {
if (frontend.web_browser == nullptr) {
frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
}
-
- if (frontend.e_commerce == nullptr) {
- frontend.e_commerce = std::make_unique<Core::Frontend::DefaultECommerceApplet>();
- }
}
void AppletManager::ClearAll() {
@@ -225,6 +248,8 @@ std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const {
switch (id) {
case AppletId::Auth:
return std::make_shared<Auth>(system, *frontend.parental_controls);
+ case AppletId::Controller:
+ return std::make_shared<Controller>(system, *frontend.controller);
case AppletId::Error:
return std::make_shared<Error>(system, *frontend.error);
case AppletId::ProfileSelect:
diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h
index e75be86a2..a1f4cf897 100644
--- a/src/core/hle/service/am/applets/applets.h
+++ b/src/core/hle/service/am/applets/applets.h
@@ -17,6 +17,7 @@ class System;
}
namespace Core::Frontend {
+class ControllerApplet;
class ECommerceApplet;
class ErrorApplet;
class ParentalControlsApplet;
@@ -155,19 +156,20 @@ protected:
};
struct AppletFrontendSet {
- using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>;
+ using ControllerApplet = std::unique_ptr<Core::Frontend::ControllerApplet>;
+ using ECommerceApplet = std::unique_ptr<Core::Frontend::ECommerceApplet>;
using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>;
+ using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>;
using PhotoViewer = std::unique_ptr<Core::Frontend::PhotoViewerApplet>;
using ProfileSelect = std::unique_ptr<Core::Frontend::ProfileSelectApplet>;
using SoftwareKeyboard = std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet>;
using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>;
- using ECommerceApplet = std::unique_ptr<Core::Frontend::ECommerceApplet>;
AppletFrontendSet();
- AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error,
- PhotoViewer photo_viewer, ProfileSelect profile_select,
- SoftwareKeyboard software_keyboard, WebBrowser web_browser,
- ECommerceApplet e_commerce);
+ AppletFrontendSet(ControllerApplet controller, ECommerceApplet e_commerce, ErrorApplet error,
+ ParentalControlsApplet parental_controls, PhotoViewer photo_viewer,
+ ProfileSelect profile_select, SoftwareKeyboard software_keyboard,
+ WebBrowser web_browser);
~AppletFrontendSet();
AppletFrontendSet(const AppletFrontendSet&) = delete;
@@ -176,13 +178,14 @@ struct AppletFrontendSet {
AppletFrontendSet(AppletFrontendSet&&) noexcept;
AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept;
- ParentalControlsApplet parental_controls;
+ ControllerApplet controller;
+ ECommerceApplet e_commerce;
ErrorApplet error;
+ ParentalControlsApplet parental_controls;
PhotoViewer photo_viewer;
ProfileSelect profile_select;
SoftwareKeyboard software_keyboard;
WebBrowser web_browser;
- ECommerceApplet e_commerce;
};
class AppletManager {
diff --git a/src/core/hle/service/am/applets/controller.cpp b/src/core/hle/service/am/applets/controller.cpp
new file mode 100644
index 000000000..2151da783
--- /dev/null
+++ b/src/core/hle/service/am/applets/controller.cpp
@@ -0,0 +1,210 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <cstring>
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/frontend/applets/controller.h"
+#include "core/hle/result.h"
+#include "core/hle/service/am/am.h"
+#include "core/hle/service/am/applets/controller.h"
+#include "core/hle/service/hid/controllers/npad.h"
+
+namespace Service::AM::Applets {
+
+// This error code (0x183ACA) is thrown when the applet fails to initialize.
+[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3101{ErrorModule::HID, 3101};
+// This error code (0x183CCA) is thrown when the u32 result in ControllerSupportResultInfo is 2.
+[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3102{ErrorModule::HID, 3102};
+
+static Core::Frontend::ControllerParameters ConvertToFrontendParameters(
+ ControllerSupportArgPrivate private_arg, ControllerSupportArgHeader header, bool enable_text,
+ std::vector<IdentificationColor> identification_colors, std::vector<ExplainText> text) {
+ HID::Controller_NPad::NPadType npad_style_set;
+ npad_style_set.raw = private_arg.style_set;
+
+ return {
+ .min_players = std::max(s8(1), header.player_count_min),
+ .max_players = header.player_count_max,
+ .keep_controllers_connected = header.enable_take_over_connection,
+ .enable_single_mode = header.enable_single_mode,
+ .enable_border_color = header.enable_identification_color,
+ .border_colors = identification_colors,
+ .enable_explain_text = enable_text,
+ .explain_text = text,
+ .allow_pro_controller = npad_style_set.pro_controller == 1,
+ .allow_handheld = npad_style_set.handheld == 1,
+ .allow_dual_joycons = npad_style_set.joycon_dual == 1,
+ .allow_left_joycon = npad_style_set.joycon_left == 1,
+ .allow_right_joycon = npad_style_set.joycon_right == 1,
+ };
+}
+
+Controller::Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_)
+ : Applet{system_.Kernel()}, frontend(frontend_) {}
+
+Controller::~Controller() = default;
+
+void Controller::Initialize() {
+ Applet::Initialize();
+
+ LOG_INFO(Service_HID, "Initializing Controller Applet.");
+
+ LOG_DEBUG(Service_HID,
+ "Initializing Applet with common_args: arg_version={}, lib_version={}, "
+ "play_startup_sound={}, size={}, system_tick={}, theme_color={}",
+ common_args.arguments_version, common_args.library_version,
+ common_args.play_startup_sound, common_args.size, common_args.system_tick,
+ common_args.theme_color);
+
+ library_applet_version = LibraryAppletVersion{common_args.library_version};
+
+ const auto private_arg_storage = broker.PopNormalDataToApplet();
+ ASSERT(private_arg_storage != nullptr);
+
+ const auto& private_arg = private_arg_storage->GetData();
+ ASSERT(private_arg.size() == sizeof(ControllerSupportArgPrivate));
+
+ std::memcpy(&controller_private_arg, private_arg.data(), sizeof(ControllerSupportArgPrivate));
+ ASSERT_MSG(controller_private_arg.arg_private_size == sizeof(ControllerSupportArgPrivate),
+ "Unknown ControllerSupportArgPrivate revision={} with size={}",
+ library_applet_version, controller_private_arg.arg_private_size);
+
+ switch (controller_private_arg.mode) {
+ case ControllerSupportMode::ShowControllerSupport: {
+ const auto user_arg_storage = broker.PopNormalDataToApplet();
+ ASSERT(user_arg_storage != nullptr);
+
+ const auto& user_arg = user_arg_storage->GetData();
+ switch (library_applet_version) {
+ case LibraryAppletVersion::Version3:
+ case LibraryAppletVersion::Version4:
+ case LibraryAppletVersion::Version5:
+ ASSERT(user_arg.size() == sizeof(ControllerSupportArgOld));
+ std::memcpy(&controller_user_arg_old, user_arg.data(), sizeof(ControllerSupportArgOld));
+ break;
+ case LibraryAppletVersion::Version7:
+ ASSERT(user_arg.size() == sizeof(ControllerSupportArgNew));
+ std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew));
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unknown ControllerSupportArg revision={} with size={}",
+ library_applet_version, controller_private_arg.arg_size);
+ ASSERT(user_arg.size() >= sizeof(ControllerSupportArgNew));
+ std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew));
+ break;
+ }
+ break;
+ }
+ case ControllerSupportMode::ShowControllerStrapGuide:
+ case ControllerSupportMode::ShowControllerFirmwareUpdate:
+ default: {
+ UNIMPLEMENTED_MSG("Unimplemented ControllerSupportMode={}", controller_private_arg.mode);
+ break;
+ }
+ }
+}
+
+bool Controller::TransactionComplete() const {
+ return complete;
+}
+
+ResultCode Controller::GetStatus() const {
+ return status;
+}
+
+void Controller::ExecuteInteractive() {
+ UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet.");
+}
+
+void Controller::Execute() {
+ switch (controller_private_arg.mode) {
+ case ControllerSupportMode::ShowControllerSupport: {
+ const auto parameters = [this] {
+ switch (library_applet_version) {
+ case LibraryAppletVersion::Version3:
+ case LibraryAppletVersion::Version4:
+ case LibraryAppletVersion::Version5:
+ return ConvertToFrontendParameters(
+ controller_private_arg, controller_user_arg_old.header,
+ controller_user_arg_old.enable_explain_text,
+ std::vector<IdentificationColor>(
+ controller_user_arg_old.identification_colors.begin(),
+ controller_user_arg_old.identification_colors.end()),
+ std::vector<ExplainText>(controller_user_arg_old.explain_text.begin(),
+ controller_user_arg_old.explain_text.end()));
+ case LibraryAppletVersion::Version7:
+ default:
+ return ConvertToFrontendParameters(
+ controller_private_arg, controller_user_arg_new.header,
+ controller_user_arg_new.enable_explain_text,
+ std::vector<IdentificationColor>(
+ controller_user_arg_new.identification_colors.begin(),
+ controller_user_arg_new.identification_colors.end()),
+ std::vector<ExplainText>(controller_user_arg_new.explain_text.begin(),
+ controller_user_arg_new.explain_text.end()));
+ }
+ }();
+
+ is_single_mode = parameters.enable_single_mode;
+
+ LOG_DEBUG(Service_HID,
+ "Controller Parameters: min_players={}, max_players={}, "
+ "keep_controllers_connected={}, enable_single_mode={}, enable_border_color={}, "
+ "enable_explain_text={}, allow_pro_controller={}, allow_handheld={}, "
+ "allow_dual_joycons={}, allow_left_joycon={}, allow_right_joycon={}",
+ parameters.min_players, parameters.max_players,
+ parameters.keep_controllers_connected, parameters.enable_single_mode,
+ parameters.enable_border_color, parameters.enable_explain_text,
+ parameters.allow_pro_controller, parameters.allow_handheld,
+ parameters.allow_dual_joycons, parameters.allow_left_joycon,
+ parameters.allow_right_joycon);
+
+ frontend.ReconfigureControllers([this] { ConfigurationComplete(); }, parameters);
+ break;
+ }
+ case ControllerSupportMode::ShowControllerStrapGuide:
+ case ControllerSupportMode::ShowControllerFirmwareUpdate:
+ default: {
+ ConfigurationComplete();
+ break;
+ }
+ }
+}
+
+void Controller::ConfigurationComplete() {
+ ControllerSupportResultInfo result_info{};
+
+ const auto& players = Settings::values.players;
+
+ // If enable_single_mode is enabled, player_count is 1 regardless of any other parameters.
+ // Otherwise, only count connected players from P1-P8.
+ result_info.player_count =
+ is_single_mode ? 1
+ : static_cast<s8>(std::count_if(
+ players.begin(), players.end() - 2,
+ [](Settings::PlayerInput player) { return player.connected; }));
+
+ result_info.selected_id = HID::Controller_NPad::IndexToNPad(
+ std::distance(players.begin(),
+ std::find_if(players.begin(), players.end(),
+ [](Settings::PlayerInput player) { return player.connected; })));
+
+ result_info.result = 0;
+
+ LOG_DEBUG(Service_HID, "Result Info: player_count={}, selected_id={}, result={}",
+ result_info.player_count, result_info.selected_id, result_info.result);
+
+ complete = true;
+ out_data = std::vector<u8>(sizeof(ControllerSupportResultInfo));
+ std::memcpy(out_data.data(), &result_info, out_data.size());
+ broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(out_data)));
+ broker.SignalStateChanged();
+}
+
+} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/controller.h b/src/core/hle/service/am/applets/controller.h
new file mode 100644
index 000000000..f7bb3fba9
--- /dev/null
+++ b/src/core/hle/service/am/applets/controller.h
@@ -0,0 +1,123 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "core/hle/service/am/applets/applets.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::AM::Applets {
+
+using IdentificationColor = std::array<u8, 4>;
+using ExplainText = std::array<char, 0x81>;
+
+enum class LibraryAppletVersion : u32_le {
+ Version3 = 0x3, // 1.0.0 - 2.3.0
+ Version4 = 0x4, // 3.0.0 - 5.1.0
+ Version5 = 0x5, // 6.0.0 - 7.0.1
+ Version7 = 0x7, // 8.0.0+
+};
+
+enum class ControllerSupportMode : u8 {
+ ShowControllerSupport = 0,
+ ShowControllerStrapGuide = 1,
+ ShowControllerFirmwareUpdate = 2,
+};
+
+enum class ControllerSupportCaller : u8 {
+ Application = 0,
+ System = 1,
+};
+
+struct ControllerSupportArgPrivate {
+ u32 arg_private_size{};
+ u32 arg_size{};
+ bool flag_0{};
+ bool flag_1{};
+ ControllerSupportMode mode{};
+ ControllerSupportCaller caller{};
+ u32 style_set{};
+ u32 joy_hold_type{};
+};
+static_assert(sizeof(ControllerSupportArgPrivate) == 0x14,
+ "ControllerSupportArgPrivate has incorrect size.");
+
+struct ControllerSupportArgHeader {
+ s8 player_count_min{};
+ s8 player_count_max{};
+ bool enable_take_over_connection{};
+ bool enable_left_justify{};
+ bool enable_permit_joy_dual{};
+ bool enable_single_mode{};
+ bool enable_identification_color{};
+};
+static_assert(sizeof(ControllerSupportArgHeader) == 0x7,
+ "ControllerSupportArgHeader has incorrect size.");
+
+// LibraryAppletVersion 0x3, 0x4, 0x5
+struct ControllerSupportArgOld {
+ ControllerSupportArgHeader header{};
+ std::array<IdentificationColor, 4> identification_colors{};
+ bool enable_explain_text{};
+ std::array<ExplainText, 4> explain_text{};
+};
+static_assert(sizeof(ControllerSupportArgOld) == 0x21C,
+ "ControllerSupportArgOld has incorrect size.");
+
+// LibraryAppletVersion 0x7
+struct ControllerSupportArgNew {
+ ControllerSupportArgHeader header{};
+ std::array<IdentificationColor, 8> identification_colors{};
+ bool enable_explain_text{};
+ std::array<ExplainText, 8> explain_text{};
+};
+static_assert(sizeof(ControllerSupportArgNew) == 0x430,
+ "ControllerSupportArgNew has incorrect size.");
+
+struct ControllerSupportResultInfo {
+ s8 player_count{};
+ INSERT_PADDING_BYTES(3);
+ u32 selected_id{};
+ u32 result{};
+};
+static_assert(sizeof(ControllerSupportResultInfo) == 0xC,
+ "ControllerSupportResultInfo has incorrect size.");
+
+class Controller final : public Applet {
+public:
+ explicit Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_);
+ ~Controller() override;
+
+ void Initialize() override;
+
+ bool TransactionComplete() const override;
+ ResultCode GetStatus() const override;
+ void ExecuteInteractive() override;
+ void Execute() override;
+
+ void ConfigurationComplete();
+
+private:
+ const Core::Frontend::ControllerApplet& frontend;
+
+ LibraryAppletVersion library_applet_version;
+ ControllerSupportArgPrivate controller_private_arg;
+ ControllerSupportArgOld controller_user_arg_old;
+ ControllerSupportArgNew controller_user_arg_new;
+ bool complete{false};
+ ResultCode status{RESULT_SUCCESS};
+ bool is_single_mode{false};
+ std::vector<u8> out_data;
+};
+
+} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp
index fbe3686ae..bdeb0737a 100644
--- a/src/core/hle/service/am/applets/software_keyboard.cpp
+++ b/src/core/hle/service/am/applets/software_keyboard.cpp
@@ -13,11 +13,23 @@
namespace Service::AM::Applets {
+namespace {
+enum class Request : u32 {
+ Finalize = 0x4,
+ SetUserWordInfo = 0x6,
+ SetCustomizeDic = 0x7,
+ Calc = 0xa,
+ SetCustomizedDictionaries = 0xb,
+ UnsetCustomizedDictionaries = 0xc,
+ UnknownD = 0xd,
+ UnknownE = 0xe,
+};
+constexpr std::size_t SWKBD_INLINE_INIT_SIZE = 0x8;
constexpr std::size_t SWKBD_OUTPUT_BUFFER_SIZE = 0x7D8;
constexpr std::size_t SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE = 0x7D4;
constexpr std::size_t DEFAULT_MAX_LENGTH = 500;
constexpr bool INTERACTIVE_STATUS_OK = false;
-
+} // Anonymous namespace
static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters(
KeyboardConfig config, std::u16string initial_text) {
Core::Frontend::SoftwareKeyboardParameters params{};
@@ -47,6 +59,7 @@ SoftwareKeyboard::~SoftwareKeyboard() = default;
void SoftwareKeyboard::Initialize() {
complete = false;
+ is_inline = false;
initial_text.clear();
final_data.clear();
@@ -56,6 +69,11 @@ void SoftwareKeyboard::Initialize() {
ASSERT(keyboard_config_storage != nullptr);
const auto& keyboard_config = keyboard_config_storage->GetData();
+ if (keyboard_config.size() == SWKBD_INLINE_INIT_SIZE) {
+ is_inline = true;
+ return;
+ }
+
ASSERT(keyboard_config.size() >= sizeof(KeyboardConfig));
std::memcpy(&config, keyboard_config.data(), sizeof(KeyboardConfig));
@@ -87,16 +105,31 @@ void SoftwareKeyboard::ExecuteInteractive() {
const auto storage = broker.PopInteractiveDataToApplet();
ASSERT(storage != nullptr);
const auto data = storage->GetData();
- const auto status = static_cast<bool>(data[0]);
-
- if (status == INTERACTIVE_STATUS_OK) {
- complete = true;
+ if (!is_inline) {
+ const auto status = static_cast<bool>(data[0]);
+ if (status == INTERACTIVE_STATUS_OK) {
+ complete = true;
+ } else {
+ std::array<char16_t, SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE / 2 - 2> string;
+ std::memcpy(string.data(), data.data() + 4, string.size() * 2);
+ frontend.SendTextCheckDialog(
+ Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size()),
+ [this] { broker.SignalStateChanged(); });
+ }
} else {
- std::array<char16_t, SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE / 2 - 2> string;
- std::memcpy(string.data(), data.data() + 4, string.size() * 2);
- frontend.SendTextCheckDialog(
- Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size()),
- [this] { broker.SignalStateChanged(); });
+ Request request{};
+ std::memcpy(&request, data.data(), sizeof(Request));
+
+ switch (request) {
+ case Request::Calc: {
+ broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::vector<u8>{1}));
+ broker.SignalStateChanged();
+ break;
+ }
+ default:
+ UNIMPLEMENTED_MSG("Request {:X} is not implemented", request);
+ break;
+ }
}
}
@@ -108,9 +141,10 @@ void SoftwareKeyboard::Execute() {
}
const auto parameters = ConvertToFrontendParameters(config, initial_text);
-
- frontend.RequestText([this](std::optional<std::u16string> text) { WriteText(std::move(text)); },
- parameters);
+ if (!is_inline) {
+ frontend.RequestText(
+ [this](std::optional<std::u16string> text) { WriteText(std::move(text)); }, parameters);
+ }
}
void SoftwareKeyboard::WriteText(std::optional<std::u16string> text) {
diff --git a/src/core/hle/service/am/applets/software_keyboard.h b/src/core/hle/service/am/applets/software_keyboard.h
index ef4801fc6..5a3824b5a 100644
--- a/src/core/hle/service/am/applets/software_keyboard.h
+++ b/src/core/hle/service/am/applets/software_keyboard.h
@@ -78,6 +78,7 @@ private:
KeyboardConfig config;
std::u16string initial_text;
bool complete = false;
+ bool is_inline = false;
std::vector<u8> final_data;
};
diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp
index 9f30e167d..efe595c4f 100644
--- a/src/core/hle/service/am/applets/web_browser.cpp
+++ b/src/core/hle/service/am/applets/web_browser.cpp
@@ -293,8 +293,8 @@ void WebBrowser::Finalize() {
broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(data)));
broker.SignalStateChanged();
- if (!temporary_dir.empty() && FileUtil::IsDirectory(temporary_dir)) {
- FileUtil::DeleteDirRecursively(temporary_dir);
+ if (!temporary_dir.empty() && Common::FS::IsDirectory(temporary_dir)) {
+ Common::FS::DeleteDirRecursively(temporary_dir);
}
}
@@ -452,10 +452,10 @@ void WebBrowser::InitializeOffline() {
};
temporary_dir =
- FileUtil::SanitizePath(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + "web_applet_" +
- WEB_SOURCE_NAMES[static_cast<u32>(source) - 1],
- FileUtil::DirectorySeparator::PlatformDefault);
- FileUtil::DeleteDirRecursively(temporary_dir);
+ Common::FS::SanitizePath(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) +
+ "web_applet_" + WEB_SOURCE_NAMES[static_cast<u32>(source) - 1],
+ Common::FS::DirectorySeparator::PlatformDefault);
+ Common::FS::DeleteDirRecursively(temporary_dir);
u64 title_id = 0; // 0 corresponds to current process
ASSERT(args[WebArgTLVType::ApplicationID].size() >= 0x8);
@@ -492,8 +492,8 @@ void WebBrowser::InitializeOffline() {
}
filename =
- FileUtil::SanitizePath(temporary_dir + path_additional_directory + DIR_SEP + filename,
- FileUtil::DirectorySeparator::PlatformDefault);
+ Common::FS::SanitizePath(temporary_dir + path_additional_directory + DIR_SEP + filename,
+ Common::FS::DirectorySeparator::PlatformDefault);
}
void WebBrowser::ExecuteShop() {
@@ -551,7 +551,8 @@ void WebBrowser::ExecuteShop() {
}
void WebBrowser::ExecuteOffline() {
- frontend.OpenPageLocal(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); });
+ frontend.OpenPageLocal(
+ filename, [this] { UnpackRomFS(); }, [this] { Finalize(); });
}
} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index 106e89743..9b4910e53 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -71,7 +71,7 @@ public:
stream = audio_core.OpenStream(system.CoreTiming(), audio_params.sample_rate,
audio_params.channel_count, std::move(unique_name),
- [=]() { buffer_event.writable->Signal(); });
+ [this] { buffer_event.writable->Signal(); });
}
private:
@@ -206,7 +206,7 @@ private:
AudioCore::StreamPtr stream;
std::string device_name;
- [[maybe_unused]] AudoutParams audio_params {};
+ [[maybe_unused]] AudoutParams audio_params{};
/// This is the event handle used to check if the audio buffer was released
Kernel::EventPair buffer_event;
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp
index d19513cbb..f1d81602c 100644
--- a/src/core/hle/service/audio/hwopus.cpp
+++ b/src/core/hle/service/audio/hwopus.cpp
@@ -92,7 +92,7 @@ private:
if (performance) {
rb.Push<u64>(*performance);
}
- ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16));
+ ctx.WriteBuffer(samples);
}
bool DecodeOpusData(u32& consumed, u32& sample_count, const std::vector<u8>& input,
diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp
index d29e78d7e..ca021a99f 100644
--- a/src/core/hle/service/bcat/backend/boxcat.cpp
+++ b/src/core/hle/service/bcat/backend/boxcat.cpp
@@ -89,12 +89,12 @@ constexpr u32 TIMEOUT_SECONDS = 30;
std::string GetBINFilePath(u64 title_id) {
return fmt::format("{}bcat/{:016X}/launchparam.bin",
- FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id);
+ Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), title_id);
}
std::string GetZIPFilePath(u64 title_id) {
return fmt::format("{}bcat/{:016X}/data.zip",
- FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id);
+ Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), title_id);
}
// If the error is something the user should know about (build ID mismatch, bad client version),
@@ -205,8 +205,8 @@ private:
{std::string("Game-Build-Id"), fmt::format("{:016X}", build_id)},
};
- if (FileUtil::Exists(path)) {
- FileUtil::IOFile file{path, "rb"};
+ if (Common::FS::Exists(path)) {
+ Common::FS::IOFile file{path, "rb"};
if (file.IsOpen()) {
std::vector<u8> bytes(file.GetSize());
file.ReadBytes(bytes.data(), bytes.size());
@@ -236,8 +236,8 @@ private:
return DownloadResult::InvalidContentType;
}
- FileUtil::CreateFullPath(path);
- FileUtil::IOFile file{path, "wb"};
+ Common::FS::CreateFullPath(path);
+ Common::FS::IOFile file{path, "wb"};
if (!file.IsOpen())
return DownloadResult::GeneralFSError;
if (!file.Resize(response->body.size()))
@@ -290,7 +290,7 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe
LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
- FileUtil::Delete(zip_path);
+ Common::FS::Delete(zip_path);
}
HandleDownloadDisplayResult(applet_manager, res);
@@ -300,7 +300,7 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe
progress.StartProcessingDataList();
- FileUtil::IOFile zip{zip_path, "rb"};
+ Common::FS::IOFile zip{zip_path, "rb"};
const auto size = zip.GetSize();
std::vector<u8> bytes(size);
if (!zip.IsOpen() || size == 0 || zip.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) {
@@ -365,8 +365,7 @@ bool Boxcat::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress)
std::thread([this, title, &progress] {
SynchronizeInternal(applet_manager, dir_getter, title, progress);
- })
- .detach();
+ }).detach();
return true;
}
@@ -377,8 +376,7 @@ bool Boxcat::SynchronizeDirectory(TitleIDVersion title, std::string name,
std::thread([this, title, name, &progress] {
SynchronizeInternal(applet_manager, dir_getter, title, progress, name);
- })
- .detach();
+ }).detach();
return true;
}
@@ -422,7 +420,7 @@ std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title)
LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
- FileUtil::Delete(path);
+ Common::FS::Delete(path);
}
HandleDownloadDisplayResult(applet_manager, res);
@@ -430,7 +428,7 @@ std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title)
}
}
- FileUtil::IOFile bin{path, "rb"};
+ Common::FS::IOFile bin{path, "rb"};
const auto size = bin.GetSize();
std::vector<u8> bytes(size);
if (!bin.IsOpen() || size == 0 || bin.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) {
diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp
index 603b64d4f..db0e06ca1 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -112,7 +112,7 @@ private:
void GetImpl(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_BCAT, "called");
- ctx.WriteBuffer(&impl, sizeof(DeliveryCacheProgressImpl));
+ ctx.WriteBuffer(impl);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp
index a41c73c48..c2737a365 100644
--- a/src/core/hle/service/es/es.cpp
+++ b/src/core/hle/service/es/es.cpp
@@ -160,7 +160,7 @@ private:
return;
}
- ctx.WriteBuffer(key.data(), key.size());
+ ctx.WriteBuffer(key);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index c66124998..2cee1193c 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -36,7 +36,7 @@ constexpr u64 SUFFICIENT_SAVE_DATA_SIZE = 0xF0000000;
static FileSys::VirtualDir GetDirectoryRelativeWrapped(FileSys::VirtualDir base,
std::string_view dir_name_) {
- std::string dir_name(FileUtil::SanitizePath(dir_name_));
+ std::string dir_name(Common::FS::SanitizePath(dir_name_));
if (dir_name.empty() || dir_name == "." || dir_name == "/" || dir_name == "\\")
return base;
@@ -53,13 +53,13 @@ std::string VfsDirectoryServiceWrapper::GetName() const {
}
ResultCode VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64 size) const {
- std::string path(FileUtil::SanitizePath(path_));
- auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
+ std::string path(Common::FS::SanitizePath(path_));
+ auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
// dir can be nullptr if path contains subdirectories, create those prior to creating the file.
if (dir == nullptr) {
- dir = backing->CreateSubdirectory(FileUtil::GetParentPath(path));
+ dir = backing->CreateSubdirectory(Common::FS::GetParentPath(path));
}
- auto file = dir->CreateFile(FileUtil::GetFilename(path));
+ auto file = dir->CreateFile(Common::FS::GetFilename(path));
if (file == nullptr) {
// TODO(DarkLordZach): Find a better error code for this
return RESULT_UNKNOWN;
@@ -72,17 +72,17 @@ ResultCode VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64
}
ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) const {
- std::string path(FileUtil::SanitizePath(path_));
+ std::string path(Common::FS::SanitizePath(path_));
if (path.empty()) {
// TODO(DarkLordZach): Why do games call this and what should it do? Works as is but...
return RESULT_SUCCESS;
}
- auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
- if (dir->GetFile(FileUtil::GetFilename(path)) == nullptr) {
+ auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
+ if (dir->GetFile(Common::FS::GetFilename(path)) == nullptr) {
return FileSys::ERROR_PATH_NOT_FOUND;
}
- if (!dir->DeleteFile(FileUtil::GetFilename(path))) {
+ if (!dir->DeleteFile(Common::FS::GetFilename(path))) {
// TODO(DarkLordZach): Find a better error code for this
return RESULT_UNKNOWN;
}
@@ -91,11 +91,11 @@ ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) cons
}
ResultCode VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_) const {
- std::string path(FileUtil::SanitizePath(path_));
- auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
- if (dir == nullptr && FileUtil::GetFilename(FileUtil::GetParentPath(path)).empty())
+ std::string path(Common::FS::SanitizePath(path_));
+ auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
+ if (dir == nullptr && Common::FS::GetFilename(Common::FS::GetParentPath(path)).empty())
dir = backing;
- auto new_dir = dir->CreateSubdirectory(FileUtil::GetFilename(path));
+ auto new_dir = dir->CreateSubdirectory(Common::FS::GetFilename(path));
if (new_dir == nullptr) {
// TODO(DarkLordZach): Find a better error code for this
return RESULT_UNKNOWN;
@@ -104,9 +104,9 @@ ResultCode VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_)
}
ResultCode VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path_) const {
- std::string path(FileUtil::SanitizePath(path_));
- auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
- if (!dir->DeleteSubdirectory(FileUtil::GetFilename(path))) {
+ std::string path(Common::FS::SanitizePath(path_));
+ auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
+ if (!dir->DeleteSubdirectory(Common::FS::GetFilename(path))) {
// TODO(DarkLordZach): Find a better error code for this
return RESULT_UNKNOWN;
}
@@ -114,9 +114,9 @@ ResultCode VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path_)
}
ResultCode VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::string& path_) const {
- std::string path(FileUtil::SanitizePath(path_));
- auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
- if (!dir->DeleteSubdirectoryRecursive(FileUtil::GetFilename(path))) {
+ std::string path(Common::FS::SanitizePath(path_));
+ auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
+ if (!dir->DeleteSubdirectoryRecursive(Common::FS::GetFilename(path))) {
// TODO(DarkLordZach): Find a better error code for this
return RESULT_UNKNOWN;
}
@@ -124,10 +124,10 @@ ResultCode VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::str
}
ResultCode VfsDirectoryServiceWrapper::CleanDirectoryRecursively(const std::string& path) const {
- const std::string sanitized_path(FileUtil::SanitizePath(path));
- auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(sanitized_path));
+ const std::string sanitized_path(Common::FS::SanitizePath(path));
+ auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(sanitized_path));
- if (!dir->CleanSubdirectoryRecursive(FileUtil::GetFilename(sanitized_path))) {
+ if (!dir->CleanSubdirectoryRecursive(Common::FS::GetFilename(sanitized_path))) {
// TODO(DarkLordZach): Find a better error code for this
return RESULT_UNKNOWN;
}
@@ -137,14 +137,14 @@ ResultCode VfsDirectoryServiceWrapper::CleanDirectoryRecursively(const std::stri
ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_,
const std::string& dest_path_) const {
- std::string src_path(FileUtil::SanitizePath(src_path_));
- std::string dest_path(FileUtil::SanitizePath(dest_path_));
+ std::string src_path(Common::FS::SanitizePath(src_path_));
+ std::string dest_path(Common::FS::SanitizePath(dest_path_));
auto src = backing->GetFileRelative(src_path);
- if (FileUtil::GetParentPath(src_path) == FileUtil::GetParentPath(dest_path)) {
+ if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) {
// Use more-optimized vfs implementation rename.
if (src == nullptr)
return FileSys::ERROR_PATH_NOT_FOUND;
- if (!src->Rename(FileUtil::GetFilename(dest_path))) {
+ if (!src->Rename(Common::FS::GetFilename(dest_path))) {
// TODO(DarkLordZach): Find a better error code for this
return RESULT_UNKNOWN;
}
@@ -162,7 +162,7 @@ ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_,
ASSERT_MSG(dest->WriteBytes(src->ReadAllBytes()) == src->GetSize(),
"Could not write all of the bytes but everything else has succeded.");
- if (!src->GetContainingDirectory()->DeleteFile(FileUtil::GetFilename(src_path))) {
+ if (!src->GetContainingDirectory()->DeleteFile(Common::FS::GetFilename(src_path))) {
// TODO(DarkLordZach): Find a better error code for this
return RESULT_UNKNOWN;
}
@@ -172,14 +172,14 @@ ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_,
ResultCode VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path_,
const std::string& dest_path_) const {
- std::string src_path(FileUtil::SanitizePath(src_path_));
- std::string dest_path(FileUtil::SanitizePath(dest_path_));
+ std::string src_path(Common::FS::SanitizePath(src_path_));
+ std::string dest_path(Common::FS::SanitizePath(dest_path_));
auto src = GetDirectoryRelativeWrapped(backing, src_path);
- if (FileUtil::GetParentPath(src_path) == FileUtil::GetParentPath(dest_path)) {
+ if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) {
// Use more-optimized vfs implementation rename.
if (src == nullptr)
return FileSys::ERROR_PATH_NOT_FOUND;
- if (!src->Rename(FileUtil::GetFilename(dest_path))) {
+ if (!src->Rename(Common::FS::GetFilename(dest_path))) {
// TODO(DarkLordZach): Find a better error code for this
return RESULT_UNKNOWN;
}
@@ -198,7 +198,7 @@ ResultCode VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_pa
ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std::string& path_,
FileSys::Mode mode) const {
- const std::string path(FileUtil::SanitizePath(path_));
+ const std::string path(Common::FS::SanitizePath(path_));
std::string_view npath = path;
while (!npath.empty() && (npath[0] == '/' || npath[0] == '\\')) {
npath.remove_prefix(1);
@@ -218,7 +218,7 @@ ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std::
}
ResultVal<FileSys::VirtualDir> VfsDirectoryServiceWrapper::OpenDirectory(const std::string& path_) {
- std::string path(FileUtil::SanitizePath(path_));
+ std::string path(Common::FS::SanitizePath(path_));
auto dir = GetDirectoryRelativeWrapped(backing, path);
if (dir == nullptr) {
// TODO(DarkLordZach): Find a better error code for this
@@ -229,11 +229,11 @@ ResultVal<FileSys::VirtualDir> VfsDirectoryServiceWrapper::OpenDirectory(const s
ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(
const std::string& path_) const {
- std::string path(FileUtil::SanitizePath(path_));
- auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
+ std::string path(Common::FS::SanitizePath(path_));
+ auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
if (dir == nullptr)
return FileSys::ERROR_PATH_NOT_FOUND;
- auto filename = FileUtil::GetFilename(path);
+ auto filename = Common::FS::GetFilename(path);
// TODO(Subv): Some games use the '/' path, find out what this means.
if (filename.empty())
return MakeResult(FileSys::EntryType::Directory);
@@ -311,7 +311,7 @@ ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS(
}
ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData(
- FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& save_struct) const {
+ FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const {
LOG_TRACE(Service_FS, "Creating Save Data for space_id={:01X}, save_struct={}",
static_cast<u8>(space), save_struct.DebugInfo());
@@ -323,15 +323,15 @@ ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData(
}
ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveData(
- FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& descriptor) const {
+ FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& attribute) const {
LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}",
- static_cast<u8>(space), descriptor.DebugInfo());
+ static_cast<u8>(space), attribute.DebugInfo());
if (save_data_factory == nullptr) {
return FileSys::ERROR_ENTITY_NOT_FOUND;
}
- return save_data_factory->Open(space, descriptor);
+ return save_data_factory->Open(space, attribute);
}
ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveDataSpace(
@@ -695,13 +695,13 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
sdmc_factory = nullptr;
}
- auto nand_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir),
+ auto nand_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir),
FileSys::Mode::ReadWrite);
- auto sd_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir),
+ auto sd_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir),
FileSys::Mode::ReadWrite);
- auto load_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
+ auto load_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir),
FileSys::Mode::ReadWrite);
- auto dump_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir),
+ auto dump_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir),
FileSys::Mode::ReadWrite);
if (bis_factory == nullptr) {
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index 1b0a6a949..6dbbf0b2b 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -31,7 +31,7 @@ enum class SaveDataSpaceId : u8;
enum class SaveDataType : u8;
enum class StorageId : u8;
-struct SaveDataDescriptor;
+struct SaveDataAttribute;
struct SaveDataSize;
} // namespace FileSys
@@ -69,9 +69,9 @@ public:
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
FileSys::ContentRecordType type) const;
ResultVal<FileSys::VirtualDir> CreateSaveData(
- FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& save_struct) const;
+ FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const;
ResultVal<FileSys::VirtualDir> OpenSaveData(
- FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& save_struct) const;
+ FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const;
ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space) const;
ResultVal<FileSys::VirtualDir> OpenSDMC() const;
ResultVal<FileSys::VirtualDir> OpenBISPartition(FileSys::BisPartitionId id) const;
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index 20c331b77..649128be4 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -696,8 +696,8 @@ FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter)
{67, nullptr, "FindSaveDataWithFilter"},
{68, nullptr, "OpenSaveDataInfoReaderBySaveDataFilter"},
{69, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataAttribute"},
- {70, nullptr, "WriteSaveDataFileSystemExtraDataBySaveDataAttribute"},
- {71, nullptr, "ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute"},
+ {70, &FSP_SRV::WriteSaveDataFileSystemExtraDataBySaveDataAttribute, "WriteSaveDataFileSystemExtraDataBySaveDataAttribute"},
+ {71, &FSP_SRV::ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute, "ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute"},
{80, nullptr, "OpenSaveDataMetaFile"},
{81, nullptr, "OpenSaveDataTransferManager"},
{82, nullptr, "OpenSaveDataTransferManagerVersion2"},
@@ -812,7 +812,7 @@ void FSP_SRV::OpenSdCardFileSystem(Kernel::HLERequestContext& ctx) {
void FSP_SRV::CreateSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- auto save_struct = rp.PopRaw<FileSys::SaveDataDescriptor>();
+ auto save_struct = rp.PopRaw<FileSys::SaveDataAttribute>();
[[maybe_unused]] auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>();
u128 uid = rp.PopRaw<u128>();
@@ -826,31 +826,40 @@ void FSP_SRV::CreateSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
}
void FSP_SRV::OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
- LOG_INFO(Service_FS, "called.");
+ IPC::RequestParser rp{ctx};
struct Parameters {
- FileSys::SaveDataSpaceId save_data_space_id;
- FileSys::SaveDataDescriptor descriptor;
+ FileSys::SaveDataSpaceId space_id;
+ FileSys::SaveDataAttribute attribute;
};
- IPC::RequestParser rp{ctx};
const auto parameters = rp.PopRaw<Parameters>();
- auto dir = fsc.OpenSaveData(parameters.save_data_space_id, parameters.descriptor);
+ LOG_INFO(Service_FS, "called.");
+
+ auto dir = fsc.OpenSaveData(parameters.space_id, parameters.attribute);
if (dir.Failed()) {
IPC::ResponseBuilder rb{ctx, 2, 0, 0};
rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND);
return;
}
- FileSys::StorageId id;
- if (parameters.save_data_space_id == FileSys::SaveDataSpaceId::NandUser) {
+ FileSys::StorageId id{};
+ switch (parameters.space_id) {
+ case FileSys::SaveDataSpaceId::NandUser:
id = FileSys::StorageId::NandUser;
- } else if (parameters.save_data_space_id == FileSys::SaveDataSpaceId::SdCardSystem ||
- parameters.save_data_space_id == FileSys::SaveDataSpaceId::SdCardUser) {
+ break;
+ case FileSys::SaveDataSpaceId::SdCardSystem:
+ case FileSys::SaveDataSpaceId::SdCardUser:
id = FileSys::StorageId::SdCard;
- } else {
+ break;
+ case FileSys::SaveDataSpaceId::NandSystem:
id = FileSys::StorageId::NandSystem;
+ break;
+ case FileSys::SaveDataSpaceId::TemporaryStorage:
+ case FileSys::SaveDataSpaceId::ProperSystem:
+ case FileSys::SaveDataSpaceId::SafeMode:
+ UNREACHABLE();
}
auto filesystem =
@@ -876,22 +885,38 @@ void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext&
rb.PushIpcInterface<ISaveDataInfoReader>(std::make_shared<ISaveDataInfoReader>(space, fsc));
}
-void FSP_SRV::SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- log_mode = rp.PopEnum<LogMode>();
-
- LOG_DEBUG(Service_FS, "called, log_mode={:08X}", static_cast<u32>(log_mode));
+void FSP_SRV::WriteSaveDataFileSystemExtraDataBySaveDataAttribute(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_FS, "(STUBBED) called.");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_FS, "called");
+void FSP_SRV::ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(
+ Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ struct Parameters {
+ FileSys::SaveDataSpaceId space_id;
+ FileSys::SaveDataAttribute attribute;
+ };
+
+ const auto parameters = rp.PopRaw<Parameters>();
+ // Stub this to None for now, backend needs an impl to read/write the SaveDataExtraData
+ constexpr auto flags = static_cast<u32>(FileSys::SaveDataFlags::None);
+
+ LOG_WARNING(Service_FS,
+ "(STUBBED) called, flags={}, space_id={}, attribute.title_id={:016X}\n"
+ "attribute.user_id={:016X}{:016X}, attribute.save_id={:016X}\n"
+ "attribute.type={}, attribute.rank={}, attribute.index={}",
+ flags, static_cast<u32>(parameters.space_id), parameters.attribute.title_id,
+ parameters.attribute.user_id[1], parameters.attribute.user_id[0],
+ parameters.attribute.save_id, static_cast<u32>(parameters.attribute.type),
+ static_cast<u32>(parameters.attribute.rank), parameters.attribute.index);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.PushEnum(log_mode);
+ rb.Push(flags);
}
void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
@@ -966,6 +991,24 @@ void FSP_SRV::OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ct
rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND);
}
+void FSP_SRV::SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ log_mode = rp.PopEnum<LogMode>();
+
+ LOG_DEBUG(Service_FS, "called, log_mode={:08X}", static_cast<u32>(log_mode));
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_FS, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(log_mode);
+}
+
void FSP_SRV::OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx) {
const auto raw = ctx.ReadBuffer();
auto log = Common::StringFromFixedZeroTerminatedBuffer(
diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h
index dfb3e395b..4964e874e 100644
--- a/src/core/hle/service/filesystem/fsp_srv.h
+++ b/src/core/hle/service/filesystem/fsp_srv.h
@@ -43,11 +43,13 @@ private:
void OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx);
void OpenReadOnlySaveDataFileSystem(Kernel::HLERequestContext& ctx);
void OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext& ctx);
- void SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
- void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
+ void WriteSaveDataFileSystemExtraDataBySaveDataAttribute(Kernel::HLERequestContext& ctx);
+ void ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(Kernel::HLERequestContext& ctx);
void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
void OpenDataStorageByDataId(Kernel::HLERequestContext& ctx);
void OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
+ void SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
+ void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
void OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx);
void GetAccessLogVersionInfo(Kernel::HLERequestContext& ctx);
void OpenMultiCommitManager(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/hid/controllers/debug_pad.cpp b/src/core/hle/service/hid/controllers/debug_pad.cpp
index cb35919e9..ad251ed4a 100644
--- a/src/core/hle/service/hid/controllers/debug_pad.cpp
+++ b/src/core/hle/service/hid/controllers/debug_pad.cpp
@@ -39,33 +39,36 @@ void Controller_DebugPad::OnUpdate(const Core::Timing::CoreTiming& core_timing,
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
- cur_entry.attribute.connected.Assign(1);
- auto& pad = cur_entry.pad_state;
- using namespace Settings::NativeButton;
- pad.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
- pad.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
- pad.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
- pad.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
- pad.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
- pad.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
- pad.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus());
- pad.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus());
- pad.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus());
- pad.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus());
- pad.d_left.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus());
- pad.d_up.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus());
- pad.d_right.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus());
- pad.d_down.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus());
+ if (Settings::values.debug_pad_enabled) {
+ cur_entry.attribute.connected.Assign(1);
+ auto& pad = cur_entry.pad_state;
- const auto [stick_l_x_f, stick_l_y_f] =
- analogs[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus();
- const auto [stick_r_x_f, stick_r_y_f] =
- analogs[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus();
- cur_entry.l_stick.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
- cur_entry.l_stick.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
- cur_entry.r_stick.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
- cur_entry.r_stick.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
+ using namespace Settings::NativeButton;
+ pad.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
+ pad.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
+ pad.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
+ pad.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
+ pad.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
+ pad.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
+ pad.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus());
+ pad.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus());
+ pad.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus());
+ pad.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus());
+ pad.d_left.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus());
+ pad.d_up.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus());
+ pad.d_right.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus());
+ pad.d_down.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus());
+
+ const auto [stick_l_x_f, stick_l_y_f] =
+ analogs[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus();
+ const auto [stick_r_x_f, stick_r_y_f] =
+ analogs[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus();
+ cur_entry.l_stick.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
+ cur_entry.l_stick.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
+ cur_entry.r_stick.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
+ cur_entry.r_stick.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
+ }
std::memcpy(data, &shared_memory, sizeof(SharedMemory));
}
diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp
index feae89525..0b896d5ad 100644
--- a/src/core/hle/service/hid/controllers/keyboard.cpp
+++ b/src/core/hle/service/hid/controllers/keyboard.cpp
@@ -40,15 +40,16 @@ void Controller_Keyboard::OnUpdate(const Core::Timing::CoreTiming& core_timing,
cur_entry.key.fill(0);
cur_entry.modifier = 0;
-
- for (std::size_t i = 0; i < keyboard_keys.size(); ++i) {
- cur_entry.key[i / KEYS_PER_BYTE] |= (keyboard_keys[i]->GetStatus() << (i % KEYS_PER_BYTE));
- }
-
- for (std::size_t i = 0; i < keyboard_mods.size(); ++i) {
- cur_entry.modifier |= (keyboard_mods[i]->GetStatus() << i);
+ if (Settings::values.keyboard_enabled) {
+ for (std::size_t i = 0; i < keyboard_keys.size(); ++i) {
+ cur_entry.key[i / KEYS_PER_BYTE] |=
+ (keyboard_keys[i]->GetStatus() << (i % KEYS_PER_BYTE));
+ }
+
+ for (std::size_t i = 0; i < keyboard_mods.size(); ++i) {
+ cur_entry.modifier |= (keyboard_mods[i]->GetStatus() << i);
+ }
}
-
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
}
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index ef67ad690..7818c098f 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -24,6 +24,7 @@ constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
constexpr std::size_t NPAD_OFFSET = 0x9A00;
constexpr u32 BATTERY_FULL = 2;
constexpr u32 MAX_NPAD_ID = 7;
+constexpr std::size_t HANDHELD_INDEX = 8;
constexpr std::array<u32, 10> npad_id_list{
0, 1, 2, 3, 4, 5, 6, 7, NPAD_HANDHELD, NPAD_UNKNOWN,
};
@@ -33,19 +34,41 @@ enum class JoystickId : std::size_t {
Joystick_Right,
};
-static Controller_NPad::NPadControllerType MapSettingsTypeToNPad(Settings::ControllerType type) {
+Controller_NPad::NPadControllerType Controller_NPad::MapSettingsTypeToNPad(
+ Settings::ControllerType type) {
switch (type) {
case Settings::ControllerType::ProController:
- return Controller_NPad::NPadControllerType::ProController;
- case Settings::ControllerType::DualJoycon:
- return Controller_NPad::NPadControllerType::JoyDual;
+ return NPadControllerType::ProController;
+ case Settings::ControllerType::DualJoyconDetached:
+ return NPadControllerType::JoyDual;
case Settings::ControllerType::LeftJoycon:
- return Controller_NPad::NPadControllerType::JoyLeft;
+ return NPadControllerType::JoyLeft;
case Settings::ControllerType::RightJoycon:
- return Controller_NPad::NPadControllerType::JoyRight;
+ return NPadControllerType::JoyRight;
+ case Settings::ControllerType::Handheld:
+ return NPadControllerType::Handheld;
default:
UNREACHABLE();
- return Controller_NPad::NPadControllerType::JoyDual;
+ return NPadControllerType::ProController;
+ }
+}
+
+Settings::ControllerType Controller_NPad::MapNPadToSettingsType(
+ Controller_NPad::NPadControllerType type) {
+ switch (type) {
+ case NPadControllerType::ProController:
+ return Settings::ControllerType::ProController;
+ case NPadControllerType::JoyDual:
+ return Settings::ControllerType::DualJoyconDetached;
+ case NPadControllerType::JoyLeft:
+ return Settings::ControllerType::LeftJoycon;
+ case NPadControllerType::JoyRight:
+ return Settings::ControllerType::RightJoycon;
+ case NPadControllerType::Handheld:
+ return Settings::ControllerType::Handheld;
+ default:
+ UNREACHABLE();
+ return Settings::ControllerType::ProController;
}
}
@@ -60,9 +83,9 @@ std::size_t Controller_NPad::NPadIdToIndex(u32 npad_id) {
case 6:
case 7:
return npad_id;
- case 8:
+ case HANDHELD_INDEX:
case NPAD_HANDHELD:
- return 8;
+ return HANDHELD_INDEX;
case 9:
case NPAD_UNKNOWN:
return 9;
@@ -83,38 +106,48 @@ u32 Controller_NPad::IndexToNPad(std::size_t index) {
case 6:
case 7:
return static_cast<u32>(index);
- case 8:
+ case HANDHELD_INDEX:
return NPAD_HANDHELD;
case 9:
return NPAD_UNKNOWN;
default:
UNIMPLEMENTED_MSG("Unknown npad index {}", index);
return 0;
- };
+ }
}
Controller_NPad::Controller_NPad(Core::System& system) : ControllerBase(system), system(system) {}
Controller_NPad::~Controller_NPad() = default;
-void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
+void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
const auto controller_type = connected_controllers[controller_idx].type;
auto& controller = shared_memory_entries[controller_idx];
if (controller_type == NPadControllerType::None) {
+ styleset_changed_events[controller_idx].writable->Signal();
return;
}
controller.joy_styles.raw = 0; // Zero out
controller.device_type.raw = 0;
+ controller.properties.raw = 0;
switch (controller_type) {
case NPadControllerType::None:
UNREACHABLE();
break;
+ case NPadControllerType::ProController:
+ controller.joy_styles.pro_controller.Assign(1);
+ controller.device_type.pro_controller.Assign(1);
+ controller.properties.is_vertical.Assign(1);
+ controller.properties.use_plus.Assign(1);
+ controller.properties.use_minus.Assign(1);
+ controller.pad_assignment = NPadAssignments::Single;
+ break;
case NPadControllerType::Handheld:
controller.joy_styles.handheld.Assign(1);
controller.device_type.handheld.Assign(1);
- controller.pad_assignment = NPadAssignments::Dual;
controller.properties.is_vertical.Assign(1);
controller.properties.use_plus.Assign(1);
controller.properties.use_minus.Assign(1);
+ controller.pad_assignment = NPadAssignments::Dual;
break;
case NPadControllerType::JoyDual:
controller.joy_styles.joycon_dual.Assign(1);
@@ -144,14 +177,6 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
controller.device_type.pokeball.Assign(1);
controller.pad_assignment = NPadAssignments::Single;
break;
- case NPadControllerType::ProController:
- controller.joy_styles.pro_controller.Assign(1);
- controller.device_type.pro_controller.Assign(1);
- controller.properties.is_vertical.Assign(1);
- controller.properties.use_plus.Assign(1);
- controller.properties.use_minus.Assign(1);
- controller.pad_assignment = NPadAssignments::Single;
- break;
}
controller.single_color_error = ColorReadError::ReadOk;
@@ -168,7 +193,8 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
controller.battery_level[0] = BATTERY_FULL;
controller.battery_level[1] = BATTERY_FULL;
controller.battery_level[2] = BATTERY_FULL;
- styleset_changed_events[controller_idx].writable->Signal();
+
+ SignalStyleSetChangedEvent(IndexToNPad(controller_idx));
}
void Controller_NPad::OnInit() {
@@ -192,36 +218,25 @@ void Controller_NPad::OnInit() {
style.pokeball.Assign(1);
}
- std::transform(
- Settings::values.players.begin(), Settings::values.players.end(),
- connected_controllers.begin(), [](const Settings::PlayerInput& player) {
- return ControllerHolder{MapSettingsTypeToNPad(player.type), player.connected};
- });
-
- std::stable_partition(connected_controllers.begin(), connected_controllers.begin() + 8,
- [](const ControllerHolder& holder) { return holder.is_connected; });
+ std::transform(Settings::values.players.begin(), Settings::values.players.end(),
+ connected_controllers.begin(), [](const Settings::PlayerInput& player) {
+ return ControllerHolder{MapSettingsTypeToNPad(player.controller_type),
+ player.connected};
+ });
// Account for handheld
- if (connected_controllers[8].is_connected)
- connected_controllers[8].type = NPadControllerType::Handheld;
+ if (connected_controllers[HANDHELD_INDEX].is_connected) {
+ connected_controllers[HANDHELD_INDEX].type = NPadControllerType::Handheld;
+ }
supported_npad_id_types.resize(npad_id_list.size());
std::memcpy(supported_npad_id_types.data(), npad_id_list.data(),
npad_id_list.size() * sizeof(u32));
- // Add a default dual joycon controller if none are present.
- if (std::none_of(connected_controllers.begin(), connected_controllers.end(),
- [](const ControllerHolder& controller) { return controller.is_connected; })) {
- supported_npad_id_types.resize(npad_id_list.size());
- std::memcpy(supported_npad_id_types.data(), npad_id_list.data(),
- npad_id_list.size() * sizeof(u32));
- AddNewController(NPadControllerType::JoyDual);
- }
-
for (std::size_t i = 0; i < connected_controllers.size(); ++i) {
const auto& controller = connected_controllers[i];
if (controller.is_connected) {
- AddNewControllerAt(controller.type, IndexToNPad(i));
+ AddNewControllerAt(controller.type, i);
}
}
}
@@ -309,8 +324,9 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t data_len) {
- if (!IsControllerActivated())
+ if (!IsControllerActivated()) {
return;
+ }
for (std::size_t i = 0; i < shared_memory_entries.size(); i++) {
auto& npad = shared_memory_entries[i];
const std::array<NPadGeneric*, 7> controller_npads{&npad.main_controller_states,
@@ -360,13 +376,25 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
auto& libnx_entry = npad.libnx.npad[npad.libnx.common.last_entry_index];
libnx_entry.connection_status.raw = 0;
+ libnx_entry.connection_status.IsConnected.Assign(1);
switch (controller_type) {
case NPadControllerType::None:
UNREACHABLE();
break;
+ case NPadControllerType::ProController:
+ main_controller.connection_status.raw = 0;
+ main_controller.connection_status.IsConnected.Assign(1);
+ main_controller.connection_status.IsWired.Assign(1);
+ main_controller.pad.pad_states.raw = pad_state.pad_states.raw;
+ main_controller.pad.l_stick = pad_state.l_stick;
+ main_controller.pad.r_stick = pad_state.r_stick;
+
+ libnx_entry.connection_status.IsWired.Assign(1);
+ break;
case NPadControllerType::Handheld:
handheld_entry.connection_status.raw = 0;
+ handheld_entry.connection_status.IsConnected.Assign(1);
handheld_entry.connection_status.IsWired.Assign(1);
handheld_entry.connection_status.IsLeftJoyConnected.Assign(1);
handheld_entry.connection_status.IsRightJoyConnected.Assign(1);
@@ -375,57 +403,52 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
handheld_entry.pad.pad_states.raw = pad_state.pad_states.raw;
handheld_entry.pad.l_stick = pad_state.l_stick;
handheld_entry.pad.r_stick = pad_state.r_stick;
+
+ libnx_entry.connection_status.IsWired.Assign(1);
+ libnx_entry.connection_status.IsLeftJoyConnected.Assign(1);
+ libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
+ libnx_entry.connection_status.IsLeftJoyWired.Assign(1);
+ libnx_entry.connection_status.IsRightJoyWired.Assign(1);
break;
case NPadControllerType::JoyDual:
dual_entry.connection_status.raw = 0;
-
+ dual_entry.connection_status.IsConnected.Assign(1);
dual_entry.connection_status.IsLeftJoyConnected.Assign(1);
dual_entry.connection_status.IsRightJoyConnected.Assign(1);
- dual_entry.connection_status.IsConnected.Assign(1);
-
- libnx_entry.connection_status.IsLeftJoyConnected.Assign(1);
- libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
- libnx_entry.connection_status.IsConnected.Assign(1);
-
dual_entry.pad.pad_states.raw = pad_state.pad_states.raw;
dual_entry.pad.l_stick = pad_state.l_stick;
dual_entry.pad.r_stick = pad_state.r_stick;
+
+ libnx_entry.connection_status.IsLeftJoyConnected.Assign(1);
+ libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
break;
case NPadControllerType::JoyLeft:
left_entry.connection_status.raw = 0;
-
left_entry.connection_status.IsConnected.Assign(1);
+ left_entry.connection_status.IsLeftJoyConnected.Assign(1);
left_entry.pad.pad_states.raw = pad_state.pad_states.raw;
left_entry.pad.l_stick = pad_state.l_stick;
left_entry.pad.r_stick = pad_state.r_stick;
+
+ libnx_entry.connection_status.IsLeftJoyConnected.Assign(1);
break;
case NPadControllerType::JoyRight:
right_entry.connection_status.raw = 0;
-
right_entry.connection_status.IsConnected.Assign(1);
+ right_entry.connection_status.IsRightJoyConnected.Assign(1);
right_entry.pad.pad_states.raw = pad_state.pad_states.raw;
right_entry.pad.l_stick = pad_state.l_stick;
right_entry.pad.r_stick = pad_state.r_stick;
+
+ libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
break;
case NPadControllerType::Pokeball:
pokeball_entry.connection_status.raw = 0;
-
pokeball_entry.connection_status.IsConnected.Assign(1);
- pokeball_entry.connection_status.IsWired.Assign(1);
-
pokeball_entry.pad.pad_states.raw = pad_state.pad_states.raw;
pokeball_entry.pad.l_stick = pad_state.l_stick;
pokeball_entry.pad.r_stick = pad_state.r_stick;
break;
- case NPadControllerType::ProController:
- main_controller.connection_status.raw = 0;
-
- main_controller.connection_status.IsConnected.Assign(1);
- main_controller.connection_status.IsWired.Assign(1);
- main_controller.pad.pad_states.raw = pad_state.pad_states.raw;
- main_controller.pad.l_stick = pad_state.l_stick;
- main_controller.pad.r_stick = pad_state.r_stick;
- break;
}
// LibNX exclusively uses this section, so we always update it since LibNX doesn't activate
@@ -453,26 +476,6 @@ void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) {
supported_npad_id_types.clear();
supported_npad_id_types.resize(length / sizeof(u32));
std::memcpy(supported_npad_id_types.data(), data, length);
- for (std::size_t i = 0; i < connected_controllers.size(); i++) {
- auto& controller = connected_controllers[i];
- if (!controller.is_connected) {
- continue;
- }
- const auto requested_controller =
- i <= MAX_NPAD_ID ? MapSettingsTypeToNPad(Settings::values.players[i].type)
- : NPadControllerType::Handheld;
- if (!IsControllerSupported(requested_controller)) {
- const auto is_handheld = requested_controller == NPadControllerType::Handheld;
- if (is_handheld) {
- controller.type = NPadControllerType::None;
- controller.is_connected = false;
- AddNewController(requested_controller);
- } else {
- controller.type = requested_controller;
- InitNewlyAddedControler(i);
- }
- }
- }
}
void Controller_NPad::GetSupportedNpadIdTypes(u32* data, std::size_t max_length) {
@@ -504,7 +507,7 @@ void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids,
const std::vector<Vibration>& vibrations) {
LOG_DEBUG(Service_HID, "(STUBBED) called");
- if (!can_controllers_vibrate) {
+ if (!Settings::values.vibration_enabled || !can_controllers_vibrate) {
return;
}
for (std::size_t i = 0; i < controller_ids.size(); i++) {
@@ -516,54 +519,59 @@ void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids,
last_processed_vibration = vibrations.back();
}
+Controller_NPad::Vibration Controller_NPad::GetLastVibration() const {
+ return last_processed_vibration;
+}
+
std::shared_ptr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEvent(u32 npad_id) const {
- // TODO(ogniK): Figure out the best time to signal this event. This event seems that it should
- // be signalled at least once, and signaled after a new controller is connected?
const auto& styleset_event = styleset_changed_events[NPadIdToIndex(npad_id)];
return styleset_event.readable;
}
-Controller_NPad::Vibration Controller_NPad::GetLastVibration() const {
- return last_processed_vibration;
+void Controller_NPad::SignalStyleSetChangedEvent(u32 npad_id) const {
+ styleset_changed_events[NPadIdToIndex(npad_id)].writable->Signal();
}
-void Controller_NPad::AddNewController(NPadControllerType controller) {
- controller = DecideBestController(controller);
- if (controller == NPadControllerType::Handheld) {
- connected_controllers[8] = {controller, true};
- InitNewlyAddedControler(8);
- return;
- }
- const auto pos =
- std::find_if(connected_controllers.begin(), connected_controllers.end() - 2,
- [](const ControllerHolder& holder) { return !holder.is_connected; });
- if (pos == connected_controllers.end() - 2) {
- LOG_ERROR(Service_HID, "Cannot connect any more controllers!");
+void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::size_t npad_index) {
+ UpdateControllerAt(controller, npad_index, true);
+}
+
+void Controller_NPad::UpdateControllerAt(NPadControllerType controller, std::size_t npad_index,
+ bool connected) {
+ if (!connected) {
+ DisconnectNPadAtIndex(npad_index);
return;
}
- const auto controller_id = std::distance(connected_controllers.begin(), pos);
- connected_controllers[controller_id] = {controller, true};
- InitNewlyAddedControler(controller_id);
-}
-void Controller_NPad::AddNewControllerAt(NPadControllerType controller, u32 npad_id) {
- controller = DecideBestController(controller);
if (controller == NPadControllerType::Handheld) {
- connected_controllers[NPadIdToIndex(NPAD_HANDHELD)] = {controller, true};
- InitNewlyAddedControler(NPadIdToIndex(NPAD_HANDHELD));
+ Settings::values.players[HANDHELD_INDEX].controller_type =
+ MapNPadToSettingsType(controller);
+ Settings::values.players[HANDHELD_INDEX].connected = true;
+ connected_controllers[HANDHELD_INDEX] = {controller, true};
+ InitNewlyAddedController(HANDHELD_INDEX);
return;
}
- connected_controllers[NPadIdToIndex(npad_id)] = {controller, true};
- InitNewlyAddedControler(NPadIdToIndex(npad_id));
+ Settings::values.players[npad_index].controller_type = MapNPadToSettingsType(controller);
+ Settings::values.players[npad_index].connected = true;
+ connected_controllers[npad_index] = {controller, true};
+ InitNewlyAddedController(npad_index);
}
-void Controller_NPad::ConnectNPad(u32 npad_id) {
- connected_controllers[NPadIdToIndex(npad_id)].is_connected = true;
+void Controller_NPad::DisconnectNPad(u32 npad_id) {
+ DisconnectNPadAtIndex(NPadIdToIndex(npad_id));
}
-void Controller_NPad::DisconnectNPad(u32 npad_id) {
- connected_controllers[NPadIdToIndex(npad_id)].is_connected = false;
+void Controller_NPad::DisconnectNPadAtIndex(std::size_t npad_index) {
+ Settings::values.players[npad_index].connected = false;
+ connected_controllers[npad_index].is_connected = false;
+
+ auto& controller = shared_memory_entries[npad_index];
+ controller.joy_styles.raw = 0; // Zero out
+ controller.device_type.raw = 0;
+ controller.properties.raw = 0;
+
+ SignalStyleSetChangedEvent(IndexToNPad(npad_index));
}
void Controller_NPad::SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode) {
@@ -574,6 +582,22 @@ Controller_NPad::GyroscopeZeroDriftMode Controller_NPad::GetGyroscopeZeroDriftMo
return gyroscope_zero_drift_mode;
}
+void Controller_NPad::MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2) {
+ const auto npad_index_1 = NPadIdToIndex(npad_id_1);
+ const auto npad_index_2 = NPadIdToIndex(npad_id_2);
+
+ // If the controllers at both npad indices form a pair of left and right joycons, merge them.
+ // Otherwise, do nothing.
+ if ((connected_controllers[npad_index_1].type == NPadControllerType::JoyLeft &&
+ connected_controllers[npad_index_2].type == NPadControllerType::JoyRight) ||
+ (connected_controllers[npad_index_2].type == NPadControllerType::JoyLeft &&
+ connected_controllers[npad_index_1].type == NPadControllerType::JoyRight)) {
+ // Disconnect the joycon at the second id and connect the dual joycon at the first index.
+ DisconnectNPad(npad_id_2);
+ AddNewControllerAt(NPadControllerType::JoyDual, npad_index_1);
+ }
+}
+
void Controller_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
@@ -599,8 +623,8 @@ bool Controller_NPad::SwapNpadAssignment(u32 npad_id_1, u32 npad_id_2) {
std::swap(connected_controllers[npad_index_1].type, connected_controllers[npad_index_2].type);
- InitNewlyAddedControler(npad_index_1);
- InitNewlyAddedControler(npad_index_2);
+ AddNewControllerAt(connected_controllers[npad_index_1].type, npad_index_1);
+ AddNewControllerAt(connected_controllers[npad_index_2].type, npad_index_2);
return true;
}
@@ -614,11 +638,11 @@ Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) {
case 0:
return LedPattern{1, 0, 0, 0};
case 1:
- return LedPattern{0, 1, 0, 0};
+ return LedPattern{1, 1, 0, 0};
case 2:
- return LedPattern{0, 0, 1, 0};
+ return LedPattern{1, 1, 1, 0};
case 3:
- return LedPattern{0, 0, 0, 1};
+ return LedPattern{1, 1, 1, 1};
case 4:
return LedPattern{1, 0, 0, 1};
case 5:
@@ -628,9 +652,8 @@ Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) {
case 7:
return LedPattern{0, 1, 1, 0};
default:
- UNIMPLEMENTED_MSG("Unhandled npad_id {}", npad_id);
return LedPattern{0, 0, 0, 0};
- };
+ }
}
void Controller_NPad::SetVibrationEnabled(bool can_vibrate) {
@@ -651,13 +674,13 @@ void Controller_NPad::ClearAllConnectedControllers() {
}
void Controller_NPad::DisconnectAllConnectedControllers() {
- for (ControllerHolder& controller : connected_controllers) {
+ for (auto& controller : connected_controllers) {
controller.is_connected = false;
}
}
void Controller_NPad::ConnectAllDisconnectedControllers() {
- for (ControllerHolder& controller : connected_controllers) {
+ for (auto& controller : connected_controllers) {
if (controller.type != NPadControllerType::None && !controller.is_connected) {
controller.is_connected = true;
}
@@ -665,7 +688,7 @@ void Controller_NPad::ConnectAllDisconnectedControllers() {
}
void Controller_NPad::ClearAllControllers() {
- for (ControllerHolder& controller : connected_controllers) {
+ for (auto& controller : connected_controllers) {
controller.type = NPadControllerType::None;
controller.is_connected = false;
}
@@ -713,92 +736,4 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const
return false;
}
-Controller_NPad::NPadControllerType Controller_NPad::DecideBestController(
- NPadControllerType priority) const {
- if (IsControllerSupported(priority)) {
- return priority;
- }
- const auto is_docked = Settings::values.use_docked_mode;
- if (is_docked && priority == NPadControllerType::Handheld) {
- priority = NPadControllerType::JoyDual;
- if (IsControllerSupported(priority)) {
- return priority;
- }
- }
- std::vector<NPadControllerType> priority_list;
- switch (priority) {
- case NPadControllerType::ProController:
- priority_list.push_back(NPadControllerType::JoyDual);
- if (!is_docked) {
- priority_list.push_back(NPadControllerType::Handheld);
- }
- priority_list.push_back(NPadControllerType::JoyLeft);
- priority_list.push_back(NPadControllerType::JoyRight);
- priority_list.push_back(NPadControllerType::Pokeball);
- break;
- case NPadControllerType::Handheld:
- priority_list.push_back(NPadControllerType::JoyDual);
- priority_list.push_back(NPadControllerType::ProController);
- priority_list.push_back(NPadControllerType::JoyLeft);
- priority_list.push_back(NPadControllerType::JoyRight);
- priority_list.push_back(NPadControllerType::Pokeball);
- break;
- case NPadControllerType::JoyDual:
- if (!is_docked) {
- priority_list.push_back(NPadControllerType::Handheld);
- }
- priority_list.push_back(NPadControllerType::ProController);
- priority_list.push_back(NPadControllerType::JoyLeft);
- priority_list.push_back(NPadControllerType::JoyRight);
- priority_list.push_back(NPadControllerType::Pokeball);
- break;
- case NPadControllerType::JoyLeft:
- priority_list.push_back(NPadControllerType::JoyRight);
- priority_list.push_back(NPadControllerType::JoyDual);
- if (!is_docked) {
- priority_list.push_back(NPadControllerType::Handheld);
- }
- priority_list.push_back(NPadControllerType::ProController);
- priority_list.push_back(NPadControllerType::Pokeball);
- break;
- case NPadControllerType::JoyRight:
- priority_list.push_back(NPadControllerType::JoyLeft);
- priority_list.push_back(NPadControllerType::JoyDual);
- if (!is_docked) {
- priority_list.push_back(NPadControllerType::Handheld);
- }
- priority_list.push_back(NPadControllerType::ProController);
- priority_list.push_back(NPadControllerType::Pokeball);
- break;
- case NPadControllerType::Pokeball:
- priority_list.push_back(NPadControllerType::JoyLeft);
- priority_list.push_back(NPadControllerType::JoyRight);
- priority_list.push_back(NPadControllerType::JoyDual);
- if (!is_docked) {
- priority_list.push_back(NPadControllerType::Handheld);
- }
- priority_list.push_back(NPadControllerType::ProController);
- break;
- default:
- priority_list.push_back(NPadControllerType::JoyDual);
- if (!is_docked) {
- priority_list.push_back(NPadControllerType::Handheld);
- }
- priority_list.push_back(NPadControllerType::ProController);
- priority_list.push_back(NPadControllerType::JoyLeft);
- priority_list.push_back(NPadControllerType::JoyRight);
- priority_list.push_back(NPadControllerType::JoyDual);
- break;
- }
-
- const auto iter = std::find_if(priority_list.begin(), priority_list.end(),
- [this](auto type) { return IsControllerSupported(type); });
- if (iter == priority_list.end()) {
- UNIMPLEMENTED_MSG("Could not find supported controller!");
- return priority;
- }
-
- return *iter;
-}
-
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 5d4c58a43..e9788da8d 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -115,14 +115,19 @@ public:
void VibrateController(const std::vector<u32>& controller_ids,
const std::vector<Vibration>& vibrations);
- std::shared_ptr<Kernel::ReadableEvent> GetStyleSetChangedEvent(u32 npad_id) const;
Vibration GetLastVibration() const;
- void AddNewController(NPadControllerType controller);
- void AddNewControllerAt(NPadControllerType controller, u32 npad_id);
+ std::shared_ptr<Kernel::ReadableEvent> GetStyleSetChangedEvent(u32 npad_id) const;
+ void SignalStyleSetChangedEvent(u32 npad_id) const;
+
+ // Adds a new controller at an index.
+ void AddNewControllerAt(NPadControllerType controller, std::size_t npad_index);
+ // Adds a new controller at an index with connection status.
+ void UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, bool connected);
- void ConnectNPad(u32 npad_id);
void DisconnectNPad(u32 npad_id);
+ void DisconnectNPadAtIndex(std::size_t index);
+
void SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode);
GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode() const;
LedPattern GetLedPattern(u32 npad_id);
@@ -133,6 +138,7 @@ public:
void ConnectAllDisconnectedControllers();
void ClearAllControllers();
+ void MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2);
void StartLRAssignmentMode();
void StopLRAssignmentMode();
bool SwapNpadAssignment(u32 npad_id_1, u32 npad_id_2);
@@ -141,6 +147,8 @@ public:
// Specifically for cheat engine and other features.
u32 GetAndResetPressState();
+ static Controller_NPad::NPadControllerType MapSettingsTypeToNPad(Settings::ControllerType type);
+ static Settings::ControllerType MapNPadToSettingsType(Controller_NPad::NPadControllerType type);
static std::size_t NPadIdToIndex(u32 npad_id);
static u32 IndexToNPad(std::size_t index);
@@ -309,9 +317,8 @@ private:
bool is_connected;
};
- void InitNewlyAddedControler(std::size_t controller_idx);
+ void InitNewlyAddedController(std::size_t controller_idx);
bool IsControllerSupported(NPadControllerType controller) const;
- NPadControllerType DecideBestController(NPadControllerType priority) const;
void RequestPadStateUpdate(u32 npad_id);
u32 press_state{};
diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp
index e326f8f5c..0df395e85 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.cpp
+++ b/src/core/hle/service/hid/controllers/touchscreen.cpp
@@ -40,9 +40,14 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
- const auto [x, y, pressed] = touch_device->GetStatus();
+ bool pressed = false;
+ float x, y;
+ std::tie(x, y, pressed) = touch_device->GetStatus();
auto& touch_entry = cur_entry.states[0];
touch_entry.attribute.raw = 0;
+ if (!pressed && touch_btn_device) {
+ std::tie(x, y, pressed) = touch_btn_device->GetStatus();
+ }
if (pressed && Settings::values.touchscreen.enabled) {
touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width);
touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height);
@@ -63,5 +68,10 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
void Controller_Touchscreen::OnLoadInputDevices() {
touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touchscreen.device);
+ if (Settings::values.use_touch_from_button) {
+ touch_btn_device = Input::CreateDevice<Input::TouchDevice>("engine:touch_from_button");
+ } else {
+ touch_btn_device.reset();
+ }
}
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h
index a1d97269e..4d9042adc 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.h
+++ b/src/core/hle/service/hid/controllers/touchscreen.h
@@ -68,6 +68,7 @@ private:
"TouchScreenSharedMemory is an invalid size");
TouchScreenSharedMemory shared_memory{};
std::unique_ptr<Input::TouchDevice> touch_device;
+ std::unique_ptr<Input::TouchDevice> touch_btn_device;
s64_le last_touch{};
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 680290cbd..bd3c2f26b 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -38,11 +38,9 @@
namespace Service::HID {
// Updating period for each HID device.
-// TODO(ogniK): Find actual polling rate of hid
-constexpr auto pad_update_ns = std::chrono::nanoseconds{1000000000 / 66};
-[[maybe_unused]] constexpr auto accelerometer_update_ns =
- std::chrono::nanoseconds{1000000000 / 100};
-[[maybe_unused]] constexpr auto gyroscope_update_ticks = std::chrono::nanoseconds{1000000000 / 100};
+// HID is polled every 15ms, this value was derived from
+// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering#joy-con-status-data-packet
+constexpr auto pad_update_ns = std::chrono::nanoseconds{15 * 1000 * 1000}; // (15ms, 66.6Hz)
constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;
IAppletResource::IAppletResource(Core::System& system)
@@ -77,8 +75,9 @@ IAppletResource::IAppletResource(Core::System& system)
// Register update callbacks
pad_update_event = Core::Timing::CreateEvent(
- "HID::UpdatePadCallback", [this](u64 userdata, std::chrono::nanoseconds ns_late) {
- UpdateControllers(userdata, ns_late);
+ "HID::UpdatePadCallback",
+ [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ UpdateControllers(user_data, ns_late);
});
// TODO(shinyquagsire23): Other update callbacks? (accel, gyro?)
@@ -108,7 +107,8 @@ void IAppletResource::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
rb.PushCopyObjects(shared_mem);
}
-void IAppletResource::UpdateControllers(u64 userdata, std::chrono::nanoseconds ns_late) {
+void IAppletResource::UpdateControllers(std::uintptr_t user_data,
+ std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming();
const bool should_reload = Settings::values.is_device_reload_pending.exchange(false);
@@ -671,13 +671,15 @@ void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
void Hid::MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto unknown_1{rp.Pop<u32>()};
- const auto unknown_2{rp.Pop<u32>()};
+ const auto npad_id_1{rp.Pop<u32>()};
+ const auto npad_id_2{rp.Pop<u32>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
- LOG_WARNING(Service_HID,
- "(STUBBED) called, unknown_1={}, unknown_2={}, applet_resource_user_id={}",
- unknown_1, unknown_2, applet_resource_user_id);
+ 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);
+
+ auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
+ controller.MergeSingleJoyAsDualJoy(npad_id_1, npad_id_2);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -843,8 +845,7 @@ void Hid::CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) {
void Hid::PermitVibration(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto can_vibrate{rp.Pop<bool>()};
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetVibrationEnabled(can_vibrate);
+ Settings::values.vibration_enabled = can_vibrate;
LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate);
@@ -857,8 +858,7 @@ void Hid::IsVibrationPermitted(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push(
- applet_resource->GetController<Controller_NPad>(HidController::NPad).IsVibrationEnabled());
+ rb.Push(Settings::values.vibration_enabled);
}
void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index c6f0a2584..efb07547f 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -64,7 +64,7 @@ private:
}
void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx);
- void UpdateControllers(u64 userdata, std::chrono::nanoseconds ns_late);
+ void UpdateControllers(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
std::shared_ptr<Kernel::SharedMemory> shared_mem;
diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp
index 64a526b9e..d8cd10e31 100644
--- a/src/core/hle/service/ldr/ldr.cpp
+++ b/src/core/hle/service/ldr/ldr.cpp
@@ -310,7 +310,7 @@ public:
ResultVal<VAddr> MapProcessCodeMemory(Kernel::Process* process, VAddr baseAddress,
u64 size) const {
- for (int retry{}; retry < MAXIMUM_MAP_RETRIES; retry++) {
+ for (std::size_t retry = 0; retry < MAXIMUM_MAP_RETRIES; retry++) {
auto& page_table{process->PageTable()};
const VAddr addr{GetRandomMapRegion(page_table, size)};
const ResultCode result{page_table.MapProcessCodeMemory(addr, baseAddress, size)};
@@ -331,8 +331,7 @@ public:
ResultVal<VAddr> MapNro(Kernel::Process* process, VAddr nro_addr, std::size_t nro_size,
VAddr bss_addr, std::size_t bss_size, std::size_t size) const {
-
- for (int retry{}; retry < MAXIMUM_MAP_RETRIES; retry++) {
+ for (std::size_t retry = 0; retry < MAXIMUM_MAP_RETRIES; retry++) {
auto& page_table{process->PageTable()};
VAddr addr{};
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index 4b79eb81d..5e2d769a4 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -127,7 +127,7 @@ private:
const u32 array_size = rp.Pop<u32>();
LOG_DEBUG(Service_NFP, "called, array_size={}", array_size);
- ctx.WriteBuffer(&device_handle, sizeof(device_handle));
+ ctx.WriteBuffer(device_handle);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
@@ -220,7 +220,7 @@ private:
tag_info.protocol = 1; // TODO(ogniK): Figure out actual values
tag_info.tag_type = 2;
- ctx.WriteBuffer(&tag_info, sizeof(TagInfo));
+ ctx.WriteBuffer(tag_info);
rb.Push(RESULT_SUCCESS);
}
@@ -237,7 +237,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2};
auto amiibo = nfp_interface.GetAmiiboBuffer();
- ctx.WriteBuffer(&amiibo.model_info, sizeof(amiibo.model_info));
+ ctx.WriteBuffer(amiibo.model_info);
rb.Push(RESULT_SUCCESS);
}
@@ -283,7 +283,7 @@ private:
CommonInfo common_info{};
common_info.application_area_size = 0;
- ctx.WriteBuffer(&common_info, sizeof(CommonInfo));
+ ctx.WriteBuffer(common_info);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index 01ddcdbd6..2e9d95195 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -9,6 +9,7 @@
#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/nifm/nifm.h"
#include "core/hle/service/service.h"
+#include "core/network/network.h"
#include "core/settings.h"
namespace Service::NIFM {
@@ -174,6 +175,16 @@ private:
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
+ void GetCurrentIpAddress(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
+
+ const auto [ipv4, error] = Network::GetHostIPv4Address();
+ UNIMPLEMENTED_IF(error != Network::Errno::SUCCESS);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(ipv4);
+ }
void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NIFM, "called");
@@ -235,7 +246,7 @@ IGeneralService::IGeneralService(Core::System& system)
{9, nullptr, "SetNetworkProfile"},
{10, &IGeneralService::RemoveNetworkProfile, "RemoveNetworkProfile"},
{11, nullptr, "GetScanDataOld"},
- {12, nullptr, "GetCurrentIpAddress"},
+ {12, &IGeneralService::GetCurrentIpAddress, "GetCurrentIpAddress"},
{13, nullptr, "GetCurrentAccessPointOld"},
{14, &IGeneralService::CreateTemporaryNetworkProfile, "CreateTemporaryNetworkProfile"},
{15, nullptr, "GetCurrentIpConfigInfo"},
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index 886450be2..58ee1f712 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -5,6 +5,7 @@
#include "common/logging/log.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
+#include "core/file_sys/vfs.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/ns/errors.h"
diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h
index 1b52511a5..0240d6643 100644
--- a/src/core/hle/service/nvdrv/devices/nvdevice.h
+++ b/src/core/hle/service/nvdrv/devices/nvdevice.h
@@ -21,8 +21,9 @@ namespace Service::Nvidia::Devices {
/// implement the ioctl interface.
class nvdevice {
public:
- explicit nvdevice(Core::System& system) : system{system} {};
+ explicit nvdevice(Core::System& system) : system{system} {}
virtual ~nvdevice() = default;
+
union Ioctl {
u32_le raw;
BitField<0, 8, u32> cmd;
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 195421cc0..d4ba88147 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
@@ -16,11 +16,12 @@
#include "video_core/renderer_base.h"
namespace Service::Nvidia::Devices {
+
namespace NvErrCodes {
-enum {
- InvalidNmapHandle = -22,
-};
-}
+constexpr u32 Success{};
+constexpr u32 OutOfMemory{static_cast<u32>(-12)};
+constexpr u32 InvalidInput{static_cast<u32>(-22)};
+} // namespace NvErrCodes
nvhost_as_gpu::nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev)
: nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
@@ -49,8 +50,9 @@ u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std:
break;
}
- if (static_cast<IoctlCommand>(command.cmd.Value()) == IoctlCommand::IocRemapCommand)
+ if (static_cast<IoctlCommand>(command.cmd.Value()) == IoctlCommand::IocRemapCommand) {
return Remap(input, output);
+ }
UNIMPLEMENTED_MSG("Unimplemented ioctl command");
return 0;
@@ -59,6 +61,7 @@ u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std:
u32 nvhost_as_gpu::InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlInitalizeEx params{};
std::memcpy(&params, input.data(), input.size());
+
LOG_WARNING(Service_NVDRV, "(STUBBED) called, big_page_size=0x{:X}", params.big_page_size);
return 0;
@@ -67,53 +70,61 @@ u32 nvhost_as_gpu::InitalizeEx(const std::vector<u8>& input, std::vector<u8>& ou
u32 nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlAllocSpace params{};
std::memcpy(&params, input.data(), input.size());
+
LOG_DEBUG(Service_NVDRV, "called, pages={:X}, page_size={:X}, flags={:X}", params.pages,
params.page_size, params.flags);
- auto& gpu = system.GPU();
- const u64 size{static_cast<u64>(params.pages) * static_cast<u64>(params.page_size)};
- if (params.flags & 1) {
- params.offset = gpu.MemoryManager().AllocateSpace(params.offset, size, 1);
+ const auto size{static_cast<u64>(params.pages) * static_cast<u64>(params.page_size)};
+ if ((params.flags & AddressSpaceFlags::FixedOffset) != AddressSpaceFlags::None) {
+ params.offset = *system.GPU().MemoryManager().AllocateFixed(params.offset, size);
} else {
- params.offset = gpu.MemoryManager().AllocateSpace(size, params.align);
+ params.offset = system.GPU().MemoryManager().Allocate(size, params.align);
+ }
+
+ auto result{NvErrCodes::Success};
+ if (!params.offset) {
+ LOG_CRITICAL(Service_NVDRV, "allocation failed for size {}", size);
+ result = NvErrCodes::OutOfMemory;
}
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return result;
}
u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) {
- std::size_t num_entries = input.size() / sizeof(IoctlRemapEntry);
+ const auto num_entries = input.size() / sizeof(IoctlRemapEntry);
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, num_entries=0x{:X}", num_entries);
+ LOG_DEBUG(Service_NVDRV, "called, num_entries=0x{:X}", num_entries);
+ auto result{NvErrCodes::Success};
std::vector<IoctlRemapEntry> entries(num_entries);
std::memcpy(entries.data(), input.data(), input.size());
- auto& gpu = system.GPU();
for (const auto& entry : entries) {
- LOG_WARNING(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}",
- entry.offset, entry.nvmap_handle, entry.pages);
- GPUVAddr offset = static_cast<GPUVAddr>(entry.offset) << 0x10;
- auto object = nvmap_dev->GetObject(entry.nvmap_handle);
+ LOG_DEBUG(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}",
+ entry.offset, entry.nvmap_handle, entry.pages);
+
+ const auto object{nvmap_dev->GetObject(entry.nvmap_handle)};
if (!object) {
- LOG_CRITICAL(Service_NVDRV, "nvmap {} is an invalid handle!", entry.nvmap_handle);
- std::memcpy(output.data(), entries.data(), output.size());
- return static_cast<u32>(NvErrCodes::InvalidNmapHandle);
+ LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", entry.nvmap_handle);
+ result = NvErrCodes::InvalidInput;
+ break;
}
- ASSERT(object->status == nvmap::Object::Status::Allocated);
+ const auto offset{static_cast<GPUVAddr>(entry.offset) << 0x10};
+ const auto size{static_cast<u64>(entry.pages) << 0x10};
+ const auto map_offset{static_cast<u64>(entry.map_offset) << 0x10};
+ const auto addr{system.GPU().MemoryManager().Map(object->addr + map_offset, offset, size)};
- const u64 size = static_cast<u64>(entry.pages) << 0x10;
- ASSERT(size <= object->size);
- const u64 map_offset = static_cast<u64>(entry.map_offset) << 0x10;
-
- const GPUVAddr returned =
- gpu.MemoryManager().MapBufferEx(object->addr + map_offset, offset, size);
- ASSERT(returned == offset);
+ if (!addr) {
+ LOG_CRITICAL(Service_NVDRV, "map returned an invalid address!");
+ result = NvErrCodes::InvalidInput;
+ break;
+ }
}
+
std::memcpy(output.data(), entries.data(), output.size());
- return 0;
+ return result;
}
u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
@@ -126,44 +137,76 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou
params.flags, params.nvmap_handle, params.buffer_offset, params.mapping_size,
params.offset);
- if (!params.nvmap_handle) {
- return 0;
+ const auto object{nvmap_dev->GetObject(params.nvmap_handle)};
+ if (!object) {
+ LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", params.nvmap_handle);
+ std::memcpy(output.data(), &params, output.size());
+ return NvErrCodes::InvalidInput;
}
- auto object = nvmap_dev->GetObject(params.nvmap_handle);
- ASSERT(object);
-
- // We can only map objects that have already been assigned a CPU address.
- ASSERT(object->status == nvmap::Object::Status::Allocated);
-
- ASSERT(params.buffer_offset == 0);
-
// The real nvservices doesn't make a distinction between handles and ids, and
// object can only have one handle and it will be the same as its id. Assert that this is the
// case to prevent unexpected behavior.
ASSERT(object->id == params.nvmap_handle);
-
auto& gpu = system.GPU();
- if (params.flags & 1) {
- params.offset = gpu.MemoryManager().MapBufferEx(object->addr, params.offset, object->size);
- } else {
- params.offset = gpu.MemoryManager().MapBufferEx(object->addr, object->size);
+ u64 page_size{params.page_size};
+ if (!page_size) {
+ page_size = object->align;
+ }
+
+ if ((params.flags & AddressSpaceFlags::Remap) != AddressSpaceFlags::None) {
+ if (const auto buffer_map{FindBufferMap(params.offset)}; buffer_map) {
+ const auto cpu_addr{static_cast<VAddr>(buffer_map->CpuAddr() + params.buffer_offset)};
+ const auto gpu_addr{static_cast<GPUVAddr>(params.offset + params.buffer_offset)};
+
+ if (!gpu.MemoryManager().Map(cpu_addr, gpu_addr, params.mapping_size)) {
+ LOG_CRITICAL(Service_NVDRV,
+ "remap failed, flags={:X}, nvmap_handle={:X}, buffer_offset={}, "
+ "mapping_size = {}, offset={}",
+ params.flags, params.nvmap_handle, params.buffer_offset,
+ params.mapping_size, params.offset);
+
+ std::memcpy(output.data(), &params, output.size());
+ return NvErrCodes::InvalidInput;
+ }
+
+ std::memcpy(output.data(), &params, output.size());
+ return NvErrCodes::Success;
+ } else {
+ LOG_CRITICAL(Service_NVDRV, "address not mapped offset={}", params.offset);
+
+ std::memcpy(output.data(), &params, output.size());
+ return NvErrCodes::InvalidInput;
+ }
}
- // Create a new mapping entry for this operation.
- ASSERT_MSG(buffer_mappings.find(params.offset) == buffer_mappings.end(),
- "Offset is already mapped");
+ // We can only map objects that have already been assigned a CPU address.
+ ASSERT(object->status == nvmap::Object::Status::Allocated);
+
+ const auto physical_address{object->addr + params.buffer_offset};
+ u64 size{params.mapping_size};
+ if (!size) {
+ size = object->size;
+ }
- BufferMapping mapping{};
- mapping.nvmap_handle = params.nvmap_handle;
- mapping.offset = params.offset;
- mapping.size = object->size;
+ const bool is_alloc{(params.flags & AddressSpaceFlags::FixedOffset) == AddressSpaceFlags::None};
+ if (is_alloc) {
+ params.offset = gpu.MemoryManager().MapAllocate(physical_address, size, page_size);
+ } else {
+ params.offset = gpu.MemoryManager().Map(physical_address, params.offset, size);
+ }
- buffer_mappings[params.offset] = mapping;
+ auto result{NvErrCodes::Success};
+ if (!params.offset) {
+ LOG_CRITICAL(Service_NVDRV, "failed to map size={}", size);
+ result = NvErrCodes::InvalidInput;
+ } else {
+ AddBufferMap(params.offset, size, physical_address, is_alloc);
+ }
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return result;
}
u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
@@ -172,24 +215,20 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou
LOG_DEBUG(Service_NVDRV, "called, offset=0x{:X}", params.offset);
- const auto itr = buffer_mappings.find(params.offset);
- if (itr == buffer_mappings.end()) {
- LOG_WARNING(Service_NVDRV, "Tried to unmap an invalid offset 0x{:X}", params.offset);
- // Hardware tests shows that unmapping an already unmapped buffer always returns successful
- // and doesn't fail.
- return 0;
+ if (const auto size{RemoveBufferMap(params.offset)}; size) {
+ system.GPU().MemoryManager().Unmap(params.offset, *size);
+ } else {
+ LOG_ERROR(Service_NVDRV, "invalid offset=0x{:X}", params.offset);
}
- params.offset = system.GPU().MemoryManager().UnmapBuffer(params.offset, itr->second.size);
- buffer_mappings.erase(itr->second.offset);
-
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvErrCodes::Success;
}
u32 nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlBindChannel params{};
std::memcpy(&params, input.data(), input.size());
+
LOG_DEBUG(Service_NVDRV, "called, fd={:X}", params.fd);
channel = params.fd;
@@ -199,6 +238,7 @@ u32 nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8>& ou
u32 nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlGetVaRegions params{};
std::memcpy(&params, input.data(), input.size());
+
LOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
params.buf_size);
@@ -210,9 +250,43 @@ u32 nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& o
params.regions[1].offset = 0x04000000;
params.regions[1].page_size = 0x10000;
params.regions[1].pages = 0x1bffff;
+
// TODO(ogniK): This probably can stay stubbed but should add support way way later
+
std::memcpy(output.data(), &params, output.size());
return 0;
}
+std::optional<nvhost_as_gpu::BufferMap> nvhost_as_gpu::FindBufferMap(GPUVAddr gpu_addr) const {
+ const auto end{buffer_mappings.upper_bound(gpu_addr)};
+ for (auto iter{buffer_mappings.begin()}; iter != end; ++iter) {
+ if (gpu_addr >= iter->second.StartAddr() && gpu_addr < iter->second.EndAddr()) {
+ return iter->second;
+ }
+ }
+
+ return {};
+}
+
+void nvhost_as_gpu::AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr,
+ bool is_allocated) {
+ buffer_mappings[gpu_addr] = {gpu_addr, size, cpu_addr, is_allocated};
+}
+
+std::optional<std::size_t> nvhost_as_gpu::RemoveBufferMap(GPUVAddr gpu_addr) {
+ if (const auto iter{buffer_mappings.find(gpu_addr)}; iter != buffer_mappings.end()) {
+ std::size_t size{};
+
+ if (iter->second.IsAllocated()) {
+ size = iter->second.Size();
+ }
+
+ buffer_mappings.erase(iter);
+
+ return size;
+ }
+
+ return {};
+}
+
} // namespace Service::Nvidia::Devices
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 f79fcc065..9a0cdff0c 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
@@ -4,9 +4,12 @@
#pragma once
+#include <map>
#include <memory>
-#include <unordered_map>
+#include <optional>
#include <vector>
+
+#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
@@ -15,6 +18,13 @@ namespace Service::Nvidia::Devices {
class nvmap;
+enum class AddressSpaceFlags : u32 {
+ None = 0x0,
+ FixedOffset = 0x1,
+ Remap = 0x100,
+};
+DECLARE_ENUM_FLAG_OPERATORS(AddressSpaceFlags);
+
class nvhost_as_gpu final : public nvdevice {
public:
explicit nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
@@ -25,6 +35,45 @@ public:
IoctlVersion version) override;
private:
+ class BufferMap final {
+ public:
+ constexpr BufferMap() = default;
+
+ constexpr BufferMap(GPUVAddr start_addr, std::size_t size)
+ : start_addr{start_addr}, end_addr{start_addr + size} {}
+
+ constexpr BufferMap(GPUVAddr start_addr, std::size_t size, VAddr cpu_addr,
+ bool is_allocated)
+ : start_addr{start_addr}, end_addr{start_addr + size}, cpu_addr{cpu_addr},
+ is_allocated{is_allocated} {}
+
+ constexpr VAddr StartAddr() const {
+ return start_addr;
+ }
+
+ constexpr VAddr EndAddr() const {
+ return end_addr;
+ }
+
+ constexpr std::size_t Size() const {
+ return end_addr - start_addr;
+ }
+
+ constexpr VAddr CpuAddr() const {
+ return cpu_addr;
+ }
+
+ constexpr bool IsAllocated() const {
+ return is_allocated;
+ }
+
+ private:
+ GPUVAddr start_addr{};
+ GPUVAddr end_addr{};
+ VAddr cpu_addr{};
+ bool is_allocated{};
+ };
+
enum class IoctlCommand : u32_le {
IocInitalizeExCommand = 0x40284109,
IocAllocateSpaceCommand = 0xC0184102,
@@ -49,7 +98,7 @@ private:
struct IoctlAllocSpace {
u32_le pages;
u32_le page_size;
- u32_le flags;
+ AddressSpaceFlags flags;
INSERT_PADDING_WORDS(1);
union {
u64_le offset;
@@ -69,18 +118,18 @@ private:
static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size");
struct IoctlMapBufferEx {
- u32_le flags; // bit0: fixed_offset, bit2: cacheable
- u32_le kind; // -1 is default
+ AddressSpaceFlags flags; // bit0: fixed_offset, bit2: cacheable
+ u32_le kind; // -1 is default
u32_le nvmap_handle;
u32_le page_size; // 0 means don't care
- u64_le buffer_offset;
+ s64_le buffer_offset;
u64_le mapping_size;
- u64_le offset;
+ s64_le offset;
};
static_assert(sizeof(IoctlMapBufferEx) == 40, "IoctlMapBufferEx is incorrect size");
struct IoctlUnmapBuffer {
- u64_le offset;
+ s64_le offset;
};
static_assert(sizeof(IoctlUnmapBuffer) == 8, "IoctlUnmapBuffer is incorrect size");
@@ -106,15 +155,6 @@ private:
static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(IoctlVaRegion) * 2,
"IoctlGetVaRegions is incorrect size");
- struct BufferMapping {
- u64 offset;
- u64 size;
- u32 nvmap_handle;
- };
-
- /// Map containing the nvmap object mappings in GPU memory.
- std::unordered_map<u64, BufferMapping> buffer_mappings;
-
u32 channel{};
u32 InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output);
@@ -125,7 +165,14 @@ private:
u32 BindChannel(const std::vector<u8>& input, std::vector<u8>& output);
u32 GetVARegions(const std::vector<u8>& input, std::vector<u8>& output);
+ std::optional<BufferMap> FindBufferMap(GPUVAddr gpu_addr) const;
+ void AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, bool is_allocated);
+ std::optional<std::size_t> RemoveBufferMap(GPUVAddr gpu_addr);
+
std::shared_ptr<nvmap> nvmap_dev;
+
+ // This is expected to be ordered, therefore we must use a map, not unordered_map
+ std::map<GPUVAddr, BufferMap> buffer_mappings;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp
index 8c742316c..9436e16ad 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp
@@ -18,7 +18,12 @@ enum {
};
}
-nvmap::nvmap(Core::System& system) : nvdevice(system) {}
+nvmap::nvmap(Core::System& system) : nvdevice(system) {
+ // Handle 0 appears to be used when remapping, so we create a placeholder empty nvmap object to
+ // represent this.
+ CreateObject(0);
+}
+
nvmap::~nvmap() = default;
VAddr nvmap::GetObjectAddress(u32 handle) const {
@@ -50,6 +55,21 @@ u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<
return 0;
}
+u32 nvmap::CreateObject(u32 size) {
+ // Create a new nvmap object and obtain a handle to it.
+ auto object = std::make_shared<Object>();
+ object->id = next_id++;
+ object->size = size;
+ object->status = Object::Status::Created;
+ object->refcount = 1;
+
+ const u32 handle = next_handle++;
+
+ handles.insert_or_assign(handle, std::move(object));
+
+ return handle;
+}
+
u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
IocCreateParams params;
std::memcpy(&params, input.data(), sizeof(params));
@@ -59,17 +79,8 @@ u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_ERROR(Service_NVDRV, "Size is 0");
return static_cast<u32>(NvErrCodes::InvalidValue);
}
- // Create a new nvmap object and obtain a handle to it.
- auto object = std::make_shared<Object>();
- object->id = next_id++;
- object->size = params.size;
- object->status = Object::Status::Created;
- object->refcount = 1;
-
- u32 handle = next_handle++;
- handles[handle] = std::move(object);
- params.handle = handle;
+ params.handle = CreateObject(params.size);
std::memcpy(output.data(), &params, sizeof(params));
return 0;
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h
index 73c2e8809..84624be00 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.h
+++ b/src/core/hle/service/nvdrv/devices/nvmap.h
@@ -49,10 +49,10 @@ public:
private:
/// Id to use for the next handle that is created.
- u32 next_handle = 1;
+ u32 next_handle = 0;
/// Id to use for the next object that is created.
- u32 next_id = 1;
+ u32 next_id = 0;
/// Mapping of currently allocated handles to the objects they represent.
std::unordered_map<u32, std::shared_ptr<Object>> handles;
@@ -119,6 +119,8 @@ private:
};
static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size");
+ u32 CreateObject(u32 size);
+
u32 IocCreate(const std::vector<u8>& input, std::vector<u8>& output);
u32 IocAlloc(const std::vector<u8>& input, std::vector<u8>& output);
u32 IocGetId(const std::vector<u8>& input, std::vector<u8>& output);
diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp
index deaf0808b..88fbfa9b0 100644
--- a/src/core/hle/service/nvdrv/interface.cpp
+++ b/src/core/hle/service/nvdrv/interface.cpp
@@ -60,24 +60,24 @@ void NVDRV::IoctlBase(Kernel::HLERequestContext& ctx, IoctlVersion version) {
if (ctrl.must_delay) {
ctrl.fresh_call = false;
- ctx.SleepClientThread("NVServices::DelayedResponse", ctrl.timeout,
- [=](std::shared_ptr<Kernel::Thread> thread,
- Kernel::HLERequestContext& ctx,
- Kernel::ThreadWakeupReason reason) {
- IoctlCtrl ctrl2{ctrl};
- std::vector<u8> tmp_output = output;
- std::vector<u8> tmp_output2 = output2;
- u32 result = nvdrv->Ioctl(fd, command, input, input2, tmp_output,
- tmp_output2, ctrl2, version);
- ctx.WriteBuffer(tmp_output, 0);
- if (version == IoctlVersion::Version3) {
- ctx.WriteBuffer(tmp_output2, 1);
- }
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push(result);
- },
- nvdrv->GetEventWriteable(ctrl.event_id));
+ ctx.SleepClientThread(
+ "NVServices::DelayedResponse", ctrl.timeout,
+ [=, this](std::shared_ptr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx_,
+ Kernel::ThreadWakeupReason reason) {
+ IoctlCtrl ctrl2{ctrl};
+ std::vector<u8> tmp_output = output;
+ std::vector<u8> tmp_output2 = output2;
+ const u32 ioctl_result = nvdrv->Ioctl(fd, command, input, input2, tmp_output,
+ tmp_output2, ctrl2, version);
+ ctx_.WriteBuffer(tmp_output, 0);
+ if (version == IoctlVersion::Version3) {
+ ctx_.WriteBuffer(tmp_output2, 1);
+ }
+ IPC::ResponseBuilder rb{ctx_, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(ioctl_result);
+ },
+ nvdrv->GetEventWriteable(ctrl.event_id));
} else {
ctx.WriteBuffer(output);
if (version == IoctlVersion::Version3) {
diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp
index caca80dde..637b310d7 100644
--- a/src/core/hle/service/nvflinger/buffer_queue.cpp
+++ b/src/core/hle/service/nvflinger/buffer_queue.cpp
@@ -24,13 +24,13 @@ BufferQueue::~BufferQueue() = default;
void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) {
LOG_WARNING(Service, "Adding graphics buffer {}", slot);
- Buffer buffer{};
- buffer.slot = slot;
- buffer.igbp_buffer = igbp_buffer;
- buffer.status = Buffer::Status::Free;
free_buffers.push_back(slot);
+ queue.push_back({
+ .slot = slot,
+ .status = Buffer::Status::Free,
+ .igbp_buffer = igbp_buffer,
+ });
- queue.emplace_back(buffer);
buffer_wait_event.writable->Signal();
}
@@ -38,7 +38,7 @@ std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::Dequeue
u32 height) {
if (free_buffers.empty()) {
- return {};
+ return std::nullopt;
}
auto f_itr = free_buffers.begin();
@@ -69,7 +69,7 @@ std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::Dequeue
}
if (itr == queue.end()) {
- return {};
+ return std::nullopt;
}
itr->status = Buffer::Status::Dequeued;
@@ -103,14 +103,15 @@ std::optional<std::reference_wrapper<const BufferQueue::Buffer>> BufferQueue::Ac
auto itr = queue.end();
// Iterate to find a queued buffer matching the requested slot.
while (itr == queue.end() && !queue_sequence.empty()) {
- u32 slot = queue_sequence.front();
+ const u32 slot = queue_sequence.front();
itr = std::find_if(queue.begin(), queue.end(), [&slot](const Buffer& buffer) {
return buffer.status == Buffer::Status::Queued && buffer.slot == slot;
});
queue_sequence.pop_front();
}
- if (itr == queue.end())
- return {};
+ if (itr == queue.end()) {
+ return std::nullopt;
+ }
itr->status = Buffer::Status::Acquired;
return *itr;
}
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index 789856118..f644a460d 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -67,8 +67,8 @@ NVFlinger::NVFlinger(Core::System& system) : system(system) {
// Schedule the screen composition events
composition_event = Core::Timing::CreateEvent(
- "ScreenComposition", [this](u64, std::chrono::nanoseconds ns_late) {
- Lock();
+ "ScreenComposition", [this](std::uintptr_t, std::chrono::nanoseconds ns_late) {
+ const auto guard = Lock();
Compose();
const auto ticks = std::chrono::nanoseconds{GetNextTicks()};
diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h
index e4959a9af..1ebe949c0 100644
--- a/src/core/hle/service/nvflinger/nvflinger.h
+++ b/src/core/hle/service/nvflinger/nvflinger.h
@@ -54,12 +54,12 @@ public:
/// Opens the specified display and returns the ID.
///
/// If an invalid display name is provided, then an empty optional is returned.
- std::optional<u64> OpenDisplay(std::string_view name);
+ [[nodiscard]] std::optional<u64> OpenDisplay(std::string_view name);
/// Creates a layer on the specified display and returns the layer ID.
///
/// If an invalid display ID is specified, then an empty optional is returned.
- std::optional<u64> CreateLayer(u64 display_id);
+ [[nodiscard]] std::optional<u64> CreateLayer(u64 display_id);
/// Closes a layer on all displays for the given layer ID.
void CloseLayer(u64 layer_id);
@@ -67,41 +67,41 @@ public:
/// Finds the buffer queue ID of the specified layer in the specified display.
///
/// If an invalid display ID or layer ID is provided, then an empty optional is returned.
- std::optional<u32> FindBufferQueueId(u64 display_id, u64 layer_id) const;
+ [[nodiscard]] std::optional<u32> FindBufferQueueId(u64 display_id, u64 layer_id) const;
/// Gets the vsync event for the specified display.
///
/// If an invalid display ID is provided, then nullptr is returned.
- std::shared_ptr<Kernel::ReadableEvent> FindVsyncEvent(u64 display_id) const;
+ [[nodiscard]] std::shared_ptr<Kernel::ReadableEvent> FindVsyncEvent(u64 display_id) const;
/// Obtains a buffer queue identified by the ID.
- BufferQueue& FindBufferQueue(u32 id);
+ [[nodiscard]] BufferQueue& FindBufferQueue(u32 id);
/// Obtains a buffer queue identified by the ID.
- const BufferQueue& FindBufferQueue(u32 id) const;
+ [[nodiscard]] const BufferQueue& FindBufferQueue(u32 id) const;
/// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when
/// finished.
void Compose();
- s64 GetNextTicks() const;
+ [[nodiscard]] s64 GetNextTicks() const;
- std::unique_lock<std::mutex> Lock() {
+ [[nodiscard]] std::unique_lock<std::mutex> Lock() const {
return std::unique_lock{*guard};
}
private:
/// Finds the display identified by the specified ID.
- VI::Display* FindDisplay(u64 display_id);
+ [[nodiscard]] VI::Display* FindDisplay(u64 display_id);
/// Finds the display identified by the specified ID.
- const VI::Display* FindDisplay(u64 display_id) const;
+ [[nodiscard]] const VI::Display* FindDisplay(u64 display_id) const;
/// Finds the layer identified by the specified ID in the desired display.
- VI::Layer* FindLayer(u64 display_id, u64 layer_id);
+ [[nodiscard]] VI::Layer* FindLayer(u64 display_id, u64 layer_id);
/// Finds the layer identified by the specified ID in the desired display.
- const VI::Layer* FindLayer(u64 display_id, u64 layer_id) const;
+ [[nodiscard]] const VI::Layer* FindLayer(u64 display_id, u64 layer_id) const;
static void VSyncThread(NVFlinger& nv_flinger);
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index fa5347af9..538f28495 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -246,7 +246,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
PSC::InstallInterfaces(*sm);
PSM::InstallInterfaces(*sm);
Set::InstallInterfaces(*sm);
- Sockets::InstallInterfaces(*sm);
+ Sockets::InstallInterfaces(*sm, system);
SPL::InstallInterfaces(*sm);
SSL::InstallInterfaces(*sm);
Time::InstallInterfaces(system);
diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp
index 34fe2fd82..e64777668 100644
--- a/src/core/hle/service/set/set.cpp
+++ b/src/core/hle/service/set/set.cpp
@@ -106,7 +106,7 @@ void GetKeyCodeMapImpl(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- ctx.WriteBuffer(&layout, sizeof(KeyboardLayout));
+ ctx.WriteBuffer(layout);
}
} // Anonymous namespace
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index b06d2f103..aabf166b7 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -9,6 +9,7 @@
#include <type_traits>
#include <unordered_map>
+#include "common/concepts.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/server_port.h"
@@ -56,10 +57,8 @@ public:
ResultVal<std::shared_ptr<Kernel::ClientPort>> GetServicePort(const std::string& name);
ResultVal<std::shared_ptr<Kernel::ClientSession>> ConnectToService(const std::string& name);
- template <typename T>
+ template <Common::DerivedFrom<Kernel::SessionRequestHandler> T>
std::shared_ptr<T> GetService(const std::string& service_name) const {
- static_assert(std::is_base_of_v<Kernel::SessionRequestHandler, T>,
- "Not a base of ServiceFrameworkBase");
auto service = registered_services.find(service_name);
if (service == registered_services.end()) {
LOG_DEBUG(Service, "Can't find service: {}", service_name);
diff --git a/src/core/hle/service/sockets/blocking_worker.h b/src/core/hle/service/sockets/blocking_worker.h
new file mode 100644
index 000000000..31ef6b821
--- /dev/null
+++ b/src/core/hle/service/sockets/blocking_worker.h
@@ -0,0 +1,162 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <thread>
+#include <variant>
+#include <vector>
+
+#include <fmt/format.h>
+
+#include "common/assert.h"
+#include "common/microprofile.h"
+#include "common/thread.h"
+#include "core/core.h"
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/thread.h"
+#include "core/hle/kernel/writable_event.h"
+
+namespace Service::Sockets {
+
+/**
+ * Worker abstraction to execute blocking calls on host without blocking the guest thread
+ *
+ * @tparam Service Service where the work is executed
+ * @tparam ...Types Types of work to execute
+ */
+template <class Service, class... Types>
+class BlockingWorker {
+ using This = BlockingWorker<Service, Types...>;
+ using WorkVariant = std::variant<std::monostate, Types...>;
+
+public:
+ /// Create a new worker
+ static std::unique_ptr<This> Create(Core::System& system, Service* service,
+ std::string_view name) {
+ return std::unique_ptr<This>(new This(system, service, name));
+ }
+
+ ~BlockingWorker() {
+ while (!is_available.load(std::memory_order_relaxed)) {
+ // Busy wait until work is finished
+ std::this_thread::yield();
+ }
+ // Monostate means to exit the thread
+ work = std::monostate{};
+ work_event.Set();
+ thread.join();
+ }
+
+ /**
+ * Try to capture the worker to send work after a success
+ * @returns True when the worker has been successfully captured
+ */
+ bool TryCapture() {
+ bool expected = true;
+ return is_available.compare_exchange_weak(expected, false, std::memory_order_relaxed,
+ std::memory_order_relaxed);
+ }
+
+ /**
+ * Send work to this worker abstraction
+ * @see TryCapture must be called before attempting to call this function
+ */
+ template <class Work>
+ void SendWork(Work new_work) {
+ ASSERT_MSG(!is_available, "Trying to send work on a worker that's not captured");
+ work = std::move(new_work);
+ work_event.Set();
+ }
+
+ /// Generate a callback for @see SleepClientThread
+ template <class Work>
+ auto Callback() {
+ return [this](std::shared_ptr<Kernel::Thread>, Kernel::HLERequestContext& ctx,
+ Kernel::ThreadWakeupReason reason) {
+ ASSERT(reason == Kernel::ThreadWakeupReason::Signal);
+ std::get<Work>(work).Response(ctx);
+ is_available.store(true);
+ };
+ }
+
+ /// Get kernel event that will be signalled by the worker when the host operation finishes
+ std::shared_ptr<Kernel::WritableEvent> KernelEvent() const {
+ return kernel_event;
+ }
+
+private:
+ explicit BlockingWorker(Core::System& system, Service* service, std::string_view name) {
+ auto pair = Kernel::WritableEvent::CreateEventPair(system.Kernel(), std::string(name));
+ kernel_event = std::move(pair.writable);
+ thread = std::thread([this, &system, service, name] { Run(system, service, name); });
+ }
+
+ void Run(Core::System& system, Service* service, std::string_view name) {
+ system.RegisterHostThread();
+
+ const std::string thread_name = fmt::format("yuzu:{}", name);
+ MicroProfileOnThreadCreate(thread_name.c_str());
+ Common::SetCurrentThreadName(thread_name.c_str());
+
+ bool keep_running = true;
+ while (keep_running) {
+ work_event.Wait();
+
+ const auto visit_fn = [service, &keep_running](auto&& w) {
+ using T = std::decay_t<decltype(w)>;
+ if constexpr (std::is_same_v<T, std::monostate>) {
+ keep_running = false;
+ } else {
+ w.Execute(service);
+ }
+ };
+ std::visit(visit_fn, work);
+
+ kernel_event->Signal();
+ }
+ }
+
+ std::thread thread;
+ WorkVariant work;
+ Common::Event work_event;
+ std::shared_ptr<Kernel::WritableEvent> kernel_event;
+ std::atomic_bool is_available{true};
+};
+
+template <class Service, class... Types>
+class BlockingWorkerPool {
+ using Worker = BlockingWorker<Service, Types...>;
+
+public:
+ explicit BlockingWorkerPool(Core::System& system_, Service* service_)
+ : system{system_}, service{service_} {}
+
+ /// Returns a captured worker thread, creating new ones if necessary
+ Worker* CaptureWorker() {
+ for (auto& worker : workers) {
+ if (worker->TryCapture()) {
+ return worker.get();
+ }
+ }
+ auto new_worker = Worker::Create(system, service, fmt::format("BSD:{}", workers.size()));
+ [[maybe_unused]] const bool success = new_worker->TryCapture();
+ ASSERT(success);
+
+ return workers.emplace_back(std::move(new_worker)).get();
+ }
+
+private:
+ Core::System& system;
+ Service* const service;
+
+ std::vector<std::unique_ptr<Worker>> workers;
+};
+
+} // namespace Service::Sockets
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index 8d4952c0e..803505452 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -2,18 +2,138 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <fmt/format.h>
+
+#include "common/microprofile.h"
+#include "common/thread.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/thread.h"
#include "core/hle/service/sockets/bsd.h"
+#include "core/hle/service/sockets/sockets_translate.h"
+#include "core/network/network.h"
+#include "core/network/sockets.h"
namespace Service::Sockets {
+namespace {
+
+bool IsConnectionBased(Type type) {
+ switch (type) {
+ case Type::STREAM:
+ return true;
+ case Type::DGRAM:
+ return false;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented type={}", static_cast<int>(type));
+ return false;
+ }
+}
+
+} // Anonymous namespace
+
+void BSD::PollWork::Execute(BSD* bsd) {
+ std::tie(ret, bsd_errno) = bsd->PollImpl(write_buffer, read_buffer, nfds, timeout);
+}
+
+void BSD::PollWork::Response(Kernel::HLERequestContext& ctx) {
+ ctx.WriteBuffer(write_buffer);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
+}
+
+void BSD::AcceptWork::Execute(BSD* bsd) {
+ std::tie(ret, bsd_errno) = bsd->AcceptImpl(fd, write_buffer);
+}
+
+void BSD::AcceptWork::Response(Kernel::HLERequestContext& ctx) {
+ ctx.WriteBuffer(write_buffer);
+
+ IPC::ResponseBuilder rb{ctx, 5};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
+ rb.Push<u32>(static_cast<u32>(write_buffer.size()));
+}
+
+void BSD::ConnectWork::Execute(BSD* bsd) {
+ bsd_errno = bsd->ConnectImpl(fd, addr);
+}
+
+void BSD::ConnectWork::Response(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(bsd_errno == Errno::SUCCESS ? 0 : -1);
+ rb.PushEnum(bsd_errno);
+}
+
+void BSD::RecvWork::Execute(BSD* bsd) {
+ std::tie(ret, bsd_errno) = bsd->RecvImpl(fd, flags, message);
+}
+
+void BSD::RecvWork::Response(Kernel::HLERequestContext& ctx) {
+ ctx.WriteBuffer(message);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
+}
+
+void BSD::RecvFromWork::Execute(BSD* bsd) {
+ std::tie(ret, bsd_errno) = bsd->RecvFromImpl(fd, flags, message, addr);
+}
+
+void BSD::RecvFromWork::Response(Kernel::HLERequestContext& ctx) {
+ ctx.WriteBuffer(message, 0);
+ if (!addr.empty()) {
+ ctx.WriteBuffer(addr, 1);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 5};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
+ rb.Push<u32>(static_cast<u32>(addr.size()));
+}
+
+void BSD::SendWork::Execute(BSD* bsd) {
+ std::tie(ret, bsd_errno) = bsd->SendImpl(fd, flags, message);
+}
+
+void BSD::SendWork::Response(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
+}
+
+void BSD::SendToWork::Execute(BSD* bsd) {
+ std::tie(ret, bsd_errno) = bsd->SendToImpl(fd, flags, message, addr);
+}
+
+void BSD::SendToWork::Response(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
+}
+
void BSD::RegisterClient(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // bsd errno
+ rb.Push<s32>(0); // bsd errno
}
void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) {
@@ -26,20 +146,19 @@ void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) {
void BSD::Socket(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
+ const u32 domain = rp.Pop<u32>();
+ const u32 type = rp.Pop<u32>();
+ const u32 protocol = rp.Pop<u32>();
- u32 domain = rp.Pop<u32>();
- u32 type = rp.Pop<u32>();
- u32 protocol = rp.Pop<u32>();
-
- LOG_WARNING(Service, "(STUBBED) called domain={} type={} protocol={}", domain, type, protocol);
+ LOG_DEBUG(Service, "called. domain={} type={} protocol={}", domain, type, protocol);
- u32 fd = next_fd++;
+ const auto [fd, bsd_errno] = SocketImpl(static_cast<Domain>(domain), static_cast<Type>(type),
+ static_cast<Protocol>(protocol));
IPC::ResponseBuilder rb{ctx, 4};
-
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(fd);
- rb.Push<u32>(0); // bsd errno
+ rb.Push<s32>(fd);
+ rb.PushEnum(bsd_errno);
}
void BSD::Select(Kernel::HLERequestContext& ctx) {
@@ -52,67 +171,663 @@ void BSD::Select(Kernel::HLERequestContext& ctx) {
rb.Push<u32>(0); // bsd errno
}
+void BSD::Poll(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const s32 nfds = rp.Pop<s32>();
+ const s32 timeout = rp.Pop<s32>();
+
+ LOG_DEBUG(Service, "called. nfds={} timeout={}", nfds, timeout);
+
+ ExecuteWork(ctx, "BSD:Poll", timeout != 0,
+ PollWork{
+ .nfds = nfds,
+ .timeout = timeout,
+ .read_buffer = ctx.ReadBuffer(),
+ .write_buffer = std::vector<u8>(ctx.GetWriteBufferSize()),
+ });
+}
+
+void BSD::Accept(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+
+ LOG_DEBUG(Service, "called. fd={}", fd);
+
+ ExecuteWork(ctx, "BSD:Accept", IsBlockingSocket(fd),
+ AcceptWork{
+ .fd = fd,
+ .write_buffer = std::vector<u8>(ctx.GetWriteBufferSize()),
+ });
+}
+
void BSD::Bind(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
- IPC::ResponseBuilder rb{ctx, 4};
+ LOG_DEBUG(Service, "called. fd={} addrlen={}", fd, ctx.GetReadBufferSize());
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
+ BuildErrnoResponse(ctx, BindImpl(fd, ctx.ReadBuffer()));
}
void BSD::Connect(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
- IPC::ResponseBuilder rb{ctx, 4};
+ LOG_DEBUG(Service, "called. fd={} addrlen={}", fd, ctx.GetReadBufferSize());
+
+ ExecuteWork(ctx, "BSD:Connect", IsBlockingSocket(fd),
+ ConnectWork{
+ .fd = fd,
+ .addr = ctx.ReadBuffer(),
+ });
+}
+
+void BSD::GetPeerName(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+
+ LOG_DEBUG(Service, "called. fd={}", fd);
+ std::vector<u8> write_buffer(ctx.GetWriteBufferSize());
+ const Errno bsd_errno = GetPeerNameImpl(fd, write_buffer);
+
+ ctx.WriteBuffer(write_buffer);
+
+ IPC::ResponseBuilder rb{ctx, 5};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
+ rb.Push<s32>(bsd_errno != Errno::SUCCESS ? -1 : 0);
+ rb.PushEnum(bsd_errno);
+ rb.Push<u32>(static_cast<u32>(write_buffer.size()));
+}
+
+void BSD::GetSockName(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+
+ LOG_DEBUG(Service, "called. fd={}", fd);
+
+ std::vector<u8> write_buffer(ctx.GetWriteBufferSize());
+ const Errno bsd_errno = GetSockNameImpl(fd, write_buffer);
+
+ ctx.WriteBuffer(write_buffer);
+
+ IPC::ResponseBuilder rb{ctx, 5};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(bsd_errno != Errno::SUCCESS ? -1 : 0);
+ rb.PushEnum(bsd_errno);
+ rb.Push<u32>(static_cast<u32>(write_buffer.size()));
}
void BSD::Listen(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+ const s32 backlog = rp.Pop<s32>();
- IPC::ResponseBuilder rb{ctx, 4};
+ LOG_DEBUG(Service, "called. fd={} backlog={}", fd, backlog);
+
+ BuildErrnoResponse(ctx, ListenImpl(fd, backlog));
+}
+
+void BSD::Fcntl(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+ const s32 cmd = rp.Pop<s32>();
+ const s32 arg = rp.Pop<s32>();
+ LOG_DEBUG(Service, "called. fd={} cmd={} arg={}", fd, cmd, arg);
+
+ const auto [ret, bsd_errno] = FcntlImpl(fd, static_cast<FcntlCmd>(cmd), arg);
+
+ IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
}
void BSD::SetSockOpt(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
- IPC::ResponseBuilder rb{ctx, 4};
+ const s32 fd = rp.Pop<s32>();
+ const u32 level = rp.Pop<u32>();
+ const OptName optname = static_cast<OptName>(rp.Pop<u32>());
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
+ const std::vector<u8> 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());
+ }
+
+ LOG_DEBUG(Service, "called. fd={} level={} optname=0x{:x} optlen={}", fd, level,
+ static_cast<u32>(optname), optlen);
+
+ BuildErrnoResponse(ctx, SetSockOptImpl(fd, level, optname, optlen, optval));
+}
+
+void BSD::Shutdown(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const s32 fd = rp.Pop<s32>();
+ const s32 how = rp.Pop<s32>();
+
+ LOG_DEBUG(Service, "called. fd={} how={}", fd, how);
+
+ BuildErrnoResponse(ctx, ShutdownImpl(fd, how));
+}
+
+void BSD::Recv(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const s32 fd = rp.Pop<s32>();
+ const u32 flags = rp.Pop<u32>();
+
+ LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetWriteBufferSize());
+
+ ExecuteWork(ctx, "BSD:Recv", IsBlockingSocket(fd),
+ RecvWork{
+ .fd = fd,
+ .flags = flags,
+ .message = std::vector<u8>(ctx.GetWriteBufferSize()),
+ });
+}
+
+void BSD::RecvFrom(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const s32 fd = rp.Pop<s32>();
+ const u32 flags = rp.Pop<u32>();
+
+ LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={} addrlen={}", fd, flags,
+ ctx.GetWriteBufferSize(0), ctx.GetWriteBufferSize(1));
+
+ ExecuteWork(ctx, "BSD:RecvFrom", IsBlockingSocket(fd),
+ RecvFromWork{
+ .fd = fd,
+ .flags = flags,
+ .message = std::vector<u8>(ctx.GetWriteBufferSize(0)),
+ .addr = std::vector<u8>(ctx.GetWriteBufferSize(1)),
+ });
+}
+
+void BSD::Send(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const s32 fd = rp.Pop<s32>();
+ const u32 flags = rp.Pop<u32>();
+
+ LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetReadBufferSize());
+
+ ExecuteWork(ctx, "BSD:Send", IsBlockingSocket(fd),
+ SendWork{
+ .fd = fd,
+ .flags = flags,
+ .message = ctx.ReadBuffer(),
+ });
}
void BSD::SendTo(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+ const u32 flags = rp.Pop<u32>();
+
+ LOG_DEBUG(Service, "called. fd={} flags=0x{} len={} addrlen={}", fd, flags,
+ ctx.GetReadBufferSize(0), ctx.GetReadBufferSize(1));
+
+ ExecuteWork(ctx, "BSD:SendTo", IsBlockingSocket(fd),
+ SendToWork{
+ .fd = fd,
+ .flags = flags,
+ .message = ctx.ReadBuffer(0),
+ .addr = ctx.ReadBuffer(1),
+ });
+}
- IPC::ResponseBuilder rb{ctx, 4};
+void BSD::Write(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
+ LOG_DEBUG(Service, "called. fd={} len={}", fd, ctx.GetReadBufferSize());
+
+ ExecuteWork(ctx, "BSD:Write", IsBlockingSocket(fd),
+ SendWork{
+ .fd = fd,
+ .flags = 0,
+ .message = ctx.ReadBuffer(),
+ });
}
void BSD::Close(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+
+ LOG_DEBUG(Service, "called. fd={}", fd);
+
+ BuildErrnoResponse(ctx, CloseImpl(fd));
+}
+
+template <typename Work>
+void BSD::ExecuteWork(Kernel::HLERequestContext& ctx, std::string_view sleep_reason,
+ bool is_blocking, Work work) {
+ if (!is_blocking) {
+ work.Execute(this);
+ work.Response(ctx);
+ return;
+ }
+
+ // Signal a dummy response to make IPC validation happy
+ // This will be overwritten by the SleepClientThread callback
+ work.Response(ctx);
+
+ auto worker = worker_pool.CaptureWorker();
+
+ ctx.SleepClientThread(std::string(sleep_reason), std::numeric_limits<u64>::max(),
+ worker->Callback<Work>(), worker->KernelEvent());
+
+ worker->SendWork(std::move(work));
+}
+
+std::pair<s32, Errno> BSD::SocketImpl(Domain domain, Type type, Protocol protocol) {
+ if (type == Type::SEQPACKET) {
+ UNIMPLEMENTED_MSG("SOCK_SEQPACKET errno management");
+ } else if (type == Type::RAW && (domain != Domain::INET || protocol != Protocol::ICMP)) {
+ UNIMPLEMENTED_MSG("SOCK_RAW errno management");
+ }
+
+ [[maybe_unused]] const bool unk_flag = (static_cast<u32>(type) & 0x20000000) != 0;
+ UNIMPLEMENTED_IF_MSG(unk_flag, "Unknown flag in type");
+ type = static_cast<Type>(static_cast<u32>(type) & ~0x20000000);
+
+ const s32 fd = FindFreeFileDescriptorHandle();
+ if (fd < 0) {
+ LOG_ERROR(Service, "No more file descriptors available");
+ return {-1, Errno::MFILE};
+ }
+
+ FileDescriptor& descriptor = file_descriptors[fd].emplace();
+ // ENONMEM might be thrown here
+
+ LOG_INFO(Service, "New socket fd={}", fd);
+
+ descriptor.socket = std::make_unique<Network::Socket>();
+ descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(type, protocol));
+ descriptor.is_connection_based = IsConnectionBased(type);
+
+ return {fd, Errno::SUCCESS};
+}
+std::pair<s32, Errno> BSD::PollImpl(std::vector<u8>& write_buffer, std::vector<u8> read_buffer,
+ s32 nfds, s32 timeout) {
+ if (write_buffer.size() < nfds * sizeof(PollFD)) {
+ return {-1, Errno::INVAL};
+ }
+
+ if (nfds == 0) {
+ // When no entries are provided, -1 is returned with errno zero
+ return {-1, Errno::SUCCESS};
+ }
+
+ 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);
+
+ if (timeout >= 0) {
+ const s64 seconds = timeout / 1000;
+ const u64 nanoseconds = 1'000'000 * (static_cast<u64>(timeout) % 1000);
+
+ if (seconds < 0) {
+ return {-1, Errno::INVAL};
+ }
+ if (nanoseconds > 999'999'999) {
+ return {-1, Errno::INVAL};
+ }
+ } else if (timeout != -1) {
+ return {-1, Errno::INVAL};
+ }
+
+ for (PollFD& pollfd : fds) {
+ ASSERT(pollfd.revents == 0);
+
+ if (pollfd.fd > MAX_FD || pollfd.fd < 0) {
+ LOG_ERROR(Service, "File descriptor handle={} is invalid", pollfd.fd);
+ pollfd.revents = 0;
+ return {0, Errno::SUCCESS};
+ }
+
+ std::optional<FileDescriptor>& descriptor = file_descriptors[pollfd.fd];
+ if (!descriptor) {
+ LOG_ERROR(Service, "File descriptor handle={} is not allocated", pollfd.fd);
+ pollfd.revents = POLL_NVAL;
+ return {0, Errno::SUCCESS};
+ }
+ }
+
+ std::vector<Network::PollFD> host_pollfds(fds.size());
+ std::transform(fds.begin(), fds.end(), host_pollfds.begin(), [this](PollFD pollfd) {
+ Network::PollFD result;
+ result.socket = file_descriptors[pollfd.fd]->socket.get();
+ result.events = TranslatePollEventsToHost(pollfd.events);
+ result.revents = 0;
+ return result;
+ });
+
+ const auto result = Network::Poll(host_pollfds, timeout);
+
+ const size_t num = host_pollfds.size();
+ for (size_t i = 0; i < num; ++i) {
+ fds[i].revents = TranslatePollEventsToGuest(host_pollfds[i].revents);
+ }
+ std::memcpy(write_buffer.data(), fds.data(), length);
+
+ return Translate(result);
+}
+
+std::pair<s32, Errno> BSD::AcceptImpl(s32 fd, std::vector<u8>& write_buffer) {
+ if (!IsFileDescriptorValid(fd)) {
+ return {-1, Errno::BADF};
+ }
+
+ const s32 new_fd = FindFreeFileDescriptorHandle();
+ if (new_fd < 0) {
+ LOG_ERROR(Service, "No more file descriptors available");
+ return {-1, Errno::MFILE};
+ }
+
+ FileDescriptor& descriptor = *file_descriptors[fd];
+ auto [result, bsd_errno] = descriptor.socket->Accept();
+ if (bsd_errno != Network::Errno::SUCCESS) {
+ return {-1, Translate(bsd_errno)};
+ }
+
+ FileDescriptor& new_descriptor = file_descriptors[new_fd].emplace();
+ new_descriptor.socket = std::move(result.socket);
+ new_descriptor.is_connection_based = descriptor.is_connection_based;
+
+ ASSERT(write_buffer.size() == sizeof(SockAddrIn));
+ const SockAddrIn guest_addr_in = Translate(result.sockaddr_in);
+ std::memcpy(write_buffer.data(), &guest_addr_in, sizeof(guest_addr_in));
+
+ return {new_fd, Errno::SUCCESS};
+}
+
+Errno BSD::BindImpl(s32 fd, const std::vector<u8>& addr) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+ ASSERT(addr.size() == sizeof(SockAddrIn));
+ SockAddrIn addr_in;
+ std::memcpy(&addr_in, addr.data(), sizeof(addr_in));
+
+ return Translate(file_descriptors[fd]->socket->Bind(Translate(addr_in)));
+}
+
+Errno BSD::ConnectImpl(s32 fd, const std::vector<u8>& addr) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+
+ UNIMPLEMENTED_IF(addr.size() != sizeof(SockAddrIn));
+ SockAddrIn addr_in;
+ std::memcpy(&addr_in, addr.data(), sizeof(addr_in));
+
+ return Translate(file_descriptors[fd]->socket->Connect(Translate(addr_in)));
+}
+
+Errno BSD::GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+
+ const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetPeerName();
+ if (bsd_errno != Network::Errno::SUCCESS) {
+ return Translate(bsd_errno);
+ }
+ const SockAddrIn guest_addrin = Translate(addr_in);
+
+ ASSERT(write_buffer.size() == sizeof(guest_addrin));
+ std::memcpy(write_buffer.data(), &guest_addrin, sizeof(guest_addrin));
+ return Translate(bsd_errno);
+}
+
+Errno BSD::GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+
+ const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetSockName();
+ if (bsd_errno != Network::Errno::SUCCESS) {
+ return Translate(bsd_errno);
+ }
+ const SockAddrIn guest_addrin = Translate(addr_in);
+
+ ASSERT(write_buffer.size() == sizeof(guest_addrin));
+ std::memcpy(write_buffer.data(), &guest_addrin, sizeof(guest_addrin));
+ return Translate(bsd_errno);
+}
+
+Errno BSD::ListenImpl(s32 fd, s32 backlog) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+ return Translate(file_descriptors[fd]->socket->Listen(backlog));
+}
+
+std::pair<s32, Errno> BSD::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) {
+ if (!IsFileDescriptorValid(fd)) {
+ return {-1, Errno::BADF};
+ }
+
+ FileDescriptor& descriptor = *file_descriptors[fd];
+
+ switch (cmd) {
+ case FcntlCmd::GETFL:
+ ASSERT(arg == 0);
+ return {descriptor.flags, Errno::SUCCESS};
+ case FcntlCmd::SETFL: {
+ const bool enable = (arg & FLAG_O_NONBLOCK) != 0;
+ const Errno bsd_errno = Translate(descriptor.socket->SetNonBlock(enable));
+ if (bsd_errno != Errno::SUCCESS) {
+ return {-1, bsd_errno};
+ }
+ descriptor.flags = arg;
+ return {0, Errno::SUCCESS};
+ }
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented cmd={}", static_cast<int>(cmd));
+ return {-1, Errno::SUCCESS};
+ }
+}
+
+Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval) {
+ UNIMPLEMENTED_IF(level != 0xffff); // SOL_SOCKET
+
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+
+ Network::Socket* const socket = file_descriptors[fd]->socket.get();
+
+ if (optname == OptName::LINGER) {
+ ASSERT(optlen == sizeof(Linger));
+ Linger linger;
+ std::memcpy(&linger, optval, sizeof(linger));
+ 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));
+
+ switch (optname) {
+ case OptName::REUSEADDR:
+ ASSERT(value == 0 || value == 1);
+ return Translate(socket->SetReuseAddr(value != 0));
+ case OptName::BROADCAST:
+ ASSERT(value == 0 || value == 1);
+ return Translate(socket->SetBroadcast(value != 0));
+ case OptName::SNDBUF:
+ return Translate(socket->SetSndBuf(value));
+ case OptName::RCVBUF:
+ return Translate(socket->SetRcvBuf(value));
+ case OptName::SNDTIMEO:
+ return Translate(socket->SetSndTimeo(value));
+ case OptName::RCVTIMEO:
+ return Translate(socket->SetRcvTimeo(value));
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented optname={}", static_cast<int>(optname));
+ return Errno::SUCCESS;
+ }
+}
+
+Errno BSD::ShutdownImpl(s32 fd, s32 how) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+ const Network::ShutdownHow host_how = Translate(static_cast<ShutdownHow>(how));
+ return Translate(file_descriptors[fd]->socket->Shutdown(host_how));
+}
+
+std::pair<s32, Errno> BSD::RecvImpl(s32 fd, u32 flags, std::vector<u8>& message) {
+ if (!IsFileDescriptorValid(fd)) {
+ return {-1, Errno::BADF};
+ }
+ return Translate(file_descriptors[fd]->socket->Recv(flags, message));
+}
+
+std::pair<s32, Errno> BSD::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& message,
+ std::vector<u8>& addr) {
+ if (!IsFileDescriptorValid(fd)) {
+ return {-1, Errno::BADF};
+ }
+
+ FileDescriptor& descriptor = *file_descriptors[fd];
+
+ Network::SockAddrIn addr_in{};
+ Network::SockAddrIn* p_addr_in = nullptr;
+ if (descriptor.is_connection_based) {
+ // Connection based file descriptors (e.g. TCP) zero addr
+ addr.clear();
+ } else {
+ p_addr_in = &addr_in;
+ }
+
+ // Apply flags
+ if ((flags & FLAG_MSG_DONTWAIT) != 0) {
+ flags &= ~FLAG_MSG_DONTWAIT;
+ if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) {
+ descriptor.socket->SetNonBlock(true);
+ }
+ }
+
+ const auto [ret, bsd_errno] = Translate(descriptor.socket->RecvFrom(flags, message, p_addr_in));
+
+ // Restore original state
+ if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) {
+ descriptor.socket->SetNonBlock(false);
+ }
+
+ if (p_addr_in) {
+ if (ret < 0) {
+ addr.clear();
+ } else {
+ ASSERT(addr.size() == sizeof(SockAddrIn));
+ const SockAddrIn result = Translate(addr_in);
+ std::memcpy(addr.data(), &result, sizeof(result));
+ }
+ }
+
+ return {ret, bsd_errno};
+}
+
+std::pair<s32, Errno> BSD::SendImpl(s32 fd, u32 flags, const std::vector<u8>& message) {
+ if (!IsFileDescriptorValid(fd)) {
+ return {-1, Errno::BADF};
+ }
+ return Translate(file_descriptors[fd]->socket->Send(message, flags));
+}
+
+std::pair<s32, Errno> BSD::SendToImpl(s32 fd, u32 flags, const std::vector<u8>& message,
+ const std::vector<u8>& addr) {
+ if (!IsFileDescriptorValid(fd)) {
+ return {-1, Errno::BADF};
+ }
+
+ Network::SockAddrIn addr_in;
+ 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));
+ addr_in = Translate(guest_addr_in);
+ }
+
+ return Translate(file_descriptors[fd]->socket->SendTo(flags, message, p_addr_in));
+}
+
+Errno BSD::CloseImpl(s32 fd) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+
+ const Errno bsd_errno = Translate(file_descriptors[fd]->socket->Close());
+ if (bsd_errno != Errno::SUCCESS) {
+ return bsd_errno;
+ }
+
+ LOG_INFO(Service, "Close socket fd={}", fd);
+
+ file_descriptors[fd].reset();
+ return bsd_errno;
+}
+
+s32 BSD::FindFreeFileDescriptorHandle() noexcept {
+ for (s32 fd = 0; fd < static_cast<s32>(file_descriptors.size()); ++fd) {
+ if (!file_descriptors[fd]) {
+ return fd;
+ }
+ }
+ return -1;
+}
+
+bool BSD::IsFileDescriptorValid(s32 fd) const noexcept {
+ if (fd > MAX_FD || fd < 0) {
+ LOG_ERROR(Service, "Invalid file descriptor handle={}", fd);
+ return false;
+ }
+ if (!file_descriptors[fd]) {
+ LOG_ERROR(Service, "File descriptor handle={} is not allocated", fd);
+ return false;
+ }
+ return true;
+}
+
+bool BSD::IsBlockingSocket(s32 fd) const noexcept {
+ // Inform invalid sockets as non-blocking
+ // This way we avoid using a worker thread as it will fail without blocking host
+ if (fd > MAX_FD || fd < 0) {
+ return false;
+ }
+ if (!file_descriptors[fd]) {
+ return false;
+ }
+ return (file_descriptors[fd]->flags & FLAG_O_NONBLOCK) != 0;
+}
+
+void BSD::BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
+ rb.Push<s32>(bsd_errno == Errno::SUCCESS ? 0 : -1);
+ rb.PushEnum(bsd_errno);
}
-BSD::BSD(const char* name) : ServiceFramework(name) {
+BSD::BSD(Core::System& system, const char* name)
+ : ServiceFramework(name), worker_pool{system, this} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &BSD::RegisterClient, "RegisterClient"},
@@ -121,25 +836,25 @@ BSD::BSD(const char* name) : ServiceFramework(name) {
{3, nullptr, "SocketExempt"},
{4, nullptr, "Open"},
{5, &BSD::Select, "Select"},
- {6, nullptr, "Poll"},
+ {6, &BSD::Poll, "Poll"},
{7, nullptr, "Sysctl"},
- {8, nullptr, "Recv"},
- {9, nullptr, "RecvFrom"},
- {10, nullptr, "Send"},
+ {8, &BSD::Recv, "Recv"},
+ {9, &BSD::RecvFrom, "RecvFrom"},
+ {10, &BSD::Send, "Send"},
{11, &BSD::SendTo, "SendTo"},
- {12, nullptr, "Accept"},
+ {12, &BSD::Accept, "Accept"},
{13, &BSD::Bind, "Bind"},
{14, &BSD::Connect, "Connect"},
- {15, nullptr, "GetPeerName"},
- {16, nullptr, "GetSockName"},
+ {15, &BSD::GetPeerName, "GetPeerName"},
+ {16, &BSD::GetSockName, "GetSockName"},
{17, nullptr, "GetSockOpt"},
{18, &BSD::Listen, "Listen"},
{19, nullptr, "Ioctl"},
- {20, nullptr, "Fcntl"},
+ {20, &BSD::Fcntl, "Fcntl"},
{21, &BSD::SetSockOpt, "SetSockOpt"},
- {22, nullptr, "Shutdown"},
+ {22, &BSD::Shutdown, "Shutdown"},
{23, nullptr, "ShutdownAllSockets"},
- {24, nullptr, "Write"},
+ {24, &BSD::Write, "Write"},
{25, nullptr, "Read"},
{26, &BSD::Close, "Close"},
{27, nullptr, "DuplicateSocket"},
diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h
index 3098e3baf..357531951 100644
--- a/src/core/hle/service/sockets/bsd.h
+++ b/src/core/hle/service/sockets/bsd.h
@@ -4,30 +4,174 @@
#pragma once
+#include <memory>
+#include <string_view>
+#include <vector>
+
+#include "common/common_types.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/service.h"
+#include "core/hle/service/sockets/blocking_worker.h"
+#include "core/hle/service/sockets/sockets.h"
+
+namespace Core {
+class System;
+}
+
+namespace Network {
+class Socket;
+}
namespace Service::Sockets {
class BSD final : public ServiceFramework<BSD> {
public:
- explicit BSD(const char* name);
+ explicit BSD(Core::System& system, const char* name);
~BSD() override;
private:
+ /// Maximum number of file descriptors
+ static constexpr size_t MAX_FD = 128;
+
+ struct FileDescriptor {
+ std::unique_ptr<Network::Socket> socket;
+ s32 flags = 0;
+ bool is_connection_based = false;
+ };
+
+ struct PollWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 nfds;
+ s32 timeout;
+ std::vector<u8> read_buffer;
+ std::vector<u8> write_buffer;
+ s32 ret{};
+ Errno bsd_errno{};
+ };
+
+ struct AcceptWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 fd;
+ std::vector<u8> write_buffer;
+ s32 ret{};
+ Errno bsd_errno{};
+ };
+
+ struct ConnectWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 fd;
+ std::vector<u8> addr;
+ Errno bsd_errno{};
+ };
+
+ struct RecvWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 fd;
+ u32 flags;
+ std::vector<u8> message;
+ s32 ret{};
+ Errno bsd_errno{};
+ };
+
+ struct RecvFromWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 fd;
+ u32 flags;
+ std::vector<u8> message;
+ std::vector<u8> addr;
+ s32 ret{};
+ Errno bsd_errno{};
+ };
+
+ struct SendWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 fd;
+ u32 flags;
+ std::vector<u8> message;
+ s32 ret{};
+ Errno bsd_errno{};
+ };
+
+ struct SendToWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 fd;
+ u32 flags;
+ std::vector<u8> message;
+ std::vector<u8> addr;
+ s32 ret{};
+ Errno bsd_errno{};
+ };
+
void RegisterClient(Kernel::HLERequestContext& ctx);
void StartMonitoring(Kernel::HLERequestContext& ctx);
void Socket(Kernel::HLERequestContext& ctx);
void Select(Kernel::HLERequestContext& ctx);
+ void Poll(Kernel::HLERequestContext& ctx);
+ void Accept(Kernel::HLERequestContext& ctx);
void Bind(Kernel::HLERequestContext& ctx);
void Connect(Kernel::HLERequestContext& ctx);
+ void GetPeerName(Kernel::HLERequestContext& ctx);
+ void GetSockName(Kernel::HLERequestContext& ctx);
void Listen(Kernel::HLERequestContext& ctx);
+ void Fcntl(Kernel::HLERequestContext& ctx);
void SetSockOpt(Kernel::HLERequestContext& ctx);
+ void Shutdown(Kernel::HLERequestContext& ctx);
+ void Recv(Kernel::HLERequestContext& ctx);
+ void RecvFrom(Kernel::HLERequestContext& ctx);
+ void Send(Kernel::HLERequestContext& ctx);
void SendTo(Kernel::HLERequestContext& ctx);
+ void Write(Kernel::HLERequestContext& ctx);
void Close(Kernel::HLERequestContext& ctx);
- /// Id to use for the next open file descriptor.
- u32 next_fd = 1;
+ template <typename Work>
+ void ExecuteWork(Kernel::HLERequestContext& ctx, std::string_view sleep_reason,
+ bool is_blocking, Work work);
+
+ std::pair<s32, Errno> SocketImpl(Domain domain, Type type, Protocol protocol);
+ std::pair<s32, Errno> PollImpl(std::vector<u8>& write_buffer, std::vector<u8> read_buffer,
+ s32 nfds, s32 timeout);
+ std::pair<s32, Errno> AcceptImpl(s32 fd, std::vector<u8>& write_buffer);
+ Errno BindImpl(s32 fd, const std::vector<u8>& addr);
+ Errno ConnectImpl(s32 fd, const std::vector<u8>& addr);
+ Errno GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer);
+ Errno GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer);
+ Errno ListenImpl(s32 fd, s32 backlog);
+ std::pair<s32, Errno> FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg);
+ Errno SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* 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,
+ std::vector<u8>& addr);
+ std::pair<s32, Errno> SendImpl(s32 fd, u32 flags, const std::vector<u8>& message);
+ std::pair<s32, Errno> SendToImpl(s32 fd, u32 flags, const std::vector<u8>& message,
+ const std::vector<u8>& addr);
+ Errno CloseImpl(s32 fd);
+
+ s32 FindFreeFileDescriptorHandle() noexcept;
+ bool IsFileDescriptorValid(s32 fd) const noexcept;
+ bool IsBlockingSocket(s32 fd) const noexcept;
+
+ void BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept;
+
+ std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors;
+
+ BlockingWorkerPool<BSD, PollWork, AcceptWork, ConnectWork, RecvWork, RecvFromWork, SendWork,
+ SendToWork>
+ worker_pool;
};
class BSDCFG final : public ServiceFramework<BSDCFG> {
diff --git a/src/core/hle/service/sockets/sockets.cpp b/src/core/hle/service/sockets/sockets.cpp
index 08d2d306a..1d27f7906 100644
--- a/src/core/hle/service/sockets/sockets.cpp
+++ b/src/core/hle/service/sockets/sockets.cpp
@@ -10,9 +10,9 @@
namespace Service::Sockets {
-void InstallInterfaces(SM::ServiceManager& service_manager) {
- std::make_shared<BSD>("bsd:s")->InstallAsService(service_manager);
- std::make_shared<BSD>("bsd:u")->InstallAsService(service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
+ std::make_shared<BSD>(system, "bsd:s")->InstallAsService(service_manager);
+ std::make_shared<BSD>(system, "bsd:u")->InstallAsService(service_manager);
std::make_shared<BSDCFG>()->InstallAsService(service_manager);
std::make_shared<ETHC_C>()->InstallAsService(service_manager);
diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h
index ca8a6a7e0..89a410076 100644
--- a/src/core/hle/service/sockets/sockets.h
+++ b/src/core/hle/service/sockets/sockets.h
@@ -4,11 +4,94 @@
#pragma once
+#include "common/common_types.h"
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Sockets {
+enum class Errno : u32 {
+ SUCCESS = 0,
+ BADF = 9,
+ AGAIN = 11,
+ INVAL = 22,
+ MFILE = 24,
+ NOTCONN = 107,
+};
+
+enum class Domain : u32 {
+ INET = 2,
+};
+
+enum class Type : u32 {
+ STREAM = 1,
+ DGRAM = 2,
+ RAW = 3,
+ SEQPACKET = 5,
+};
+
+enum class Protocol : u32 {
+ UNSPECIFIED = 0,
+ ICMP = 1,
+ TCP = 6,
+ UDP = 17,
+};
+
+enum class OptName : u32 {
+ REUSEADDR = 0x4,
+ BROADCAST = 0x20,
+ LINGER = 0x80,
+ SNDBUF = 0x1001,
+ RCVBUF = 0x1002,
+ SNDTIMEO = 0x1005,
+ RCVTIMEO = 0x1006,
+};
+
+enum class ShutdownHow : s32 {
+ RD = 0,
+ WR = 1,
+ RDWR = 2,
+};
+
+enum class FcntlCmd : s32 {
+ GETFL = 3,
+ SETFL = 4,
+};
+
+struct SockAddrIn {
+ u8 len;
+ u8 family;
+ u16 portno;
+ std::array<u8, 4> ip;
+ std::array<u8, 8> zeroes;
+};
+
+struct PollFD {
+ s32 fd;
+ u16 events;
+ u16 revents;
+};
+
+struct Linger {
+ u32 onoff;
+ u32 linger;
+};
+
+constexpr u16 POLL_IN = 0x01;
+constexpr u16 POLL_PRI = 0x02;
+constexpr u16 POLL_OUT = 0x04;
+constexpr u16 POLL_ERR = 0x08;
+constexpr u16 POLL_HUP = 0x10;
+constexpr u16 POLL_NVAL = 0x20;
+
+constexpr u32 FLAG_MSG_DONTWAIT = 0x80;
+
+constexpr u32 FLAG_O_NONBLOCK = 0x800;
+
/// Registers all Sockets services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::Sockets
diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp
new file mode 100644
index 000000000..2be8f642d
--- /dev/null
+++ b/src/core/hle/service/sockets/sockets_translate.cpp
@@ -0,0 +1,165 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <utility>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "core/hle/service/sockets/sockets.h"
+#include "core/hle/service/sockets/sockets_translate.h"
+#include "core/network/network.h"
+
+namespace Service::Sockets {
+
+Errno Translate(Network::Errno value) {
+ switch (value) {
+ case Network::Errno::SUCCESS:
+ return Errno::SUCCESS;
+ case Network::Errno::BADF:
+ return Errno::BADF;
+ case Network::Errno::AGAIN:
+ return Errno::AGAIN;
+ case Network::Errno::INVAL:
+ return Errno::INVAL;
+ case Network::Errno::MFILE:
+ return Errno::MFILE;
+ case Network::Errno::NOTCONN:
+ return Errno::NOTCONN;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented errno={}", static_cast<int>(value));
+ return Errno::SUCCESS;
+ }
+}
+
+std::pair<s32, Errno> Translate(std::pair<s32, Network::Errno> value) {
+ return {value.first, Translate(value.second)};
+}
+
+Network::Domain Translate(Domain domain) {
+ switch (domain) {
+ case Domain::INET:
+ return Network::Domain::INET;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented domain={}", static_cast<int>(domain));
+ return {};
+ }
+}
+
+Domain Translate(Network::Domain domain) {
+ switch (domain) {
+ case Network::Domain::INET:
+ return Domain::INET;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented domain={}", static_cast<int>(domain));
+ return {};
+ }
+}
+
+Network::Type Translate(Type type) {
+ switch (type) {
+ case Type::STREAM:
+ return Network::Type::STREAM;
+ case Type::DGRAM:
+ return Network::Type::DGRAM;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented type={}", static_cast<int>(type));
+ }
+}
+
+Network::Protocol Translate(Type type, Protocol protocol) {
+ switch (protocol) {
+ case Protocol::UNSPECIFIED:
+ LOG_WARNING(Service, "Unspecified protocol, assuming protocol from type");
+ switch (type) {
+ case Type::DGRAM:
+ return Network::Protocol::UDP;
+ case Type::STREAM:
+ return Network::Protocol::TCP;
+ default:
+ return Network::Protocol::TCP;
+ }
+ case Protocol::TCP:
+ return Network::Protocol::TCP;
+ case Protocol::UDP:
+ return Network::Protocol::UDP;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented protocol={}", static_cast<int>(protocol));
+ return Network::Protocol::TCP;
+ }
+}
+
+u16 TranslatePollEventsToHost(u16 flags) {
+ u16 result = 0;
+ const auto translate = [&result, &flags](u16 from, u16 to) {
+ if ((flags & from) != 0) {
+ flags &= ~from;
+ result |= to;
+ }
+ };
+ translate(POLL_IN, Network::POLL_IN);
+ translate(POLL_PRI, Network::POLL_PRI);
+ translate(POLL_OUT, Network::POLL_OUT);
+ translate(POLL_ERR, Network::POLL_ERR);
+ translate(POLL_HUP, Network::POLL_HUP);
+ translate(POLL_NVAL, Network::POLL_NVAL);
+
+ UNIMPLEMENTED_IF_MSG(flags != 0, "Unimplemented flags={}", flags);
+ return result;
+}
+
+u16 TranslatePollEventsToGuest(u16 flags) {
+ u16 result = 0;
+ const auto translate = [&result, &flags](u16 from, u16 to) {
+ if ((flags & from) != 0) {
+ flags &= ~from;
+ result |= to;
+ }
+ };
+
+ translate(Network::POLL_IN, POLL_IN);
+ translate(Network::POLL_PRI, POLL_PRI);
+ translate(Network::POLL_OUT, POLL_OUT);
+ translate(Network::POLL_ERR, POLL_ERR);
+ translate(Network::POLL_HUP, POLL_HUP);
+ translate(Network::POLL_NVAL, POLL_NVAL);
+
+ UNIMPLEMENTED_IF_MSG(flags != 0, "Unimplemented flags={}", flags);
+ return result;
+}
+
+Network::SockAddrIn Translate(SockAddrIn value) {
+ ASSERT(value.len == 0 || value.len == sizeof(value));
+
+ Network::SockAddrIn result;
+ result.family = Translate(static_cast<Domain>(value.family));
+ result.ip = value.ip;
+ result.portno = value.portno >> 8 | value.portno << 8;
+ return result;
+}
+
+SockAddrIn Translate(Network::SockAddrIn value) {
+ SockAddrIn result;
+ result.len = sizeof(result);
+ result.family = static_cast<u8>(Translate(value.family));
+ result.portno = value.portno >> 8 | value.portno << 8;
+ result.ip = value.ip;
+ result.zeroes = {};
+ return result;
+}
+
+Network::ShutdownHow Translate(ShutdownHow how) {
+ switch (how) {
+ case ShutdownHow::RD:
+ return Network::ShutdownHow::RD;
+ case ShutdownHow::WR:
+ return Network::ShutdownHow::WR;
+ case ShutdownHow::RDWR:
+ return Network::ShutdownHow::RDWR;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented how={}", static_cast<int>(how));
+ return {};
+ }
+}
+
+} // namespace Service::Sockets
diff --git a/src/core/hle/service/sockets/sockets_translate.h b/src/core/hle/service/sockets/sockets_translate.h
new file mode 100644
index 000000000..8ed041e31
--- /dev/null
+++ b/src/core/hle/service/sockets/sockets_translate.h
@@ -0,0 +1,48 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <utility>
+
+#include "common/common_types.h"
+#include "core/hle/service/sockets/sockets.h"
+#include "core/network/network.h"
+
+namespace Service::Sockets {
+
+/// Translate abstract errno to guest errno
+Errno Translate(Network::Errno value);
+
+/// Translate abstract return value errno pair to guest return value errno pair
+std::pair<s32, Errno> Translate(std::pair<s32, Network::Errno> value);
+
+/// Translate guest domain to abstract domain
+Network::Domain Translate(Domain domain);
+
+/// Translate abstract domain to guest domain
+Domain Translate(Network::Domain domain);
+
+/// Translate guest type to abstract type
+Network::Type Translate(Type type);
+
+/// Translate guest protocol to abstract protocol
+Network::Protocol Translate(Type type, Protocol protocol);
+
+/// Translate abstract poll event flags to guest poll event flags
+u16 TranslatePollEventsToHost(u16 flags);
+
+/// Translate guest poll event flags to abstract poll event flags
+u16 TranslatePollEventsToGuest(u16 flags);
+
+/// Translate guest socket address structure to abstract socket address structure
+Network::SockAddrIn Translate(SockAddrIn value);
+
+/// Translate abstract socket address structure to guest socket address structure
+SockAddrIn Translate(Network::SockAddrIn value);
+
+/// Translate guest shutdown mode to abstract shutdown mode
+Network::ShutdownHow Translate(ShutdownHow how);
+
+} // namespace Service::Sockets
diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp
index 13e4b3818..ee4fa4b48 100644
--- a/src/core/hle/service/time/time.cpp
+++ b/src/core/hle/service/time/time.cpp
@@ -290,7 +290,7 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- ctx.WriteBuffer(&clock_snapshot, sizeof(Clock::ClockSnapshot));
+ ctx.WriteBuffer(clock_snapshot);
}
void Module::Interface::GetClockSnapshotFromSystemClockContext(Kernel::HLERequestContext& ctx) {
@@ -313,7 +313,7 @@ void Module::Interface::GetClockSnapshotFromSystemClockContext(Kernel::HLEReques
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- ctx.WriteBuffer(&clock_snapshot, sizeof(Clock::ClockSnapshot));
+ ctx.WriteBuffer(clock_snapshot);
}
void Module::Interface::CalculateStandardUserSystemClockDifferenceByUser(
diff --git a/src/core/hle/service/time/time_zone_content_manager.cpp b/src/core/hle/service/time/time_zone_content_manager.cpp
index c070d6e97..320672add 100644
--- a/src/core/hle/service/time/time_zone_content_manager.cpp
+++ b/src/core/hle/service/time/time_zone_content_manager.cpp
@@ -73,10 +73,8 @@ TimeZoneContentManager::TimeZoneContentManager(TimeManager& time_manager, Core::
std::string location_name;
const auto timezone_setting = Settings::GetTimeZoneString();
- if (timezone_setting == "auto") {
+ if (timezone_setting == "auto" || timezone_setting == "default") {
location_name = Common::TimeZone::GetDefaultTimeZone();
- } else if (timezone_setting == "default") {
- location_name = location_name;
} else {
location_name = timezone_setting;
}
diff --git a/src/core/hle/service/time/time_zone_service.cpp b/src/core/hle/service/time/time_zone_service.cpp
index db57ae069..ff3a10b3e 100644
--- a/src/core/hle/service/time/time_zone_service.cpp
+++ b/src/core/hle/service/time/time_zone_service.cpp
@@ -142,7 +142,7 @@ void ITimeZoneService::ToPosixTime(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.PushRaw<u32>(1); // Number of times we're returning
- ctx.WriteBuffer(&posix_time, sizeof(s64));
+ ctx.WriteBuffer(posix_time);
}
void ITimeZoneService::ToPosixTimeWithMyRule(Kernel::HLERequestContext& ctx) {
@@ -164,7 +164,7 @@ void ITimeZoneService::ToPosixTimeWithMyRule(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.PushRaw<u32>(1); // Number of times we're returning
- ctx.WriteBuffer(&posix_time, sizeof(s64));
+ ctx.WriteBuffer(posix_time);
}
} // namespace Service::Time
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index ea7b4ae13..480d34725 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -511,7 +511,7 @@ private:
LOG_DEBUG(Service_VI, "called. id=0x{:08X} transaction={:X}, flags=0x{:08X}", id,
static_cast<u32>(transaction), flags);
- nv_flinger->Lock();
+ const auto guard = nv_flinger->Lock();
auto& buffer_queue = nv_flinger->FindBufferQueue(id);
switch (transaction) {
@@ -548,10 +548,10 @@ private:
// Wait the current thread until a buffer becomes available
ctx.SleepClientThread(
"IHOSBinderDriver::DequeueBuffer", UINT64_MAX,
- [=](std::shared_ptr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx,
- Kernel::ThreadWakeupReason reason) {
+ [=, this](std::shared_ptr<Kernel::Thread> thread,
+ Kernel::HLERequestContext& ctx, Kernel::ThreadWakeupReason reason) {
// Repeat TransactParcel DequeueBuffer when a buffer is available
- nv_flinger->Lock();
+ const auto guard = nv_flinger->Lock();
auto& buffer_queue = nv_flinger->FindBufferQueue(id);
auto result = buffer_queue.DequeueBuffer(width, height);
ASSERT_MSG(result != std::nullopt, "Could not dequeue buffer.");
@@ -1199,6 +1199,23 @@ private:
}
}
+ void GetIndirectLayerImageRequiredMemoryInfo(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto width = rp.Pop<u64>();
+ const auto height = rp.Pop<u64>();
+ LOG_DEBUG(Service_VI, "called width={}, height={}", width, height);
+
+ constexpr std::size_t base_size = 0x20000;
+ constexpr std::size_t alignment = 0x1000;
+ const auto texture_size = width * height * 4;
+ const auto out_size = (texture_size + base_size - 1) / base_size * base_size;
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(out_size);
+ rb.Push(alignment);
+ }
+
static ResultVal<ConvertedScaleMode> ConvertScalingModeImpl(NintendoScaleMode mode) {
switch (mode) {
case NintendoScaleMode::None:
@@ -1243,7 +1260,8 @@ IApplicationDisplayService::IApplicationDisplayService(
{2102, &IApplicationDisplayService::ConvertScalingMode, "ConvertScalingMode"},
{2450, nullptr, "GetIndirectLayerImageMap"},
{2451, nullptr, "GetIndirectLayerImageCropMap"},
- {2460, nullptr, "GetIndirectLayerImageRequiredMemoryInfo"},
+ {2460, &IApplicationDisplayService::GetIndirectLayerImageRequiredMemoryInfo,
+ "GetIndirectLayerImageRequiredMemoryInfo"},
{5202, &IApplicationDisplayService::GetDisplayVsyncEvent, "GetDisplayVsyncEvent"},
{5203, nullptr, "GetDisplayVsyncEventForDebug"},
};
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 59ca7091a..9bc3a8840 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -3,8 +3,10 @@
// Refer to the license.txt file included.
#include <memory>
+#include <optional>
#include <ostream>
#include <string>
+#include "common/concepts.h"
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
@@ -21,27 +23,41 @@
namespace Loader {
-FileType IdentifyFile(FileSys::VirtualFile file) {
- FileType type;
-
-#define CHECK_TYPE(loader) \
- type = AppLoader_##loader::IdentifyType(file); \
- if (FileType::Error != type) \
- return type;
+namespace {
- CHECK_TYPE(DeconstructedRomDirectory)
- CHECK_TYPE(ELF)
- CHECK_TYPE(NSO)
- CHECK_TYPE(NRO)
- CHECK_TYPE(NCA)
- CHECK_TYPE(XCI)
- CHECK_TYPE(NAX)
- CHECK_TYPE(NSP)
- CHECK_TYPE(KIP)
+template <Common::DerivedFrom<AppLoader> T>
+std::optional<FileType> IdentifyFileLoader(FileSys::VirtualFile file) {
+ const auto file_type = T::IdentifyType(file);
+ if (file_type != FileType::Error) {
+ return file_type;
+ }
+ return std::nullopt;
+}
-#undef CHECK_TYPE
+} // namespace
- return FileType::Unknown;
+FileType IdentifyFile(FileSys::VirtualFile file) {
+ if (const auto romdir_type = IdentifyFileLoader<AppLoader_DeconstructedRomDirectory>(file)) {
+ return *romdir_type;
+ } else if (const auto elf_type = IdentifyFileLoader<AppLoader_ELF>(file)) {
+ return *elf_type;
+ } else if (const auto nso_type = IdentifyFileLoader<AppLoader_NSO>(file)) {
+ return *nso_type;
+ } else if (const auto nro_type = IdentifyFileLoader<AppLoader_NRO>(file)) {
+ return *nro_type;
+ } else if (const auto nca_type = IdentifyFileLoader<AppLoader_NCA>(file)) {
+ return *nca_type;
+ } else if (const auto xci_type = IdentifyFileLoader<AppLoader_XCI>(file)) {
+ return *xci_type;
+ } else if (const auto nax_type = IdentifyFileLoader<AppLoader_NAX>(file)) {
+ return *nax_type;
+ } else if (const auto nsp_type = IdentifyFileLoader<AppLoader_NSP>(file)) {
+ return *nsp_type;
+ } else if (const auto kip_type = IdentifyFileLoader<AppLoader_KIP>(file)) {
+ return *kip_type;
+ } else {
+ return FileType::Unknown;
+ }
}
FileType GuessFromFilename(const std::string& name) {
@@ -51,7 +67,7 @@ FileType GuessFromFilename(const std::string& name) {
return FileType::NCA;
const std::string extension =
- Common::ToLower(std::string(FileUtil::GetExtensionFromFilename(name)));
+ Common::ToLower(std::string(Common::FS::GetExtensionFromFilename(name)));
if (extension == "elf")
return FileType::ELF;
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 2c5588933..86d17c6cb 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -704,7 +704,7 @@ struct Memory::Impl {
u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
if (page_pointer != nullptr) {
// NOTE: Avoid adding any extra logic to this fast-path block
- T volatile* pointer = reinterpret_cast<T volatile*>(&page_pointer[vaddr]);
+ auto* pointer = reinterpret_cast<volatile T*>(&page_pointer[vaddr]);
return Common::AtomicCompareAndSwap(pointer, data, expected);
}
@@ -720,9 +720,8 @@ struct Memory::Impl {
case Common::PageType::RasterizerCachedMemory: {
u8* host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)};
system.GPU().InvalidateRegion(vaddr, sizeof(T));
- T volatile* pointer = reinterpret_cast<T volatile*>(&host_ptr);
+ auto* pointer = reinterpret_cast<volatile T*>(&host_ptr);
return Common::AtomicCompareAndSwap(pointer, data, expected);
- break;
}
default:
UNREACHABLE();
@@ -734,7 +733,7 @@ struct Memory::Impl {
u8* const page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
if (page_pointer != nullptr) {
// NOTE: Avoid adding any extra logic to this fast-path block
- u64 volatile* pointer = reinterpret_cast<u64 volatile*>(&page_pointer[vaddr]);
+ auto* pointer = reinterpret_cast<volatile u64*>(&page_pointer[vaddr]);
return Common::AtomicCompareAndSwap(pointer, data, expected);
}
@@ -750,9 +749,8 @@ struct Memory::Impl {
case Common::PageType::RasterizerCachedMemory: {
u8* host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)};
system.GPU().InvalidateRegion(vaddr, sizeof(u128));
- u64 volatile* pointer = reinterpret_cast<u64 volatile*>(&host_ptr);
+ auto* pointer = reinterpret_cast<volatile u64*>(&host_ptr);
return Common::AtomicCompareAndSwap(pointer, data, expected);
- break;
}
default:
UNREACHABLE();
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
index ced41b1fe..e503118dd 100644
--- a/src/core/memory/cheat_engine.cpp
+++ b/src/core/memory/cheat_engine.cpp
@@ -42,7 +42,7 @@ u64 StandardVmCallbacks::HidKeysDown() {
if (applet_resource == nullptr) {
LOG_WARNING(CheatEngine,
"Attempted to read input state, but applet resource is not initialized!");
- return false;
+ return 0;
}
const auto press_state =
@@ -188,28 +188,40 @@ CheatEngine::~CheatEngine() {
}
void CheatEngine::Initialize() {
- event = Core::Timing::CreateEvent("CheatEngine::FrameCallback::" +
- Common::HexToString(metadata.main_nso_build_id),
- [this](u64 userdata, std::chrono::nanoseconds ns_late) {
- FrameCallback(userdata, ns_late);
- });
+ event = Core::Timing::CreateEvent(
+ "CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id),
+ [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ FrameCallback(user_data, ns_late);
+ });
core_timing.ScheduleEvent(CHEAT_ENGINE_NS, event);
metadata.process_id = system.CurrentProcess()->GetProcessID();
metadata.title_id = system.CurrentProcess()->GetTitleID();
const auto& page_table = system.CurrentProcess()->PageTable();
- metadata.heap_extents = {page_table.GetHeapRegionStart(), page_table.GetHeapRegionSize()};
- metadata.address_space_extents = {page_table.GetAddressSpaceStart(),
- page_table.GetAddressSpaceSize()};
- metadata.alias_extents = {page_table.GetAliasCodeRegionStart(),
- page_table.GetAliasCodeRegionSize()};
+ metadata.heap_extents = {
+ .base = page_table.GetHeapRegionStart(),
+ .size = page_table.GetHeapRegionSize(),
+ };
+
+ metadata.address_space_extents = {
+ .base = page_table.GetAddressSpaceStart(),
+ .size = page_table.GetAddressSpaceSize(),
+ };
+
+ metadata.alias_extents = {
+ .base = page_table.GetAliasCodeRegionStart(),
+ .size = page_table.GetAliasCodeRegionSize(),
+ };
is_pending_reload.exchange(true);
}
void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size) {
- metadata.main_nso_extents = {main_region_begin, main_region_size};
+ metadata.main_nso_extents = {
+ .base = main_region_begin,
+ .size = main_region_size,
+ };
}
void CheatEngine::Reload(std::vector<CheatEntry> cheats) {
@@ -219,7 +231,7 @@ void CheatEngine::Reload(std::vector<CheatEntry> cheats) {
MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
-void CheatEngine::FrameCallback(u64, std::chrono::nanoseconds ns_late) {
+void CheatEngine::FrameCallback(std::uintptr_t, std::chrono::nanoseconds ns_late) {
if (is_pending_reload.exchange(false)) {
vm.LoadProgram(cheats);
}
diff --git a/src/core/memory/cheat_engine.h b/src/core/memory/cheat_engine.h
index d4068cf84..fa039a831 100644
--- a/src/core/memory/cheat_engine.h
+++ b/src/core/memory/cheat_engine.h
@@ -72,7 +72,7 @@ public:
void Reload(std::vector<CheatEntry> cheats);
private:
- void FrameCallback(u64 userdata, std::chrono::nanoseconds ns_late);
+ void FrameCallback(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
DmntCheatVm vm;
CheatProcessMetadata metadata;
diff --git a/src/core/memory/dmnt_cheat_vm.cpp b/src/core/memory/dmnt_cheat_vm.cpp
index 2e7da23fe..48be80c12 100644
--- a/src/core/memory/dmnt_cheat_vm.cpp
+++ b/src/core/memory/dmnt_cheat_vm.cpp
@@ -313,30 +313,32 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
switch (opcode_type) {
case CheatVmOpcodeType::StoreStatic: {
- StoreStaticOpcode store_static{};
// 0TMR00AA AAAAAAAA YYYYYYYY (YYYYYYYY)
// Read additional words.
const u32 second_dword = GetNextDword();
- store_static.bit_width = (first_dword >> 24) & 0xF;
- store_static.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF);
- store_static.offset_register = ((first_dword >> 16) & 0xF);
- store_static.rel_address =
- (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword);
- store_static.value = GetNextVmInt(store_static.bit_width);
- opcode.opcode = store_static;
+ const u32 bit_width = (first_dword >> 24) & 0xF;
+
+ opcode.opcode = StoreStaticOpcode{
+ .bit_width = bit_width,
+ .mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF),
+ .offset_register = (first_dword >> 16) & 0xF,
+ .rel_address = (static_cast<u64>(first_dword & 0xFF) << 32) | second_dword,
+ .value = GetNextVmInt(bit_width),
+ };
} break;
case CheatVmOpcodeType::BeginConditionalBlock: {
- BeginConditionalOpcode begin_cond{};
// 1TMC00AA AAAAAAAA YYYYYYYY (YYYYYYYY)
// Read additional words.
const u32 second_dword = GetNextDword();
- begin_cond.bit_width = (first_dword >> 24) & 0xF;
- begin_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF);
- begin_cond.cond_type = static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF);
- begin_cond.rel_address =
- (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword);
- begin_cond.value = GetNextVmInt(begin_cond.bit_width);
- opcode.opcode = begin_cond;
+ const u32 bit_width = (first_dword >> 24) & 0xF;
+
+ opcode.opcode = BeginConditionalOpcode{
+ .bit_width = bit_width,
+ .mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF),
+ .cond_type = static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF),
+ .rel_address = (static_cast<u64>(first_dword & 0xFF) << 32) | second_dword,
+ .value = GetNextVmInt(bit_width),
+ };
} break;
case CheatVmOpcodeType::EndConditionalBlock: {
// 20000000
@@ -344,12 +346,14 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
opcode.opcode = EndConditionalOpcode{};
} break;
case CheatVmOpcodeType::ControlLoop: {
- ControlLoopOpcode ctrl_loop{};
// 300R0000 VVVVVVVV
// 310R0000
// Parse register, whether loop start or loop end.
- ctrl_loop.start_loop = ((first_dword >> 24) & 0xF) == 0;
- ctrl_loop.reg_index = ((first_dword >> 20) & 0xF);
+ ControlLoopOpcode ctrl_loop{
+ .start_loop = ((first_dword >> 24) & 0xF) == 0,
+ .reg_index = (first_dword >> 20) & 0xF,
+ .num_iters = 0,
+ };
// Read number of iters if loop start.
if (ctrl_loop.start_loop) {
@@ -358,66 +362,65 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
opcode.opcode = ctrl_loop;
} break;
case CheatVmOpcodeType::LoadRegisterStatic: {
- LoadRegisterStaticOpcode ldr_static{};
// 400R0000 VVVVVVVV VVVVVVVV
// Read additional words.
- ldr_static.reg_index = ((first_dword >> 16) & 0xF);
- ldr_static.value =
- (static_cast<u64>(GetNextDword()) << 32ul) | static_cast<u64>(GetNextDword());
- opcode.opcode = ldr_static;
+ opcode.opcode = LoadRegisterStaticOpcode{
+ .reg_index = (first_dword >> 16) & 0xF,
+ .value = (static_cast<u64>(GetNextDword()) << 32) | GetNextDword(),
+ };
} break;
case CheatVmOpcodeType::LoadRegisterMemory: {
- LoadRegisterMemoryOpcode ldr_memory{};
// 5TMRI0AA AAAAAAAA
// Read additional words.
const u32 second_dword = GetNextDword();
- ldr_memory.bit_width = (first_dword >> 24) & 0xF;
- ldr_memory.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF);
- ldr_memory.reg_index = ((first_dword >> 16) & 0xF);
- ldr_memory.load_from_reg = ((first_dword >> 12) & 0xF) != 0;
- ldr_memory.rel_address =
- (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword);
- opcode.opcode = ldr_memory;
+ opcode.opcode = LoadRegisterMemoryOpcode{
+ .bit_width = (first_dword >> 24) & 0xF,
+ .mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF),
+ .reg_index = ((first_dword >> 16) & 0xF),
+ .load_from_reg = ((first_dword >> 12) & 0xF) != 0,
+ .rel_address = (static_cast<u64>(first_dword & 0xFF) << 32) | second_dword,
+ };
} break;
case CheatVmOpcodeType::StoreStaticToAddress: {
- StoreStaticToAddressOpcode str_static{};
// 6T0RIor0 VVVVVVVV VVVVVVVV
// Read additional words.
- str_static.bit_width = (first_dword >> 24) & 0xF;
- str_static.reg_index = ((first_dword >> 16) & 0xF);
- str_static.increment_reg = ((first_dword >> 12) & 0xF) != 0;
- str_static.add_offset_reg = ((first_dword >> 8) & 0xF) != 0;
- str_static.offset_reg_index = ((first_dword >> 4) & 0xF);
- str_static.value =
- (static_cast<u64>(GetNextDword()) << 32ul) | static_cast<u64>(GetNextDword());
- opcode.opcode = str_static;
+ opcode.opcode = StoreStaticToAddressOpcode{
+ .bit_width = (first_dword >> 24) & 0xF,
+ .reg_index = (first_dword >> 16) & 0xF,
+ .increment_reg = ((first_dword >> 12) & 0xF) != 0,
+ .add_offset_reg = ((first_dword >> 8) & 0xF) != 0,
+ .offset_reg_index = (first_dword >> 4) & 0xF,
+ .value = (static_cast<u64>(GetNextDword()) << 32) | GetNextDword(),
+ };
} break;
case CheatVmOpcodeType::PerformArithmeticStatic: {
- PerformArithmeticStaticOpcode perform_math_static{};
// 7T0RC000 VVVVVVVV
// Read additional words.
- perform_math_static.bit_width = (first_dword >> 24) & 0xF;
- perform_math_static.reg_index = ((first_dword >> 16) & 0xF);
- perform_math_static.math_type =
- static_cast<RegisterArithmeticType>((first_dword >> 12) & 0xF);
- perform_math_static.value = GetNextDword();
- opcode.opcode = perform_math_static;
+ opcode.opcode = PerformArithmeticStaticOpcode{
+ .bit_width = (first_dword >> 24) & 0xF,
+ .reg_index = ((first_dword >> 16) & 0xF),
+ .math_type = static_cast<RegisterArithmeticType>((first_dword >> 12) & 0xF),
+ .value = GetNextDword(),
+ };
} break;
case CheatVmOpcodeType::BeginKeypressConditionalBlock: {
- BeginKeypressConditionalOpcode begin_keypress_cond{};
// 8kkkkkkk
// Just parse the mask.
- begin_keypress_cond.key_mask = first_dword & 0x0FFFFFFF;
- opcode.opcode = begin_keypress_cond;
+ opcode.opcode = BeginKeypressConditionalOpcode{
+ .key_mask = first_dword & 0x0FFFFFFF,
+ };
} break;
case CheatVmOpcodeType::PerformArithmeticRegister: {
- PerformArithmeticRegisterOpcode perform_math_reg{};
// 9TCRSIs0 (VVVVVVVV (VVVVVVVV))
- perform_math_reg.bit_width = (first_dword >> 24) & 0xF;
- perform_math_reg.math_type = static_cast<RegisterArithmeticType>((first_dword >> 20) & 0xF);
- perform_math_reg.dst_reg_index = ((first_dword >> 16) & 0xF);
- perform_math_reg.src_reg_1_index = ((first_dword >> 12) & 0xF);
- perform_math_reg.has_immediate = ((first_dword >> 8) & 0xF) != 0;
+ PerformArithmeticRegisterOpcode perform_math_reg{
+ .bit_width = (first_dword >> 24) & 0xF,
+ .math_type = static_cast<RegisterArithmeticType>((first_dword >> 20) & 0xF),
+ .dst_reg_index = (first_dword >> 16) & 0xF,
+ .src_reg_1_index = (first_dword >> 12) & 0xF,
+ .src_reg_2_index = 0,
+ .has_immediate = ((first_dword >> 8) & 0xF) != 0,
+ .value = {},
+ };
if (perform_math_reg.has_immediate) {
perform_math_reg.src_reg_2_index = 0;
perform_math_reg.value = GetNextVmInt(perform_math_reg.bit_width);
@@ -427,7 +430,6 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
opcode.opcode = perform_math_reg;
} break;
case CheatVmOpcodeType::StoreRegisterToAddress: {
- StoreRegisterToAddressOpcode str_register{};
// ATSRIOxa (aaaaaaaa)
// A = opcode 10
// T = bit width
@@ -439,20 +441,23 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
// Relative Address
// x = offset register (for offset type 1), memory type (for offset type 3)
// a = relative address (for offset type 2+3)
- str_register.bit_width = (first_dword >> 24) & 0xF;
- str_register.str_reg_index = ((first_dword >> 20) & 0xF);
- str_register.addr_reg_index = ((first_dword >> 16) & 0xF);
- str_register.increment_reg = ((first_dword >> 12) & 0xF) != 0;
- str_register.ofs_type = static_cast<StoreRegisterOffsetType>(((first_dword >> 8) & 0xF));
- str_register.ofs_reg_index = ((first_dword >> 4) & 0xF);
+ StoreRegisterToAddressOpcode str_register{
+ .bit_width = (first_dword >> 24) & 0xF,
+ .str_reg_index = (first_dword >> 20) & 0xF,
+ .addr_reg_index = (first_dword >> 16) & 0xF,
+ .increment_reg = ((first_dword >> 12) & 0xF) != 0,
+ .ofs_type = static_cast<StoreRegisterOffsetType>(((first_dword >> 8) & 0xF)),
+ .mem_type = MemoryAccessType::MainNso,
+ .ofs_reg_index = (first_dword >> 4) & 0xF,
+ .rel_address = 0,
+ };
switch (str_register.ofs_type) {
case StoreRegisterOffsetType::None:
case StoreRegisterOffsetType::Reg:
// Nothing more to do
break;
case StoreRegisterOffsetType::Imm:
- str_register.rel_address =
- ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
+ str_register.rel_address = (static_cast<u64>(first_dword & 0xF) << 32) | GetNextDword();
break;
case StoreRegisterOffsetType::MemReg:
str_register.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
@@ -460,8 +465,7 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
case StoreRegisterOffsetType::MemImm:
case StoreRegisterOffsetType::MemImmReg:
str_register.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
- str_register.rel_address =
- ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
+ str_register.rel_address = (static_cast<u64>(first_dword & 0xF) << 32) | GetNextDword();
break;
default:
str_register.ofs_type = StoreRegisterOffsetType::None;
@@ -470,7 +474,6 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
opcode.opcode = str_register;
} break;
case CheatVmOpcodeType::BeginRegisterConditionalBlock: {
- BeginRegisterConditionalOpcode begin_reg_cond{};
// C0TcSX##
// C0TcS0Ma aaaaaaaa
// C0TcS1Mr
@@ -492,11 +495,19 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
// r = offset register.
// X = other register.
// V = value.
- begin_reg_cond.bit_width = (first_dword >> 20) & 0xF;
- begin_reg_cond.cond_type =
- static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF);
- begin_reg_cond.val_reg_index = ((first_dword >> 12) & 0xF);
- begin_reg_cond.comp_type = static_cast<CompareRegisterValueType>((first_dword >> 8) & 0xF);
+
+ BeginRegisterConditionalOpcode begin_reg_cond{
+ .bit_width = (first_dword >> 20) & 0xF,
+ .cond_type = static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF),
+ .val_reg_index = (first_dword >> 12) & 0xF,
+ .comp_type = static_cast<CompareRegisterValueType>((first_dword >> 8) & 0xF),
+ .mem_type = MemoryAccessType::MainNso,
+ .addr_reg_index = 0,
+ .other_reg_index = 0,
+ .ofs_reg_index = 0,
+ .rel_address = 0,
+ .value = {},
+ };
switch (begin_reg_cond.comp_type) {
case CompareRegisterValueType::StaticValue:
@@ -508,26 +519,25 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
case CompareRegisterValueType::MemoryRelAddr:
begin_reg_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
begin_reg_cond.rel_address =
- ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
+ (static_cast<u64>(first_dword & 0xF) << 32) | GetNextDword();
break;
case CompareRegisterValueType::MemoryOfsReg:
begin_reg_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
begin_reg_cond.ofs_reg_index = (first_dword & 0xF);
break;
case CompareRegisterValueType::RegisterRelAddr:
- begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF);
+ begin_reg_cond.addr_reg_index = (first_dword >> 4) & 0xF;
begin_reg_cond.rel_address =
- ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
+ (static_cast<u64>(first_dword & 0xF) << 32) | GetNextDword();
break;
case CompareRegisterValueType::RegisterOfsReg:
- begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF);
- begin_reg_cond.ofs_reg_index = (first_dword & 0xF);
+ begin_reg_cond.addr_reg_index = (first_dword >> 4) & 0xF;
+ begin_reg_cond.ofs_reg_index = first_dword & 0xF;
break;
}
opcode.opcode = begin_reg_cond;
} break;
case CheatVmOpcodeType::SaveRestoreRegister: {
- SaveRestoreRegisterOpcode save_restore_reg{};
// C10D0Sx0
// C1 = opcode 0xC1
// D = destination index.
@@ -535,36 +545,37 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
// x = 3 if clearing reg, 2 if clearing saved value, 1 if saving a register, 0 if restoring
// a register.
// NOTE: If we add more save slots later, current encoding is backwards compatible.
- save_restore_reg.dst_index = (first_dword >> 16) & 0xF;
- save_restore_reg.src_index = (first_dword >> 8) & 0xF;
- save_restore_reg.op_type = static_cast<SaveRestoreRegisterOpType>((first_dword >> 4) & 0xF);
- opcode.opcode = save_restore_reg;
+ opcode.opcode = SaveRestoreRegisterOpcode{
+ .dst_index = (first_dword >> 16) & 0xF,
+ .src_index = (first_dword >> 8) & 0xF,
+ .op_type = static_cast<SaveRestoreRegisterOpType>((first_dword >> 4) & 0xF),
+ };
} break;
case CheatVmOpcodeType::SaveRestoreRegisterMask: {
- SaveRestoreRegisterMaskOpcode save_restore_regmask{};
// C2x0XXXX
// C2 = opcode 0xC2
// x = 3 if clearing reg, 2 if clearing saved value, 1 if saving, 0 if restoring.
// X = 16-bit bitmask, bit i --> save or restore register i.
- save_restore_regmask.op_type =
- static_cast<SaveRestoreRegisterOpType>((first_dword >> 20) & 0xF);
+ SaveRestoreRegisterMaskOpcode save_restore_regmask{
+ .op_type = static_cast<SaveRestoreRegisterOpType>((first_dword >> 20) & 0xF),
+ .should_operate = {},
+ };
for (std::size_t i = 0; i < NumRegisters; i++) {
- save_restore_regmask.should_operate[i] = (first_dword & (1u << i)) != 0;
+ save_restore_regmask.should_operate[i] = (first_dword & (1U << i)) != 0;
}
opcode.opcode = save_restore_regmask;
} break;
case CheatVmOpcodeType::ReadWriteStaticRegister: {
- ReadWriteStaticRegisterOpcode rw_static_reg{};
// C3000XXx
// C3 = opcode 0xC3.
// XX = static register index.
// x = register index.
- rw_static_reg.static_idx = ((first_dword >> 4) & 0xFF);
- rw_static_reg.idx = (first_dword & 0xF);
- opcode.opcode = rw_static_reg;
+ opcode.opcode = ReadWriteStaticRegisterOpcode{
+ .static_idx = (first_dword >> 4) & 0xFF,
+ .idx = first_dword & 0xF,
+ };
} break;
case CheatVmOpcodeType::DebugLog: {
- DebugLogOpcode debug_log{};
// FFFTIX##
// FFFTI0Ma aaaaaaaa
// FFFTI1Mr
@@ -583,31 +594,36 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
// a = relative address.
// r = offset register.
// X = value register.
- debug_log.bit_width = (first_dword >> 16) & 0xF;
- debug_log.log_id = ((first_dword >> 12) & 0xF);
- debug_log.val_type = static_cast<DebugLogValueType>((first_dword >> 8) & 0xF);
+ DebugLogOpcode debug_log{
+ .bit_width = (first_dword >> 16) & 0xF,
+ .log_id = (first_dword >> 12) & 0xF,
+ .val_type = static_cast<DebugLogValueType>((first_dword >> 8) & 0xF),
+ .mem_type = MemoryAccessType::MainNso,
+ .addr_reg_index = 0,
+ .val_reg_index = 0,
+ .ofs_reg_index = 0,
+ .rel_address = 0,
+ };
switch (debug_log.val_type) {
case DebugLogValueType::RegisterValue:
- debug_log.val_reg_index = ((first_dword >> 4) & 0xF);
+ debug_log.val_reg_index = (first_dword >> 4) & 0xF;
break;
case DebugLogValueType::MemoryRelAddr:
debug_log.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
- debug_log.rel_address =
- ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
+ debug_log.rel_address = (static_cast<u64>(first_dword & 0xF) << 32) | GetNextDword();
break;
case DebugLogValueType::MemoryOfsReg:
debug_log.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
- debug_log.ofs_reg_index = (first_dword & 0xF);
+ debug_log.ofs_reg_index = first_dword & 0xF;
break;
case DebugLogValueType::RegisterRelAddr:
- debug_log.addr_reg_index = ((first_dword >> 4) & 0xF);
- debug_log.rel_address =
- ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
+ debug_log.addr_reg_index = (first_dword >> 4) & 0xF;
+ debug_log.rel_address = (static_cast<u64>(first_dword & 0xF) << 32) | GetNextDword();
break;
case DebugLogValueType::RegisterOfsReg:
- debug_log.addr_reg_index = ((first_dword >> 4) & 0xF);
- debug_log.ofs_reg_index = (first_dword & 0xF);
+ debug_log.addr_reg_index = (first_dword >> 4) & 0xF;
+ debug_log.ofs_reg_index = first_dword & 0xF;
break;
}
opcode.opcode = debug_log;
diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp
index 29339ead7..b93396a80 100644
--- a/src/core/perf_stats.cpp
+++ b/src/core/perf_stats.cpp
@@ -38,11 +38,11 @@ PerfStats::~PerfStats() {
std::ostringstream stream;
std::copy(perf_history.begin() + IgnoreFrames, perf_history.begin() + current_index,
std::ostream_iterator<double>(stream, "\n"));
- const std::string& path = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
+ const std::string& path = Common::FS::GetUserPath(Common::FS::UserPath::LogDir);
// %F Date format expanded is "%Y-%m-%d"
const std::string filename =
fmt::format("{}/{:%F-%H-%M}_{:016X}.csv", path, *std::localtime(&t), title_id);
- FileUtil::IOFile file(filename, "w");
+ Common::FS::IOFile file(filename, "w");
file.WriteString(stream.str());
}
@@ -74,15 +74,16 @@ void PerfStats::EndGameFrame() {
game_frames += 1;
}
-double PerfStats::GetMeanFrametime() {
+double PerfStats::GetMeanFrametime() const {
std::lock_guard lock{object_mutex};
if (current_index <= IgnoreFrames) {
return 0;
}
+
const double sum = std::accumulate(perf_history.begin() + IgnoreFrames,
perf_history.begin() + current_index, 0.0);
- return sum / (current_index - IgnoreFrames);
+ return sum / static_cast<double>(current_index - IgnoreFrames);
}
PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us) {
@@ -94,12 +95,13 @@ PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us
const auto system_us_per_second = (current_system_time_us - reset_point_system_us) / interval;
- PerfStatsResults results{};
- results.system_fps = static_cast<double>(system_frames) / interval;
- results.game_fps = static_cast<double>(game_frames) / interval;
- results.frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() /
- static_cast<double>(system_frames);
- results.emulation_speed = system_us_per_second.count() / 1'000'000.0;
+ const PerfStatsResults results{
+ .system_fps = static_cast<double>(system_frames) / interval,
+ .game_fps = static_cast<double>(game_frames) / interval,
+ .frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() /
+ static_cast<double>(system_frames),
+ .emulation_speed = system_us_per_second.count() / 1'000'000.0,
+ };
// Reset counters
reset_point = now;
@@ -111,7 +113,7 @@ PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us
return results;
}
-double PerfStats::GetLastFrameTimeScale() {
+double PerfStats::GetLastFrameTimeScale() const {
std::lock_guard lock{object_mutex};
constexpr double FRAME_LENGTH = 1.0 / 60;
diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h
index d9a64f072..69256b960 100644
--- a/src/core/perf_stats.h
+++ b/src/core/perf_stats.h
@@ -30,7 +30,6 @@ struct PerfStatsResults {
class PerfStats {
public:
explicit PerfStats(u64 title_id);
-
~PerfStats();
using Clock = std::chrono::high_resolution_clock;
@@ -42,18 +41,18 @@ public:
PerfStatsResults GetAndResetStats(std::chrono::microseconds current_system_time_us);
/**
- * Returns the Arthimetic Mean of all frametime values stored in the performance history.
+ * Returns the arithmetic mean of all frametime values stored in the performance history.
*/
- double GetMeanFrametime();
+ double GetMeanFrametime() const;
/**
* Gets the ratio between walltime and the emulated time of the previous system frame. This is
* useful for scaling inputs or outputs moving between the two time domains.
*/
- double GetLastFrameTimeScale();
+ double GetLastFrameTimeScale() const;
private:
- std::mutex object_mutex{};
+ mutable std::mutex object_mutex;
/// Title ID for the game that is running. 0 if there is no game running yet
u64 title_id{0};
@@ -61,7 +60,7 @@ private:
std::size_t current_index{0};
/// Stores an hour of historical frametime data useful for processing and tracking performance
/// regressions with code changes.
- std::array<double, 216000> perf_history = {};
+ std::array<double, 216000> perf_history{};
/// Point when the cumulative counters were reset
Clock::time_point reset_point = Clock::now();
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp
index 76cfa5a17..0becdf642 100644
--- a/src/core/reporter.cpp
+++ b/src/core/reporter.cpp
@@ -28,8 +28,9 @@
namespace {
std::string GetPath(std::string_view type, u64 title_id, std::string_view timestamp) {
- return fmt::format("{}{}/{:016X}_{}.json", FileUtil::GetUserPath(FileUtil::UserPath::LogDir),
- type, title_id, timestamp);
+ return fmt::format("{}{}/{:016X}_{}.json",
+ Common::FS::GetUserPath(Common::FS::UserPath::LogDir), type, title_id,
+ timestamp);
}
std::string GetTimestamp() {
@@ -40,13 +41,13 @@ std::string GetTimestamp() {
using namespace nlohmann;
void SaveToFile(json json, const std::string& filename) {
- if (!FileUtil::CreateFullPath(filename)) {
+ if (!Common::FS::CreateFullPath(filename)) {
LOG_ERROR(Core, "Failed to create path for '{}' to save report!", filename);
return;
}
std::ofstream file(
- FileUtil::SanitizePath(filename, FileUtil::DirectorySeparator::PlatformDefault));
+ Common::FS::SanitizePath(filename, Common::FS::DirectorySeparator::PlatformDefault));
file << std::setw(4) << json << std::endl;
}
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index 44252dd81..28d3f9099 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -13,56 +13,6 @@
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_lstick_left",
- "button_lstick_up",
- "button_lstick_right",
- "button_lstick_down",
- "button_rstick_left",
- "button_rstick_up",
- "button_rstick_right",
- "button_rstick_down",
- "button_sl",
- "button_sr",
- "button_home",
- "button_screenshot",
-}};
-}
-
-namespace NativeAnalog {
-const std::array<const char*, NumAnalogs> mapping = {{
- "lstick",
- "rstick",
-}};
-}
-
-namespace NativeMouseButton {
-const std::array<const char*, NumMouseButtons> mapping = {{
- "left",
- "right",
- "middle",
- "forward",
- "back",
-}};
-}
-
Values values = {};
bool configuring_global = true;
@@ -121,8 +71,8 @@ void LogSettings() {
log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching.GetValue());
log_setting("Audio_OutputDevice", values.audio_device_id);
log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd);
- log_setting("DataStorage_NandDir", FileUtil::GetUserPath(FileUtil::UserPath::NANDDir));
- log_setting("DataStorage_SdmcDir", FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir));
+ log_setting("DataStorage_NandDir", Common::FS::GetUserPath(Common::FS::UserPath::NANDDir));
+ log_setting("DataStorage_SdmcDir", Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir));
log_setting("Debugging_UseGdbstub", values.use_gdbstub);
log_setting("Debugging_GdbstubPort", values.gdbstub_port);
log_setting("Debugging_ProgramArgs", values.program_args);
@@ -173,7 +123,6 @@ void RestoreGlobalState() {
values.use_assembly_shaders.SetGlobal(true);
values.use_asynchronous_shaders.SetGlobal(true);
values.use_fast_gpu_time.SetGlobal(true);
- values.force_30fps_mode.SetGlobal(true);
values.bg_red.SetGlobal(true);
values.bg_green.SetGlobal(true);
values.bg_blue.SetGlobal(true);
diff --git a/src/core/settings.h b/src/core/settings.h
index 386233fdf..80f0d95a7 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -12,340 +12,10 @@
#include <string>
#include <vector>
#include "common/common_types.h"
+#include "input_common/settings.h"
namespace Settings {
-namespace NativeButton {
-enum Values {
- A,
- B,
- X,
- Y,
- LStick,
- RStick,
- L,
- R,
- ZL,
- ZR,
- Plus,
- Minus,
-
- DLeft,
- DUp,
- DRight,
- DDown,
-
- LStick_Left,
- LStick_Up,
- LStick_Right,
- LStick_Down,
-
- RStick_Left,
- RStick_Up,
- RStick_Right,
- RStick_Down,
-
- SL,
- SR,
-
- Home,
- Screenshot,
-
- NumButtons,
-};
-
-constexpr int BUTTON_HID_BEGIN = A;
-constexpr int BUTTON_NS_BEGIN = Home;
-
-constexpr int BUTTON_HID_END = BUTTON_NS_BEGIN;
-constexpr int BUTTON_NS_END = NumButtons;
-
-constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN;
-constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN;
-
-extern const std::array<const char*, NumButtons> mapping;
-
-} // namespace NativeButton
-
-namespace NativeAnalog {
-enum Values {
- LStick,
- RStick,
-
- NumAnalogs,
-};
-
-constexpr int STICK_HID_BEGIN = LStick;
-constexpr int STICK_HID_END = NumAnalogs;
-constexpr int NUM_STICKS_HID = NumAnalogs;
-
-extern const std::array<const char*, NumAnalogs> mapping;
-} // namespace NativeAnalog
-
-namespace NativeMouseButton {
-enum Values {
- Left,
- Right,
- Middle,
- Forward,
- Back,
-
- NumMouseButtons,
-};
-
-constexpr int MOUSE_HID_BEGIN = Left;
-constexpr int MOUSE_HID_END = NumMouseButtons;
-constexpr int NUM_MOUSE_HID = NumMouseButtons;
-
-extern const std::array<const char*, NumMouseButtons> mapping;
-} // namespace NativeMouseButton
-
-namespace NativeKeyboard {
-enum Keys {
- None,
- Error,
-
- A = 4,
- B,
- C,
- D,
- E,
- F,
- G,
- H,
- I,
- J,
- K,
- L,
- M,
- N,
- O,
- P,
- Q,
- R,
- S,
- T,
- U,
- V,
- W,
- X,
- Y,
- Z,
- N1,
- N2,
- N3,
- N4,
- N5,
- N6,
- N7,
- N8,
- N9,
- N0,
- Enter,
- Escape,
- Backspace,
- Tab,
- Space,
- Minus,
- Equal,
- LeftBrace,
- RightBrace,
- Backslash,
- Tilde,
- Semicolon,
- Apostrophe,
- Grave,
- Comma,
- Dot,
- Slash,
- CapsLockKey,
-
- F1,
- F2,
- F3,
- F4,
- F5,
- F6,
- F7,
- F8,
- F9,
- F10,
- F11,
- F12,
-
- SystemRequest,
- ScrollLockKey,
- Pause,
- Insert,
- Home,
- PageUp,
- Delete,
- End,
- PageDown,
- Right,
- Left,
- Down,
- Up,
-
- NumLockKey,
- KPSlash,
- KPAsterisk,
- KPMinus,
- KPPlus,
- KPEnter,
- KP1,
- KP2,
- KP3,
- KP4,
- KP5,
- KP6,
- KP7,
- KP8,
- KP9,
- KP0,
- KPDot,
-
- Key102,
- Compose,
- Power,
- KPEqual,
-
- F13,
- F14,
- F15,
- F16,
- F17,
- F18,
- F19,
- F20,
- F21,
- F22,
- F23,
- F24,
-
- Open,
- Help,
- Properties,
- Front,
- Stop,
- Repeat,
- Undo,
- Cut,
- Copy,
- Paste,
- Find,
- Mute,
- VolumeUp,
- VolumeDown,
- CapsLockActive,
- NumLockActive,
- ScrollLockActive,
- KPComma,
-
- KPLeftParenthesis,
- KPRightParenthesis,
-
- LeftControlKey = 0xE0,
- LeftShiftKey,
- LeftAltKey,
- LeftMetaKey,
- RightControlKey,
- RightShiftKey,
- RightAltKey,
- RightMetaKey,
-
- MediaPlayPause,
- MediaStopCD,
- MediaPrevious,
- MediaNext,
- MediaEject,
- MediaVolumeUp,
- MediaVolumeDown,
- MediaMute,
- MediaWebsite,
- MediaBack,
- MediaForward,
- MediaStop,
- MediaFind,
- MediaScrollUp,
- MediaScrollDown,
- MediaEdit,
- MediaSleep,
- MediaCoffee,
- MediaRefresh,
- MediaCalculator,
-
- NumKeyboardKeys,
-};
-
-static_assert(NumKeyboardKeys == 0xFC, "Incorrect number of keyboard keys.");
-
-enum Modifiers {
- LeftControl,
- LeftShift,
- LeftAlt,
- LeftMeta,
- RightControl,
- RightShift,
- RightAlt,
- RightMeta,
- CapsLock,
- ScrollLock,
- NumLock,
-
- NumKeyboardMods,
-};
-
-constexpr int KEYBOARD_KEYS_HID_BEGIN = None;
-constexpr int KEYBOARD_KEYS_HID_END = NumKeyboardKeys;
-constexpr int NUM_KEYBOARD_KEYS_HID = NumKeyboardKeys;
-
-constexpr int KEYBOARD_MODS_HID_BEGIN = LeftControl;
-constexpr int KEYBOARD_MODS_HID_END = NumKeyboardMods;
-constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
-
-} // namespace NativeKeyboard
-
-using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
-using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
-using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>;
-using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>;
-using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>;
-
-constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
-constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
-constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6;
-constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E;
-
-enum class ControllerType {
- ProController,
- DualJoycon,
- RightJoycon,
- LeftJoycon,
-};
-
-struct PlayerInput {
- bool connected;
- ControllerType type;
- ButtonsRaw buttons;
- AnalogsRaw analogs;
-
- u32 body_color_right;
- u32 button_color_right;
- u32 body_color_left;
- u32 button_color_left;
-};
-
-struct TouchscreenInput {
- bool enabled;
- std::string device;
-
- u32 finger;
- u32 diameter_x;
- u32 diameter_y;
- u32 rotation_angle;
-};
-
enum class RendererBackend {
OpenGL = 0,
Vulkan = 1,
@@ -359,7 +29,8 @@ enum class GPUAccuracy : u32 {
enum class CPUAccuracy {
Accurate = 0,
- DebugMode = 1,
+ Unsafe = 1,
+ DebugMode = 2,
};
extern bool configuring_global;
@@ -396,6 +67,11 @@ private:
Type local{};
};
+struct TouchFromButtonMap {
+ std::string name;
+ std::vector<std::string> buttons;
+};
+
struct Values {
// Audio
std::string audio_device_id;
@@ -419,6 +95,9 @@ struct Values {
bool cpuopt_misc_ir;
bool cpuopt_reduce_misalign_checks;
+ bool cpuopt_unsafe_unfuse_fma;
+ bool cpuopt_unsafe_reduce_fp_error;
+
// Renderer
Setting<RendererBackend> renderer_backend;
bool renderer_debug;
@@ -435,7 +114,6 @@ struct Values {
Setting<bool> use_vsync;
Setting<bool> use_assembly_shaders;
Setting<bool> use_asynchronous_shaders;
- Setting<bool> force_30fps_mode;
Setting<bool> use_fast_gpu_time;
Setting<float> bg_red;
@@ -458,6 +136,8 @@ struct Values {
// Controls
std::array<PlayerInput, 10> players;
+ bool use_docked_mode;
+
bool mouse_enabled;
std::string mouse_device;
MouseButtonsRaw mouse_buttons;
@@ -470,14 +150,18 @@ struct Values {
ButtonsRaw debug_pad_buttons;
AnalogsRaw debug_pad_analogs;
+ bool vibration_enabled;
+
std::string motion_device;
+ std::string touch_device;
TouchscreenInput touchscreen;
std::atomic_bool is_device_reload_pending{true};
+ bool use_touch_from_button;
+ int touch_from_button_map_index;
std::string udp_input_address;
u16 udp_input_port;
u8 udp_pad_index;
-
- bool use_docked_mode;
+ std::vector<TouchFromButtonMap> touch_from_button_maps;
// Data Storage
bool use_virtual_sd;
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 5a30c75da..da09c0dbc 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -25,6 +25,8 @@
namespace Core {
+namespace Telemetry = Common::Telemetry;
+
static u64 GenerateTelemetryId() {
u64 telemetry_id{};
@@ -70,12 +72,12 @@ static const char* TranslateGPUAccuracyLevel(Settings::GPUAccuracy backend) {
u64 GetTelemetryId() {
u64 telemetry_id{};
- const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) +
+ const std::string filename{Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir) +
"telemetry_id"};
- bool generate_new_id = !FileUtil::Exists(filename);
+ bool generate_new_id = !Common::FS::Exists(filename);
if (!generate_new_id) {
- FileUtil::IOFile file(filename, "rb");
+ Common::FS::IOFile file(filename, "rb");
if (!file.IsOpen()) {
LOG_ERROR(Core, "failed to open telemetry_id: {}", filename);
return {};
@@ -88,7 +90,7 @@ u64 GetTelemetryId() {
}
if (generate_new_id) {
- FileUtil::IOFile file(filename, "wb");
+ Common::FS::IOFile file(filename, "wb");
if (!file.IsOpen()) {
LOG_ERROR(Core, "failed to open telemetry_id: {}", filename);
return {};
@@ -102,10 +104,10 @@ u64 GetTelemetryId() {
u64 RegenerateTelemetryId() {
const u64 new_telemetry_id{GenerateTelemetryId()};
- const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) +
+ const std::string filename{Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir) +
"telemetry_id"};
- FileUtil::IOFile file(filename, "wb");
+ Common::FS::IOFile file(filename, "wb");
if (!file.IsOpen()) {
LOG_ERROR(Core, "failed to open telemetry_id: {}", filename);
return {};
diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h
index 17ac22377..66789d4bd 100644
--- a/src/core/telemetry_session.h
+++ b/src/core/telemetry_session.h
@@ -52,7 +52,7 @@ public:
* @param value Value for the field to add.
*/
template <typename T>
- void AddField(Telemetry::FieldType type, const char* name, T value) {
+ void AddField(Common::Telemetry::FieldType type, const char* name, T value) {
field_collection.AddField(type, name, std::move(value));
}
@@ -63,7 +63,8 @@ public:
bool SubmitTestcase();
private:
- Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session
+ /// Tracks all added fields for the session
+ Common::Telemetry::FieldCollection field_collection;
};
/**
diff --git a/src/core/tools/freezer.cpp b/src/core/tools/freezer.cpp
index 27b894b51..5c674a099 100644
--- a/src/core/tools/freezer.cpp
+++ b/src/core/tools/freezer.cpp
@@ -55,10 +55,11 @@ void MemoryWriteWidth(Core::Memory::Memory& memory, u32 width, VAddr addr, u64 v
Freezer::Freezer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_)
: core_timing{core_timing_}, memory{memory_} {
- event = Core::Timing::CreateEvent("MemoryFreezer::FrameCallback",
- [this](u64 userdata, std::chrono::nanoseconds ns_late) {
- FrameCallback(userdata, ns_late);
- });
+ event = Core::Timing::CreateEvent(
+ "MemoryFreezer::FrameCallback",
+ [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ FrameCallback(user_data, ns_late);
+ });
core_timing.ScheduleEvent(memory_freezer_ns, event);
}
@@ -106,28 +107,21 @@ void Freezer::Unfreeze(VAddr address) {
LOG_DEBUG(Common_Memory, "Unfreezing memory for address={:016X}", address);
- entries.erase(
- std::remove_if(entries.begin(), entries.end(),
- [&address](const Entry& entry) { return entry.address == address; }),
- entries.end());
+ std::erase_if(entries, [address](const Entry& entry) { return entry.address == address; });
}
bool Freezer::IsFrozen(VAddr address) const {
std::lock_guard lock{entries_mutex};
- return std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) {
- return entry.address == address;
- }) != entries.end();
+ return FindEntry(address) != entries.cend();
}
void Freezer::SetFrozenValue(VAddr address, u64 value) {
std::lock_guard lock{entries_mutex};
- const auto iter = std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) {
- return entry.address == address;
- });
+ const auto iter = FindEntry(address);
- if (iter == entries.end()) {
+ if (iter == entries.cend()) {
LOG_ERROR(Common_Memory,
"Tried to set freeze value for address={:016X} that is not frozen!", address);
return;
@@ -142,11 +136,9 @@ void Freezer::SetFrozenValue(VAddr address, u64 value) {
std::optional<Freezer::Entry> Freezer::GetEntry(VAddr address) const {
std::lock_guard lock{entries_mutex};
- const auto iter = std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) {
- return entry.address == address;
- });
+ const auto iter = FindEntry(address);
- if (iter == entries.end()) {
+ if (iter == entries.cend()) {
return std::nullopt;
}
@@ -159,7 +151,17 @@ std::vector<Freezer::Entry> Freezer::GetEntries() const {
return entries;
}
-void Freezer::FrameCallback(u64, std::chrono::nanoseconds ns_late) {
+Freezer::Entries::iterator Freezer::FindEntry(VAddr address) {
+ return std::find_if(entries.begin(), entries.end(),
+ [address](const Entry& entry) { return entry.address == address; });
+}
+
+Freezer::Entries::const_iterator Freezer::FindEntry(VAddr address) const {
+ return std::find_if(entries.begin(), entries.end(),
+ [address](const Entry& entry) { return entry.address == address; });
+}
+
+void Freezer::FrameCallback(std::uintptr_t, std::chrono::nanoseconds ns_late) {
if (!IsActive()) {
LOG_DEBUG(Common_Memory, "Memory freezer has been deactivated, ending callback events.");
return;
diff --git a/src/core/tools/freezer.h b/src/core/tools/freezer.h
index 8438783d5..0fdb701a7 100644
--- a/src/core/tools/freezer.h
+++ b/src/core/tools/freezer.h
@@ -73,13 +73,18 @@ public:
std::vector<Entry> GetEntries() const;
private:
- void FrameCallback(u64 userdata, std::chrono::nanoseconds ns_late);
+ using Entries = std::vector<Entry>;
+
+ Entries::iterator FindEntry(VAddr address);
+ Entries::const_iterator FindEntry(VAddr address) const;
+
+ void FrameCallback(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
void FillEntryReads();
std::atomic_bool active{false};
mutable std::mutex entries_mutex;
- std::vector<Entry> entries;
+ Entries entries;
std::shared_ptr<Core::Timing::EventType> event;
Core::Timing::CoreTiming& core_timing;
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 317c25bad..09361e37e 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -7,6 +7,12 @@ add_library(input_common STATIC
main.h
motion_emu.cpp
motion_emu.h
+ motion_input.cpp
+ motion_input.h
+ settings.cpp
+ settings.h
+ touch_from_button.cpp
+ touch_from_button.h
gcadapter/gc_adapter.cpp
gcadapter/gc_adapter.h
gcadapter/gc_poller.cpp
diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp
index 02d06876f..c6c423c4b 100644
--- a/src/input_common/gcadapter/gc_adapter.cpp
+++ b/src/input_common/gcadapter/gc_adapter.cpp
@@ -254,7 +254,7 @@ void Adapter::GetGCEndpoint(libusb_device* device) {
sizeof(clear_payload), nullptr, 16);
adapter_thread_running = true;
- adapter_input_thread = std::thread([=] { Read(); }); // Read input
+ adapter_input_thread = std::thread(&Adapter::Read, this);
}
Adapter::~Adapter() {
@@ -265,7 +265,9 @@ void Adapter::Reset() {
if (adapter_thread_running) {
adapter_thread_running = false;
}
- adapter_input_thread.join();
+ if (adapter_input_thread.joinable()) {
+ adapter_input_thread.join();
+ }
adapter_controllers_status.fill(ControllerTypes::None);
get_origin.fill(true);
@@ -281,7 +283,7 @@ void Adapter::Reset() {
}
}
-bool Adapter::DeviceConnected(std::size_t port) {
+bool Adapter::DeviceConnected(std::size_t port) const {
return adapter_controllers_status[port] != ControllerTypes::None;
}
diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h
index bed81915c..20e97d283 100644
--- a/src/input_common/gcadapter/gc_adapter.h
+++ b/src/input_common/gcadapter/gc_adapter.h
@@ -76,7 +76,7 @@ public:
void EndConfiguration();
/// Returns true if there is a device connected to port
- bool DeviceConnected(std::size_t port);
+ bool DeviceConnected(std::size_t port) const;
std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue();
const std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue() const;
diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp
index 96e22d3ad..92e9e8e89 100644
--- a/src/input_common/gcadapter/gc_poller.cpp
+++ b/src/input_common/gcadapter/gc_poller.cpp
@@ -15,7 +15,7 @@ namespace InputCommon {
class GCButton final : public Input::ButtonDevice {
public:
- explicit GCButton(int port_, int button_, GCAdapter::Adapter* adapter)
+ explicit GCButton(int port_, int button_, const GCAdapter::Adapter* adapter)
: port(port_), button(button_), gcadapter(adapter) {}
~GCButton() override;
@@ -30,15 +30,16 @@ public:
private:
const int port;
const int button;
- GCAdapter::Adapter* gcadapter;
+ const GCAdapter::Adapter* gcadapter;
};
class GCAxisButton final : public Input::ButtonDevice {
public:
explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_,
- GCAdapter::Adapter* adapter)
+ const GCAdapter::Adapter* adapter)
: port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_),
- gcadapter(adapter), origin_value(adapter->GetOriginValue(port_, axis_)) {}
+ gcadapter(adapter),
+ origin_value(static_cast<float>(adapter->GetOriginValue(port_, axis_))) {}
bool GetStatus() const override {
if (gcadapter->DeviceConnected(port)) {
@@ -59,7 +60,7 @@ private:
const int axis;
float threshold;
bool trigger_if_greater;
- GCAdapter::Adapter* gcadapter;
+ const GCAdapter::Adapter* gcadapter;
const float origin_value;
};
@@ -76,8 +77,7 @@ std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::Param
// button is not an axis/stick button
if (button_id != PAD_STICK_ID) {
- auto button = std::make_unique<GCButton>(port, button_id, adapter.get());
- return std::move(button);
+ return std::make_unique<GCButton>(port, button_id, adapter.get());
}
// For Axis buttons, used by the binary sticks.
@@ -149,19 +149,18 @@ void GCButtonFactory::EndConfiguration() {
class GCAnalog final : public Input::AnalogDevice {
public:
- GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, GCAdapter::Adapter* adapter)
+ GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_,
+ const GCAdapter::Adapter* adapter, float range_)
: port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter),
- origin_value_x(adapter->GetOriginValue(port_, axis_x_)),
- origin_value_y(adapter->GetOriginValue(port_, axis_y_)) {}
+ origin_value_x(static_cast<float>(adapter->GetOriginValue(port_, axis_x_))),
+ origin_value_y(static_cast<float>(adapter->GetOriginValue(port_, axis_y_))),
+ range(range_) {}
float GetAxis(int axis) const {
if (gcadapter->DeviceConnected(port)) {
std::lock_guard lock{mutex};
const auto origin_value = axis % 2 == 0 ? origin_value_x : origin_value_y;
- // division is not by a perfect 128 to account for some variance in center location
- // e.g. my device idled at 131 in X, 120 in Y, and full range of motion was in range
- // [20-230]
- return (gcadapter->GetPadState()[port].axes.at(axis) - origin_value) / 95.0f;
+ return (gcadapter->GetPadState()[port].axes.at(axis) - origin_value) / (100.0f * range);
}
return 0.0f;
}
@@ -194,7 +193,7 @@ public:
bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
const auto [x, y] = GetStatus();
- const float directional_deadzone = 0.4f;
+ const float directional_deadzone = 0.5f;
switch (direction) {
case Input::AnalogDirection::RIGHT:
return x > directional_deadzone;
@@ -213,9 +212,10 @@ private:
const int axis_x;
const int axis_y;
const float deadzone;
- GCAdapter::Adapter* gcadapter;
+ const GCAdapter::Adapter* gcadapter;
const float origin_value_x;
const float origin_value_y;
+ const float range;
mutable std::mutex mutex;
};
@@ -234,9 +234,10 @@ std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::Param
const int port = params.Get("port", 0);
const int axis_x = params.Get("axis_x", 0);
const int axis_y = params.Get("axis_y", 1);
- const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f);
+ const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
+ const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
- return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get());
+ return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get(), range);
}
void GCAnalogFactory::BeginConfiguration() {
@@ -264,7 +265,8 @@ Common::ParamPackage GCAnalogFactory::GetNextInput() {
if (analog_x_axis == -1) {
analog_x_axis = axis;
controller_number = static_cast<int>(port);
- } else if (analog_y_axis == -1 && analog_x_axis != axis && controller_number == port) {
+ } else if (analog_y_axis == -1 && analog_x_axis != axis &&
+ controller_number == static_cast<int>(port)) {
analog_y_axis = axis;
}
}
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index b9d5d0ec3..ea1a1cee6 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -11,6 +11,7 @@
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "input_common/motion_emu.h"
+#include "input_common/touch_from_button.h"
#include "input_common/udp/udp.h"
#ifdef HAVE_SDL2
#include "input_common/sdl/sdl.h"
@@ -18,67 +19,176 @@
namespace InputCommon {
-static std::shared_ptr<Keyboard> keyboard;
-static std::shared_ptr<MotionEmu> motion_emu;
+struct InputSubsystem::Impl {
+ void Initialize() {
+ auto gcadapter = std::make_shared<GCAdapter::Adapter>();
+ gcbuttons = std::make_shared<GCButtonFactory>(gcadapter);
+ Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons);
+ gcanalog = std::make_shared<GCAnalogFactory>(gcadapter);
+ Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog);
+
+ keyboard = std::make_shared<Keyboard>();
+ Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
+ Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
+ std::make_shared<AnalogFromButton>());
+ motion_emu = std::make_shared<MotionEmu>();
+ Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
+ Input::RegisterFactory<Input::TouchDevice>("touch_from_button",
+ std::make_shared<TouchFromButtonFactory>());
+
#ifdef HAVE_SDL2
-static std::unique_ptr<SDL::State> sdl;
+ sdl = SDL::Init();
#endif
-static std::unique_ptr<CemuhookUDP::State> udp;
-static std::shared_ptr<GCButtonFactory> gcbuttons;
-static std::shared_ptr<GCAnalogFactory> gcanalog;
-
-void Init() {
- auto gcadapter = std::make_shared<GCAdapter::Adapter>();
- gcbuttons = std::make_shared<GCButtonFactory>(gcadapter);
- Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons);
- gcanalog = std::make_shared<GCAnalogFactory>(gcadapter);
- Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog);
-
- keyboard = std::make_shared<Keyboard>();
- Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
- Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
- std::make_shared<AnalogFromButton>());
- motion_emu = std::make_shared<MotionEmu>();
- Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
+ udp = CemuhookUDP::Init();
+ }
+
+ void Shutdown() {
+ Input::UnregisterFactory<Input::ButtonDevice>("keyboard");
+ keyboard.reset();
+ Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
+ Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
+ motion_emu.reset();
+ Input::UnregisterFactory<Input::TouchDevice>("touch_from_button");
#ifdef HAVE_SDL2
- sdl = SDL::Init();
+ sdl.reset();
#endif
+ udp.reset();
+ Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
+ Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
+
+ gcbuttons.reset();
+ gcanalog.reset();
+ }
+
+ [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
+ std::vector<Common::ParamPackage> devices = {
+ Common::ParamPackage{{"display", "Any"}, {"class", "any"}},
+ Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "key"}},
+ };
+#ifdef HAVE_SDL2
+ auto sdl_devices = sdl->GetInputDevices();
+ devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
+#endif
+ auto udp_devices = udp->GetInputDevices();
+ devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
+ return devices;
+ }
+
+ [[nodiscard]] AnalogMapping GetAnalogMappingForDevice(
+ const Common::ParamPackage& params) const {
+ if (!params.Has("class") || params.Get("class", "") == "any") {
+ return {};
+ }
+ if (params.Get("class", "") == "key") {
+ // TODO consider returning the SDL key codes for the default keybindings
+ return {};
+ }
+#ifdef HAVE_SDL2
+ if (params.Get("class", "") == "sdl") {
+ return sdl->GetAnalogMappingForDevice(params);
+ }
+#endif
+ return {};
+ }
+
+ [[nodiscard]] ButtonMapping GetButtonMappingForDevice(
+ const Common::ParamPackage& params) const {
+ if (!params.Has("class") || params.Get("class", "") == "any") {
+ return {};
+ }
+ if (params.Get("class", "") == "key") {
+ // TODO consider returning the SDL key codes for the default keybindings
+ return {};
+ }
+#ifdef HAVE_SDL2
+ if (params.Get("class", "") == "sdl") {
+ return sdl->GetButtonMappingForDevice(params);
+ }
+#endif
+ return {};
+ }
- udp = CemuhookUDP::Init();
-}
-
-void Shutdown() {
- Input::UnregisterFactory<Input::ButtonDevice>("keyboard");
- keyboard.reset();
- Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
- Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
- motion_emu.reset();
+ std::shared_ptr<Keyboard> keyboard;
+ std::shared_ptr<MotionEmu> motion_emu;
#ifdef HAVE_SDL2
- sdl.reset();
+ std::unique_ptr<SDL::State> sdl;
#endif
- udp.reset();
- Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
- Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
+ std::unique_ptr<CemuhookUDP::State> udp;
+ std::shared_ptr<GCButtonFactory> gcbuttons;
+ std::shared_ptr<GCAnalogFactory> gcanalog;
+};
+
+InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
+
+InputSubsystem::~InputSubsystem() = default;
+
+void InputSubsystem::Initialize() {
+ impl->Initialize();
+}
+
+void InputSubsystem::Shutdown() {
+ impl->Shutdown();
+}
+
+Keyboard* InputSubsystem::GetKeyboard() {
+ return impl->keyboard.get();
+}
+
+const Keyboard* InputSubsystem::GetKeyboard() const {
+ return impl->keyboard.get();
+}
+
+MotionEmu* InputSubsystem::GetMotionEmu() {
+ return impl->motion_emu.get();
+}
+
+const MotionEmu* InputSubsystem::GetMotionEmu() const {
+ return impl->motion_emu.get();
+}
- gcbuttons.reset();
- gcanalog.reset();
+std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
+ return impl->GetInputDevices();
}
-Keyboard* GetKeyboard() {
- return keyboard.get();
+AnalogMapping InputSubsystem::GetAnalogMappingForDevice(const Common::ParamPackage& device) const {
+ return impl->GetAnalogMappingForDevice(device);
}
-MotionEmu* GetMotionEmu() {
- return motion_emu.get();
+ButtonMapping InputSubsystem::GetButtonMappingForDevice(const Common::ParamPackage& device) const {
+ return impl->GetButtonMappingForDevice(device);
}
-GCButtonFactory* GetGCButtons() {
- return gcbuttons.get();
+GCAnalogFactory* InputSubsystem::GetGCAnalogs() {
+ return impl->gcanalog.get();
}
-GCAnalogFactory* GetGCAnalogs() {
- return gcanalog.get();
+const GCAnalogFactory* InputSubsystem::GetGCAnalogs() const {
+ return impl->gcanalog.get();
+}
+
+GCButtonFactory* InputSubsystem::GetGCButtons() {
+ return impl->gcbuttons.get();
+}
+
+const GCButtonFactory* InputSubsystem::GetGCButtons() const {
+ return impl->gcbuttons.get();
+}
+
+void InputSubsystem::ReloadInputDevices() {
+ if (!impl->udp) {
+ return;
+ }
+ impl->udp->ReloadUDPClient();
+}
+
+std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers(
+ Polling::DeviceType type) const {
+#ifdef HAVE_SDL2
+ return impl->sdl->GetPollers(type);
+#else
+ return {};
+#endif
}
std::string GenerateKeyboardParam(int key_code) {
@@ -102,18 +212,4 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,
};
return circle_pad_param.Serialize();
}
-
-namespace Polling {
-
-std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {
- std::vector<std::unique_ptr<DevicePoller>> pollers;
-
-#ifdef HAVE_SDL2
- pollers = sdl->GetPollers(type);
-#endif
-
- return pollers;
-}
-
-} // namespace Polling
} // namespace InputCommon
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 0e32856f6..f3fbf696e 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -6,45 +6,25 @@
#include <memory>
#include <string>
+#include <unordered_map>
#include <vector>
-#include "input_common/gcadapter/gc_poller.h"
namespace Common {
class ParamPackage;
}
-namespace InputCommon {
-
-/// Initializes and registers all built-in input device factories.
-void Init();
-
-/// Deregisters all built-in input device factories and shuts them down.
-void Shutdown();
-
-class Keyboard;
-
-/// Gets the keyboard button device factory.
-Keyboard* GetKeyboard();
-
-class MotionEmu;
-
-/// Gets the motion emulation factory.
-MotionEmu* GetMotionEmu();
-
-GCButtonFactory* GetGCButtons();
-
-GCAnalogFactory* GetGCAnalogs();
-
-/// Generates a serialized param package for creating a keyboard button device
-std::string GenerateKeyboardParam(int key_code);
+namespace Settings::NativeAnalog {
+enum Values : int;
+}
-/// Generates a serialized param package for creating an analog device taking input from keyboard
-std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
- int key_modifier, float modifier_scale);
+namespace Settings::NativeButton {
+enum Values : int;
+}
+namespace InputCommon {
namespace Polling {
-enum class DeviceType { Button, Analog };
+enum class DeviceType { Button, AnalogPreferred };
/**
* A class that can be used to get inputs from an input device like controllers without having to
@@ -54,7 +34,9 @@ class DevicePoller {
public:
virtual ~DevicePoller() = default;
/// Setup and start polling for inputs, should be called before GetNextInput
- virtual void Start() = 0;
+ /// If a device_id is provided, events should be filtered to only include events from this
+ /// device id
+ virtual void Start(const std::string& device_id = "") = 0;
/// Stop polling
virtual void Stop() = 0;
/**
@@ -64,8 +46,92 @@ public:
*/
virtual Common::ParamPackage GetNextInput() = 0;
};
-
-// Get all DevicePoller from all backends for a specific device type
-std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type);
} // namespace Polling
+
+class GCAnalogFactory;
+class GCButtonFactory;
+class Keyboard;
+class MotionEmu;
+
+/**
+ * Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default
+ * mapping for the device. This is currently only implemented for the SDL backend devices.
+ */
+using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>;
+using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>;
+
+class InputSubsystem {
+public:
+ explicit InputSubsystem();
+ ~InputSubsystem();
+
+ InputSubsystem(const InputSubsystem&) = delete;
+ InputSubsystem& operator=(const InputSubsystem&) = delete;
+
+ InputSubsystem(InputSubsystem&&) = delete;
+ InputSubsystem& operator=(InputSubsystem&&) = delete;
+
+ /// Initializes and registers all built-in input device factories.
+ void Initialize();
+
+ /// Unregisters all built-in input device factories and shuts them down.
+ void Shutdown();
+
+ /// Retrieves the underlying keyboard device.
+ [[nodiscard]] Keyboard* GetKeyboard();
+
+ /// Retrieves the underlying keyboard device.
+ [[nodiscard]] const Keyboard* GetKeyboard() const;
+
+ /// Retrieves the underlying motion emulation factory.
+ [[nodiscard]] MotionEmu* GetMotionEmu();
+
+ /// Retrieves the underlying motion emulation factory.
+ [[nodiscard]] const MotionEmu* GetMotionEmu() const;
+
+ /**
+ * Returns all available input devices that this Factory can create a new device with.
+ * Each returned ParamPackage should have a `display` field used for display, a class field for
+ * backends to determine if this backend is meant to service the request and any other
+ * information needed to identify this in the backend later.
+ */
+ [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const;
+
+ /// Retrieves the analog mappings for the given device.
+ [[nodiscard]] AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& device) const;
+
+ /// Retrieves the button mappings for the given device.
+ [[nodiscard]] ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& device) const;
+
+ /// Retrieves the underlying GameCube analog handler.
+ [[nodiscard]] GCAnalogFactory* GetGCAnalogs();
+
+ /// Retrieves the underlying GameCube analog handler.
+ [[nodiscard]] const GCAnalogFactory* GetGCAnalogs() const;
+
+ /// Retrieves the underlying GameCube button handler.
+ [[nodiscard]] GCButtonFactory* GetGCButtons();
+
+ /// Retrieves the underlying GameCube button handler.
+ [[nodiscard]] const GCButtonFactory* GetGCButtons() const;
+
+ /// Reloads the input devices
+ void ReloadInputDevices();
+
+ /// Get all DevicePoller from all backends for a specific device type
+ [[nodiscard]] std::vector<std::unique_ptr<Polling::DevicePoller>> GetPollers(
+ Polling::DeviceType type) const;
+
+private:
+ struct Impl;
+ std::unique_ptr<Impl> impl;
+};
+
+/// Generates a serialized param package for creating a keyboard button device
+std::string GenerateKeyboardParam(int key_code);
+
+/// Generates a serialized param package for creating an analog device taking input from keyboard
+std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
+ int key_modifier, float modifier_scale);
+
} // namespace InputCommon
diff --git a/src/input_common/motion_input.cpp b/src/input_common/motion_input.cpp
new file mode 100644
index 000000000..22a849866
--- /dev/null
+++ b/src/input_common/motion_input.cpp
@@ -0,0 +1,181 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#include "common/math_util.h"
+#include "input_common/motion_input.h"
+
+namespace InputCommon {
+
+MotionInput::MotionInput(f32 new_kp, f32 new_ki, f32 new_kd)
+ : kp(new_kp), ki(new_ki), kd(new_kd), quat{{0, 0, -1}, 0} {}
+
+void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) {
+ accel = acceleration;
+}
+
+void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) {
+ gyro = gyroscope - gyro_drift;
+ if (gyro.Length2() < gyro_threshold) {
+ gyro = {};
+ }
+}
+
+void MotionInput::SetQuaternion(const Common::Quaternion<f32>& quaternion) {
+ quat = quaternion;
+}
+
+void MotionInput::SetGyroDrift(const Common::Vec3f& drift) {
+ gyro_drift = drift;
+}
+
+void MotionInput::SetGyroThreshold(f32 threshold) {
+ gyro_threshold = threshold;
+}
+
+void MotionInput::EnableReset(bool reset) {
+ reset_enabled = reset;
+}
+
+void MotionInput::ResetRotations() {
+ rotations = {};
+}
+
+bool MotionInput::IsMoving(f32 sensitivity) const {
+ return gyro.Length() >= sensitivity || accel.Length() <= 0.9f || accel.Length() >= 1.1f;
+}
+
+bool MotionInput::IsCalibrated(f32 sensitivity) const {
+ return real_error.Length() < sensitivity;
+}
+
+void MotionInput::UpdateRotation(u64 elapsed_time) {
+ const f32 sample_period = elapsed_time / 1000000.0f;
+ if (sample_period > 0.1f) {
+ return;
+ }
+ rotations += gyro * sample_period;
+}
+
+void MotionInput::UpdateOrientation(u64 elapsed_time) {
+ if (!IsCalibrated(0.1f)) {
+ ResetOrientation();
+ }
+ // Short name local variable for readability
+ f32 q1 = quat.w;
+ f32 q2 = quat.xyz[0];
+ f32 q3 = quat.xyz[1];
+ f32 q4 = quat.xyz[2];
+ const f32 sample_period = elapsed_time / 1000000.0f;
+
+ // ignore invalid elapsed time
+ if (sample_period > 0.1f) {
+ return;
+ }
+
+ const auto normal_accel = accel.Normalized();
+ auto rad_gyro = gyro * Common::PI * 2;
+ const f32 swap = rad_gyro.x;
+ rad_gyro.x = rad_gyro.y;
+ rad_gyro.y = -swap;
+ rad_gyro.z = -rad_gyro.z;
+
+ // Ignore drift correction if acceleration is not reliable
+ if (accel.Length() >= 0.75f && accel.Length() <= 1.25f) {
+ const f32 ax = -normal_accel.x;
+ const f32 ay = normal_accel.y;
+ const f32 az = -normal_accel.z;
+
+ // Estimated direction of gravity
+ const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
+ const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
+ const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
+
+ // Error is cross product between estimated direction and measured direction of gravity
+ const Common::Vec3f new_real_error = {az * vx - ax * vz, ay * vz - az * vy,
+ ax * vy - ay * vx};
+
+ derivative_error = new_real_error - real_error;
+ real_error = new_real_error;
+
+ // Prevent integral windup
+ if (ki != 0.0f && !IsCalibrated(0.05f)) {
+ integral_error += real_error;
+ } else {
+ integral_error = {};
+ }
+
+ // Apply feedback terms
+ rad_gyro += kp * real_error;
+ rad_gyro += ki * integral_error;
+ rad_gyro += kd * derivative_error;
+ }
+
+ const f32 gx = rad_gyro.y;
+ const f32 gy = rad_gyro.x;
+ const f32 gz = rad_gyro.z;
+
+ // Integrate rate of change of quaternion
+ const f32 pa = q2;
+ const f32 pb = q3;
+ const f32 pc = q4;
+ q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
+ q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
+ q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
+ q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
+
+ quat.w = q1;
+ quat.xyz[0] = q2;
+ quat.xyz[1] = q3;
+ quat.xyz[2] = q4;
+ quat = quat.Normalized();
+}
+
+std::array<Common::Vec3f, 3> MotionInput::GetOrientation() const {
+ const Common::Quaternion<float> quad{
+ .xyz = {-quat.xyz[1], -quat.xyz[0], -quat.w},
+ .w = -quat.xyz[2],
+ };
+ const std::array<float, 16> matrix4x4 = quad.ToMatrix();
+
+ return {Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]),
+ Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]),
+ Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10])};
+}
+
+Common::Vec3f MotionInput::GetAcceleration() const {
+ return accel;
+}
+
+Common::Vec3f MotionInput::GetGyroscope() const {
+ return gyro;
+}
+
+Common::Quaternion<f32> MotionInput::GetQuaternion() const {
+ return quat;
+}
+
+Common::Vec3f MotionInput::GetRotations() const {
+ return rotations;
+}
+
+void MotionInput::ResetOrientation() {
+ if (!reset_enabled) {
+ return;
+ }
+ if (!IsMoving(0.5f) && accel.z <= -0.9f) {
+ ++reset_counter;
+ if (reset_counter > 900) {
+ // TODO: calculate quaternion from gravity vector
+ quat.w = 0;
+ quat.xyz[0] = 0;
+ quat.xyz[1] = 0;
+ quat.xyz[2] = -1;
+ integral_error = {};
+ reset_counter = 0;
+ }
+ } else {
+ reset_counter = 0;
+ }
+}
+} // namespace InputCommon
diff --git a/src/input_common/motion_input.h b/src/input_common/motion_input.h
new file mode 100644
index 000000000..54b4439d9
--- /dev/null
+++ b/src/input_common/motion_input.h
@@ -0,0 +1,68 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#pragma once
+
+#include "common/common_types.h"
+#include "common/quaternion.h"
+#include "common/vector_math.h"
+
+namespace InputCommon {
+
+class MotionInput {
+public:
+ MotionInput(f32 new_kp, f32 new_ki, f32 new_kd);
+
+ MotionInput(const MotionInput&) = default;
+ MotionInput& operator=(const MotionInput&) = default;
+
+ MotionInput(MotionInput&&) = default;
+ MotionInput& operator=(MotionInput&&) = default;
+
+ void SetAcceleration(const Common::Vec3f& acceleration);
+ void SetGyroscope(const Common::Vec3f& acceleration);
+ void SetQuaternion(const Common::Quaternion<f32>& quaternion);
+ void SetGyroDrift(const Common::Vec3f& drift);
+ void SetGyroThreshold(f32 threshold);
+
+ void EnableReset(bool reset);
+ void ResetRotations();
+
+ void UpdateRotation(u64 elapsed_time);
+ void UpdateOrientation(u64 elapsed_time);
+
+ std::array<Common::Vec3f, 3> GetOrientation() const;
+ Common::Vec3f GetAcceleration() const;
+ Common::Vec3f GetGyroscope() const;
+ Common::Vec3f GetRotations() const;
+ Common::Quaternion<f32> GetQuaternion() const;
+
+ bool IsMoving(f32 sensitivity) const;
+ bool IsCalibrated(f32 sensitivity) const;
+
+private:
+ void ResetOrientation();
+
+ // PID constants
+ const f32 kp;
+ const f32 ki;
+ const f32 kd;
+
+ // PID errors
+ Common::Vec3f real_error;
+ Common::Vec3f integral_error;
+ Common::Vec3f derivative_error;
+
+ Common::Quaternion<f32> quat;
+ Common::Vec3f rotations;
+ Common::Vec3f accel;
+ Common::Vec3f gyro;
+ Common::Vec3f gyro_drift;
+
+ f32 gyro_threshold = 0.0f;
+ u32 reset_counter = 0;
+ bool reset_enabled = true;
+};
+
+} // namespace InputCommon
diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h
index 5306daa70..f3554be9a 100644
--- a/src/input_common/sdl/sdl.h
+++ b/src/input_common/sdl/sdl.h
@@ -6,6 +6,7 @@
#include <memory>
#include <vector>
+#include "common/param_package.h"
#include "input_common/main.h"
namespace InputCommon::Polling {
@@ -22,14 +23,24 @@ public:
/// Unregisters SDL device factories and shut them down.
virtual ~State() = default;
- virtual Pollers GetPollers(Polling::DeviceType type) = 0;
+ virtual Pollers GetPollers(Polling::DeviceType type) {
+ return {};
+ }
+
+ virtual std::vector<Common::ParamPackage> GetInputDevices() {
+ return {};
+ }
+
+ virtual ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage&) {
+ return {};
+ }
+ virtual AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage&) {
+ return {};
+ }
};
class NullState : public State {
public:
- Pollers GetPollers(Polling::DeviceType type) override {
- return {};
- }
};
std::unique_ptr<State> Init();
diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp
index 675b477fa..a9e676f4b 100644
--- a/src/input_common/sdl/sdl_impl.cpp
+++ b/src/input_common/sdl/sdl_impl.cpp
@@ -3,10 +3,13 @@
// Refer to the license.txt file included.
#include <algorithm>
+#include <array>
#include <atomic>
#include <cmath>
#include <functional>
#include <mutex>
+#include <optional>
+#include <sstream>
#include <string>
#include <thread>
#include <tuple>
@@ -15,15 +18,16 @@
#include <vector>
#include <SDL.h>
#include "common/logging/log.h"
-#include "common/math_util.h"
#include "common/param_package.h"
#include "common/threadsafe_queue.h"
#include "core/frontend/input.h"
#include "input_common/sdl/sdl_impl.h"
+#include "input_common/settings.h"
namespace InputCommon::SDL {
-static std::string GetGUID(SDL_Joystick* joystick) {
+namespace {
+std::string GetGUID(SDL_Joystick* joystick) {
const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
char guid_str[33];
SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
@@ -31,7 +35,8 @@ static std::string GetGUID(SDL_Joystick* joystick) {
}
/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice
-static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event);
+Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event);
+} // Anonymous namespace
static int SDLEventWatcher(void* user_data, SDL_Event* event) {
auto* const sdl_state = static_cast<SDLState*>(user_data);
@@ -48,8 +53,10 @@ static int SDLEventWatcher(void* user_data, SDL_Event* event) {
class SDLJoystick {
public:
- SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick)
- : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose} {}
+ SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
+ SDL_GameController* gamecontroller)
+ : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose},
+ sdl_controller{gamecontroller, &SDL_GameControllerClose} {}
void SetButton(int button, bool value) {
std::lock_guard lock{mutex};
@@ -66,14 +73,14 @@ public:
state.axes.insert_or_assign(axis, value);
}
- float GetAxis(int axis) const {
+ float GetAxis(int axis, float range) const {
std::lock_guard lock{mutex};
- return state.axes.at(axis) / 32767.0f;
+ return state.axes.at(axis) / (32767.0f * range);
}
- std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const {
- float x = GetAxis(axis_x);
- float y = GetAxis(axis_y);
+ std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range) const {
+ float x = GetAxis(axis_x, range);
+ float y = GetAxis(axis_y, range);
y = -y; // 3DS uses an y-axis inverse from SDL
// Make sure the coordinates are in the unit circle,
@@ -115,10 +122,15 @@ public:
return sdl_joystick.get();
}
- void SetSDLJoystick(SDL_Joystick* joystick) {
+ void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) {
+ sdl_controller.reset(controller);
sdl_joystick.reset(joystick);
}
+ SDL_GameController* GetSDLGameController() const {
+ return sdl_controller.get();
+ }
+
private:
struct State {
std::unordered_map<int, bool> buttons;
@@ -128,6 +140,7 @@ private:
std::string guid;
int port;
std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
+ std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
mutable std::mutex mutex;
};
@@ -136,18 +149,19 @@ std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& g
const auto it = joystick_map.find(guid);
if (it != joystick_map.end()) {
while (it->second.size() <= static_cast<std::size_t>(port)) {
- auto joystick =
- std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()), nullptr);
+ auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()),
+ nullptr, nullptr);
it->second.emplace_back(std::move(joystick));
}
return it->second[port];
}
- auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr);
+ auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr);
return joystick_map[guid].emplace_back(std::move(joystick));
}
std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
+ auto sdl_controller = SDL_GameControllerFromInstanceID(sdl_id);
const std::string guid = GetGUID(sdl_joystick);
std::lock_guard lock{joystick_map_mutex};
@@ -171,23 +185,27 @@ std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_
});
if (nullptr_it != map_it->second.end()) {
// ... and map it
- (*nullptr_it)->SetSDLJoystick(sdl_joystick);
+ (*nullptr_it)->SetSDLJoystick(sdl_joystick, sdl_controller);
return *nullptr_it;
}
// There is no SDLJoystick without a mapped SDL_Joystick
// Create a new SDLJoystick
const int port = static_cast<int>(map_it->second.size());
- auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick);
+ auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_controller);
return map_it->second.emplace_back(std::move(joystick));
}
- auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
+ auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_controller);
return joystick_map[guid].emplace_back(std::move(joystick));
}
void SDLState::InitJoystick(int joystick_index) {
SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
+ SDL_GameController* sdl_gamecontroller = nullptr;
+ if (SDL_IsGameController(joystick_index)) {
+ sdl_gamecontroller = SDL_GameControllerOpen(joystick_index);
+ }
if (!sdl_joystick) {
LOG_ERROR(Input, "failed to open joystick {}", joystick_index);
return;
@@ -196,7 +214,7 @@ void SDLState::InitJoystick(int joystick_index) {
std::lock_guard lock{joystick_map_mutex};
if (joystick_map.find(guid) == joystick_map.end()) {
- auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
+ auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
joystick_map[guid].emplace_back(std::move(joystick));
return;
}
@@ -205,11 +223,11 @@ void SDLState::InitJoystick(int joystick_index) {
joystick_guid_list.begin(), joystick_guid_list.end(),
[](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); });
if (it != joystick_guid_list.end()) {
- (*it)->SetSDLJoystick(sdl_joystick);
+ (*it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);
return;
}
const int port = static_cast<int>(joystick_guid_list.size());
- auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick);
+ auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller);
joystick_guid_list.emplace_back(std::move(joystick));
}
@@ -231,7 +249,7 @@ void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) {
// Destruct SDL_Joystick outside the lock guard because SDL can internally call the
// event callback which locks the mutex again.
- joystick->SetSDLJoystick(nullptr);
+ joystick->SetSDLJoystick(nullptr, nullptr);
}
void SDLState::HandleGameControllerEvent(const SDL_Event& event) {
@@ -313,7 +331,7 @@ public:
trigger_if_greater(trigger_if_greater_) {}
bool GetStatus() const override {
- const float axis_value = joystick->GetAxis(axis);
+ const float axis_value = joystick->GetAxis(axis, 1.0f);
if (trigger_if_greater) {
return axis_value > threshold;
}
@@ -329,22 +347,24 @@ private:
class SDLAnalog final : public Input::AnalogDevice {
public:
- SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, float deadzone_)
- : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_) {}
+ SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, float deadzone_,
+ float range_)
+ : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_),
+ range(range_) {}
std::tuple<float, float> GetStatus() const override {
- const auto [x, y] = joystick->GetAnalog(axis_x, axis_y);
+ const auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range);
const float r = std::sqrt((x * x) + (y * y));
if (r > deadzone) {
return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone),
y / r * (r - deadzone) / (1 - deadzone));
}
- return std::make_tuple<float, float>(0.0f, 0.0f);
+ return {};
}
bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
const auto [x, y] = GetStatus();
- const float directional_deadzone = 0.4f;
+ const float directional_deadzone = 0.5f;
switch (direction) {
case Input::AnalogDirection::RIGHT:
return x > directional_deadzone;
@@ -363,6 +383,7 @@ private:
const int axis_x;
const int axis_y;
const float deadzone;
+ const float range;
};
/// A button device factory that creates button devices from SDL joystick
@@ -457,14 +478,14 @@ public:
const int port = params.Get("port", 0);
const int axis_x = params.Get("axis_x", 0);
const int axis_y = params.Get("axis_y", 1);
- const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f);
-
+ const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
+ const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
auto joystick = state.GetSDLJoystickByGUID(guid, port);
// This is necessary so accessing GetAxis with axis_x and axis_y won't crash
joystick->SetAxis(axis_x, 0);
joystick->SetAxis(axis_y, 0);
- return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone);
+ return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone, range);
}
private:
@@ -473,8 +494,10 @@ private:
SDLState::SDLState() {
using namespace Input;
- RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>(*this));
- RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>(*this));
+ analog_factory = std::make_shared<SDLAnalogFactory>(*this);
+ button_factory = std::make_shared<SDLButtonFactory>(*this);
+ RegisterFactory<AnalogDevice>("sdl", analog_factory);
+ RegisterFactory<ButtonDevice>("sdl", button_factory);
// If the frontend is going to manage the event loop, then we dont start one here
start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK);
@@ -482,6 +505,7 @@ SDLState::SDLState() {
LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());
return;
}
+ has_gamecontroller = SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER);
if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) {
LOG_ERROR(Input, "Failed to set hint for background events with: {}", SDL_GetError());
}
@@ -494,7 +518,7 @@ SDLState::SDLState() {
using namespace std::chrono_literals;
while (initialized) {
SDL_PumpEvents();
- std::this_thread::sleep_for(10ms);
+ std::this_thread::sleep_for(5ms);
}
});
}
@@ -520,65 +544,230 @@ SDLState::~SDLState() {
}
}
-static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) {
+std::vector<Common::ParamPackage> SDLState::GetInputDevices() {
+ std::scoped_lock lock(joystick_map_mutex);
+ std::vector<Common::ParamPackage> devices;
+ for (const auto& [key, value] : joystick_map) {
+ for (const auto& joystick : value) {
+ auto joy = joystick->GetSDLJoystick();
+ if (auto controller = joystick->GetSDLGameController()) {
+ std::string name =
+ fmt::format("{} {}", SDL_GameControllerName(controller), joystick->GetPort());
+ devices.emplace_back(Common::ParamPackage{
+ {"class", "sdl"},
+ {"display", std::move(name)},
+ {"guid", joystick->GetGUID()},
+ {"port", std::to_string(joystick->GetPort())},
+ });
+ } else if (joy) {
+ std::string name = fmt::format("{} {}", SDL_JoystickName(joy), joystick->GetPort());
+ devices.emplace_back(Common::ParamPackage{
+ {"class", "sdl"},
+ {"display", std::move(name)},
+ {"guid", joystick->GetGUID()},
+ {"port", std::to_string(joystick->GetPort())},
+ });
+ }
+ }
+ }
+ return devices;
+}
+
+namespace {
+Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, u8 axis,
+ float value = 0.1f) {
+ Common::ParamPackage params({{"engine", "sdl"}});
+ params.Set("port", port);
+ params.Set("guid", std::move(guid));
+ params.Set("axis", axis);
+ if (value > 0) {
+ params.Set("direction", "+");
+ params.Set("threshold", "0.5");
+ } else {
+ params.Set("direction", "-");
+ params.Set("threshold", "-0.5");
+ }
+ return params;
+}
+
+Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, u8 button) {
Common::ParamPackage params({{"engine", "sdl"}});
+ params.Set("port", port);
+ params.Set("guid", std::move(guid));
+ params.Set("button", button);
+ return params;
+}
+
+Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, u8 hat, u8 value) {
+ Common::ParamPackage params({{"engine", "sdl"}});
+
+ params.Set("port", port);
+ params.Set("guid", std::move(guid));
+ params.Set("hat", hat);
+ switch (value) {
+ case SDL_HAT_UP:
+ params.Set("direction", "up");
+ break;
+ case SDL_HAT_DOWN:
+ params.Set("direction", "down");
+ break;
+ case SDL_HAT_LEFT:
+ params.Set("direction", "left");
+ break;
+ case SDL_HAT_RIGHT:
+ params.Set("direction", "right");
+ break;
+ default:
+ return {};
+ }
+ return params;
+}
+Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) {
switch (event.type) {
case SDL_JOYAXISMOTION: {
const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
- params.Set("port", joystick->GetPort());
- params.Set("guid", joystick->GetGUID());
- params.Set("axis", event.jaxis.axis);
- if (event.jaxis.value > 0) {
- params.Set("direction", "+");
- params.Set("threshold", "0.5");
- } else {
- params.Set("direction", "-");
- params.Set("threshold", "-0.5");
- }
- break;
+ return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ event.jaxis.axis, event.jaxis.value);
}
case SDL_JOYBUTTONUP: {
const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which);
- params.Set("port", joystick->GetPort());
- params.Set("guid", joystick->GetGUID());
- params.Set("button", event.jbutton.button);
- break;
+ return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ event.jbutton.button);
}
case SDL_JOYHATMOTION: {
const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which);
- params.Set("port", joystick->GetPort());
- params.Set("guid", joystick->GetGUID());
- params.Set("hat", event.jhat.hat);
- switch (event.jhat.value) {
- case SDL_HAT_UP:
- params.Set("direction", "up");
- break;
- case SDL_HAT_DOWN:
- params.Set("direction", "down");
- break;
- case SDL_HAT_LEFT:
- params.Set("direction", "left");
- break;
- case SDL_HAT_RIGHT:
- params.Set("direction", "right");
- break;
- default:
- return {};
- }
- break;
+ return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ event.jhat.hat, event.jhat.value);
}
}
+ return {};
+}
+
+Common::ParamPackage BuildParamPackageForBinding(int port, const std::string& guid,
+ const SDL_GameControllerButtonBind& binding) {
+ switch (binding.bindType) {
+ case SDL_CONTROLLER_BINDTYPE_AXIS:
+ return BuildAnalogParamPackageForButton(port, guid, binding.value.axis);
+ case SDL_CONTROLLER_BINDTYPE_BUTTON:
+ return BuildButtonParamPackageForButton(port, guid, binding.value.button);
+ case SDL_CONTROLLER_BINDTYPE_HAT:
+ return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat,
+ binding.value.hat.hat_mask);
+ }
+ return {};
+}
+
+Common::ParamPackage BuildParamPackageForAnalog(int port, const std::string& guid, int axis_x,
+ int axis_y) {
+ Common::ParamPackage params;
+ params.Set("engine", "sdl");
+ params.Set("port", port);
+ params.Set("guid", guid);
+ params.Set("axis_x", axis_x);
+ params.Set("axis_y", axis_y);
return params;
}
+} // Anonymous namespace
-namespace Polling {
+ButtonMapping SDLState::GetButtonMappingForDevice(const Common::ParamPackage& params) {
+ if (!params.Has("guid") || !params.Has("port")) {
+ return {};
+ }
+ const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
+ auto* controller = joystick->GetSDLGameController();
+ if (controller == nullptr) {
+ return {};
+ }
+
+ // This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
+ // We will add those afterwards
+ // This list also excludes Screenshot since theres not really a mapping for that
+ using ButtonBindings =
+ std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>;
+ static constexpr ButtonBindings switch_to_sdl_button{{
+ {Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
+ {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
+ {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
+ {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
+ {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
+ {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
+ {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
+ {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
+ {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
+ {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
+ {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
+ {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, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
+ {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
+ {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
+ }};
+
+ // Add the missing bindings for ZL/ZR
+ using ZBindings =
+ std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;
+ static constexpr ZBindings switch_to_sdl_axis{{
+ {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
+ {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
+ }};
+
+ ButtonMapping mapping;
+ mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
+
+ for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
+ const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
+ mapping.insert_or_assign(
+ switch_button,
+ BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
+ }
+ for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
+ const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
+ mapping.insert_or_assign(
+ switch_button,
+ BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
+ }
+
+ return mapping;
+}
+AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
+ if (!params.Has("guid") || !params.Has("port")) {
+ return {};
+ }
+ const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
+ auto* controller = joystick->GetSDLGameController();
+ if (controller == nullptr) {
+ return {};
+ }
+
+ AnalogMapping mapping = {};
+ const auto& binding_left_x =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
+ const auto& binding_left_y =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
+ mapping.insert_or_assign(Settings::NativeAnalog::LStick,
+ BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
+ binding_left_x.value.axis,
+ binding_left_y.value.axis));
+ const auto& binding_right_x =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
+ const auto& binding_right_y =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
+ mapping.insert_or_assign(Settings::NativeAnalog::RStick,
+ BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
+ binding_right_x.value.axis,
+ binding_right_y.value.axis));
+ return mapping;
+}
+
+namespace Polling {
class SDLPoller : public InputCommon::Polling::DevicePoller {
public:
explicit SDLPoller(SDLState& state_) : state(state_) {}
- void Start() override {
+ void Start(const std::string& device_id) override {
state.event_queue.Clear();
state.polling = true;
}
@@ -598,71 +787,106 @@ public:
Common::ParamPackage GetNextInput() override {
SDL_Event event;
while (state.event_queue.Pop(event)) {
- switch (event.type) {
- case SDL_JOYAXISMOTION:
- if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
- break;
- }
- [[fallthrough]];
- case SDL_JOYBUTTONUP:
- case SDL_JOYHATMOTION:
- return SDLEventToButtonParamPackage(state, event);
+ const auto package = FromEvent(event);
+ if (package) {
+ return *package;
}
}
return {};
}
+ [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) const {
+ switch (event.type) {
+ case SDL_JOYAXISMOTION:
+ if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
+ break;
+ }
+ [[fallthrough]];
+ case SDL_JOYBUTTONUP:
+ case SDL_JOYHATMOTION:
+ return {SDLEventToButtonParamPackage(state, event)};
+ }
+ return std::nullopt;
+ }
};
-class SDLAnalogPoller final : public SDLPoller {
+/**
+ * Attempts to match the press to a controller joy axis (left/right stick) and if a match
+ * isn't found, checks if the event matches anything from SDLButtonPoller and uses that
+ * instead
+ */
+class SDLAnalogPreferredPoller final : public SDLPoller {
public:
- explicit SDLAnalogPoller(SDLState& state_) : SDLPoller(state_) {}
-
- void Start() override {
- SDLPoller::Start();
+ explicit SDLAnalogPreferredPoller(SDLState& state_)
+ : SDLPoller(state_), button_poller(state_) {}
+ void Start(const std::string& device_id) override {
+ SDLPoller::Start(device_id);
+ // Load the game controller
// Reset stored axes
analog_x_axis = -1;
analog_y_axis = -1;
- analog_axes_joystick = -1;
}
Common::ParamPackage GetNextInput() override {
SDL_Event event;
while (state.event_queue.Pop(event)) {
- if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) {
+ // Filter out axis events that are below a threshold
+ if (event.type == SDL_JOYAXISMOTION && std::abs(event.jaxis.value / 32767.0) < 0.5) {
continue;
}
- // An analog device needs two axes, so we need to store the axis for later and wait for
- // a second SDL event. The axes also must be from the same joystick.
- const int axis = event.jaxis.axis;
- if (analog_x_axis == -1) {
- analog_x_axis = axis;
- analog_axes_joystick = event.jaxis.which;
- } else if (analog_y_axis == -1 && analog_x_axis != axis &&
- analog_axes_joystick == event.jaxis.which) {
- analog_y_axis = axis;
+ // Simplify controller config by testing if game controller support is enabled.
+ if (event.type == SDL_JOYAXISMOTION) {
+ const auto axis = event.jaxis.axis;
+ const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
+ const auto controller = joystick->GetSDLGameController();
+ if (controller) {
+ const auto axis_left_x =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX)
+ .value.axis;
+ const auto axis_left_y =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY)
+ .value.axis;
+ const auto axis_right_x =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX)
+ .value.axis;
+ const auto axis_right_y =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY)
+ .value.axis;
+
+ if (axis == axis_left_x || axis == axis_left_y) {
+ analog_x_axis = axis_left_x;
+ analog_y_axis = axis_left_y;
+ break;
+ } else if (axis == axis_right_x || axis == axis_right_y) {
+ analog_x_axis = axis_right_x;
+ analog_y_axis = axis_right_y;
+ break;
+ }
+ }
+ }
+
+ // If the press wasn't accepted as a joy axis, check for a button press
+ auto button_press = button_poller.FromEvent(event);
+ if (button_press) {
+ return *button_press;
}
}
- Common::ParamPackage params;
+
if (analog_x_axis != -1 && analog_y_axis != -1) {
const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
- params.Set("engine", "sdl");
- params.Set("port", joystick->GetPort());
- params.Set("guid", joystick->GetGUID());
- params.Set("axis_x", analog_x_axis);
- params.Set("axis_y", analog_y_axis);
+ auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
+ analog_x_axis, analog_y_axis);
analog_x_axis = -1;
analog_y_axis = -1;
- analog_axes_joystick = -1;
return params;
}
- return params;
+ return {};
}
private:
int analog_x_axis = -1;
int analog_y_axis = -1;
- SDL_JoystickID analog_axes_joystick = -1;
+ SDLButtonPoller button_poller;
};
} // namespace Polling
@@ -670,8 +894,8 @@ SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) {
Pollers pollers;
switch (type) {
- case InputCommon::Polling::DeviceType::Analog:
- pollers.emplace_back(std::make_unique<Polling::SDLAnalogPoller>(*this));
+ case InputCommon::Polling::DeviceType::AnalogPreferred:
+ pollers.emplace_back(std::make_unique<Polling::SDLAnalogPreferredPoller>(*this));
break;
case InputCommon::Polling::DeviceType::Button:
pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this));
diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h
index 606a32c5b..bd19ba61d 100644
--- a/src/input_common/sdl/sdl_impl.h
+++ b/src/input_common/sdl/sdl_impl.h
@@ -50,6 +50,11 @@ public:
std::atomic<bool> polling = false;
Common::SPSCQueue<SDL_Event> event_queue;
+ std::vector<Common::ParamPackage> GetInputDevices() override;
+
+ ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
+ AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
+
private:
void InitJoystick(int joystick_index);
void CloseJoystick(SDL_Joystick* sdl_joystick);
@@ -57,6 +62,9 @@ private:
/// Needs to be called before SDL_QuitSubSystem.
void CloseJoysticks();
+ // Set to true if SDL supports game controller subsystem
+ bool has_gamecontroller = false;
+
/// Map of GUID of a list of corresponding virtual Joysticks
std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
std::mutex joystick_map_mutex;
diff --git a/src/input_common/settings.cpp b/src/input_common/settings.cpp
new file mode 100644
index 000000000..80c719cf4
--- /dev/null
+++ b/src/input_common/settings.cpp
@@ -0,0 +1,33 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "input_common/settings.h"
+
+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",
+}};
+}
+
+namespace NativeAnalog {
+const std::array<const char*, NumAnalogs> mapping = {{
+ "lstick",
+ "rstick",
+}};
+}
+
+namespace NativeMouseButton {
+const std::array<const char*, NumMouseButtons> mapping = {{
+ "left",
+ "right",
+ "middle",
+ "forward",
+ "back",
+}};
+}
+} // namespace Settings
diff --git a/src/input_common/settings.h b/src/input_common/settings.h
new file mode 100644
index 000000000..2d258960b
--- /dev/null
+++ b/src/input_common/settings.h
@@ -0,0 +1,335 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <string>
+#include "common/common_types.h"
+
+namespace Settings {
+namespace NativeButton {
+enum Values : int {
+ A,
+ B,
+ X,
+ Y,
+ LStick,
+ RStick,
+ L,
+ R,
+ ZL,
+ ZR,
+ Plus,
+ Minus,
+
+ DLeft,
+ DUp,
+ DRight,
+ DDown,
+
+ SL,
+ SR,
+
+ Home,
+ Screenshot,
+
+ NumButtons,
+};
+
+constexpr int BUTTON_HID_BEGIN = A;
+constexpr int BUTTON_NS_BEGIN = Home;
+
+constexpr int BUTTON_HID_END = BUTTON_NS_BEGIN;
+constexpr int BUTTON_NS_END = NumButtons;
+
+constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN;
+constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN;
+
+extern const std::array<const char*, NumButtons> mapping;
+
+} // namespace NativeButton
+
+namespace NativeAnalog {
+enum Values : int {
+ LStick,
+ RStick,
+
+ NumAnalogs,
+};
+
+constexpr int STICK_HID_BEGIN = LStick;
+constexpr int STICK_HID_END = NumAnalogs;
+constexpr int NUM_STICKS_HID = NumAnalogs;
+
+extern const std::array<const char*, NumAnalogs> mapping;
+} // namespace NativeAnalog
+
+namespace NativeMouseButton {
+enum Values {
+ Left,
+ Right,
+ Middle,
+ Forward,
+ Back,
+
+ NumMouseButtons,
+};
+
+constexpr int MOUSE_HID_BEGIN = Left;
+constexpr int MOUSE_HID_END = NumMouseButtons;
+constexpr int NUM_MOUSE_HID = NumMouseButtons;
+
+extern const std::array<const char*, NumMouseButtons> mapping;
+} // namespace NativeMouseButton
+
+namespace NativeKeyboard {
+enum Keys {
+ None,
+ Error,
+
+ A = 4,
+ B,
+ C,
+ D,
+ E,
+ F,
+ G,
+ H,
+ I,
+ J,
+ K,
+ L,
+ M,
+ N,
+ O,
+ P,
+ Q,
+ R,
+ S,
+ T,
+ U,
+ V,
+ W,
+ X,
+ Y,
+ Z,
+ N1,
+ N2,
+ N3,
+ N4,
+ N5,
+ N6,
+ N7,
+ N8,
+ N9,
+ N0,
+ Enter,
+ Escape,
+ Backspace,
+ Tab,
+ Space,
+ Minus,
+ Equal,
+ LeftBrace,
+ RightBrace,
+ Backslash,
+ Tilde,
+ Semicolon,
+ Apostrophe,
+ Grave,
+ Comma,
+ Dot,
+ Slash,
+ CapsLockKey,
+
+ F1,
+ F2,
+ F3,
+ F4,
+ F5,
+ F6,
+ F7,
+ F8,
+ F9,
+ F10,
+ F11,
+ F12,
+
+ SystemRequest,
+ ScrollLockKey,
+ Pause,
+ Insert,
+ Home,
+ PageUp,
+ Delete,
+ End,
+ PageDown,
+ Right,
+ Left,
+ Down,
+ Up,
+
+ NumLockKey,
+ KPSlash,
+ KPAsterisk,
+ KPMinus,
+ KPPlus,
+ KPEnter,
+ KP1,
+ KP2,
+ KP3,
+ KP4,
+ KP5,
+ KP6,
+ KP7,
+ KP8,
+ KP9,
+ KP0,
+ KPDot,
+
+ Key102,
+ Compose,
+ Power,
+ KPEqual,
+
+ F13,
+ F14,
+ F15,
+ F16,
+ F17,
+ F18,
+ F19,
+ F20,
+ F21,
+ F22,
+ F23,
+ F24,
+
+ Open,
+ Help,
+ Properties,
+ Front,
+ Stop,
+ Repeat,
+ Undo,
+ Cut,
+ Copy,
+ Paste,
+ Find,
+ Mute,
+ VolumeUp,
+ VolumeDown,
+ CapsLockActive,
+ NumLockActive,
+ ScrollLockActive,
+ KPComma,
+
+ KPLeftParenthesis,
+ KPRightParenthesis,
+
+ LeftControlKey = 0xE0,
+ LeftShiftKey,
+ LeftAltKey,
+ LeftMetaKey,
+ RightControlKey,
+ RightShiftKey,
+ RightAltKey,
+ RightMetaKey,
+
+ MediaPlayPause,
+ MediaStopCD,
+ MediaPrevious,
+ MediaNext,
+ MediaEject,
+ MediaVolumeUp,
+ MediaVolumeDown,
+ MediaMute,
+ MediaWebsite,
+ MediaBack,
+ MediaForward,
+ MediaStop,
+ MediaFind,
+ MediaScrollUp,
+ MediaScrollDown,
+ MediaEdit,
+ MediaSleep,
+ MediaCoffee,
+ MediaRefresh,
+ MediaCalculator,
+
+ NumKeyboardKeys,
+};
+
+static_assert(NumKeyboardKeys == 0xFC, "Incorrect number of keyboard keys.");
+
+enum Modifiers {
+ LeftControl,
+ LeftShift,
+ LeftAlt,
+ LeftMeta,
+ RightControl,
+ RightShift,
+ RightAlt,
+ RightMeta,
+ CapsLock,
+ ScrollLock,
+ NumLock,
+
+ NumKeyboardMods,
+};
+
+constexpr int KEYBOARD_KEYS_HID_BEGIN = None;
+constexpr int KEYBOARD_KEYS_HID_END = NumKeyboardKeys;
+constexpr int NUM_KEYBOARD_KEYS_HID = NumKeyboardKeys;
+
+constexpr int KEYBOARD_MODS_HID_BEGIN = LeftControl;
+constexpr int KEYBOARD_MODS_HID_END = NumKeyboardMods;
+constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
+
+} // namespace NativeKeyboard
+
+using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
+using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
+using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>;
+using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>;
+using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>;
+
+constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
+constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
+constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6;
+constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E;
+
+enum class ControllerType {
+ ProController,
+ DualJoyconDetached,
+ LeftJoycon,
+ RightJoycon,
+ Handheld,
+};
+
+struct PlayerInput {
+ bool connected;
+ ControllerType controller_type;
+ ButtonsRaw buttons;
+ AnalogsRaw analogs;
+ std::string lstick_mod;
+ std::string rstick_mod;
+
+ u32 body_color_left;
+ u32 body_color_right;
+ u32 button_color_left;
+ u32 button_color_right;
+};
+
+struct TouchscreenInput {
+ bool enabled;
+ std::string device;
+
+ u32 finger;
+ u32 diameter_x;
+ u32 diameter_y;
+ u32 rotation_angle;
+};
+} // namespace Settings
diff --git a/src/input_common/touch_from_button.cpp b/src/input_common/touch_from_button.cpp
new file mode 100644
index 000000000..98da0ef1a
--- /dev/null
+++ b/src/input_common/touch_from_button.cpp
@@ -0,0 +1,50 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/frontend/framebuffer_layout.h"
+#include "core/settings.h"
+#include "input_common/touch_from_button.h"
+
+namespace InputCommon {
+
+class TouchFromButtonDevice final : public Input::TouchDevice {
+public:
+ TouchFromButtonDevice() {
+ for (const auto& config_entry :
+ Settings::values.touch_from_button_maps[Settings::values.touch_from_button_map_index]
+ .buttons) {
+ const Common::ParamPackage package{config_entry};
+ map.emplace_back(
+ Input::CreateDevice<Input::ButtonDevice>(config_entry),
+ std::clamp(package.Get("x", 0), 0, static_cast<int>(Layout::ScreenUndocked::Width)),
+ std::clamp(package.Get("y", 0), 0,
+ static_cast<int>(Layout::ScreenUndocked::Height)));
+ }
+ }
+
+ std::tuple<float, float, bool> GetStatus() const override {
+ for (const auto& m : map) {
+ const bool state = std::get<0>(m)->GetStatus();
+ if (state) {
+ const float x = static_cast<float>(std::get<1>(m)) /
+ static_cast<int>(Layout::ScreenUndocked::Width);
+ const float y = static_cast<float>(std::get<2>(m)) /
+ static_cast<int>(Layout::ScreenUndocked::Height);
+ return {x, y, true};
+ }
+ }
+ return {};
+ }
+
+private:
+ // A vector of the mapped button, its x and its y-coordinate
+ std::vector<std::tuple<std::unique_ptr<Input::ButtonDevice>, int, int>> map;
+};
+
+std::unique_ptr<Input::TouchDevice> TouchFromButtonFactory::Create(
+ const Common::ParamPackage& params) {
+ return std::make_unique<TouchFromButtonDevice>();
+}
+
+} // namespace InputCommon
diff --git a/src/input_common/touch_from_button.h b/src/input_common/touch_from_button.h
new file mode 100644
index 000000000..8b4d1aa96
--- /dev/null
+++ b/src/input_common/touch_from_button.h
@@ -0,0 +1,23 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "core/frontend/input.h"
+
+namespace InputCommon {
+
+/**
+ * A touch device factory that takes a list of button devices and combines them into a touch device.
+ */
+class TouchFromButtonFactory final : public Input::Factory<Input::TouchDevice> {
+public:
+ /**
+ * Creates a touch device from a list of button devices
+ */
+ std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
+};
+
+} // namespace InputCommon
diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp
index e63c73c4f..3f4eaf448 100644
--- a/src/input_common/udp/client.cpp
+++ b/src/input_common/udp/client.cpp
@@ -9,7 +9,6 @@
#include <functional>
#include <thread>
#include <boost/asio.hpp>
-#include <boost/bind.hpp>
#include "common/logging/log.h"
#include "input_common/udp/client.h"
#include "input_common/udp/protocol.h"
@@ -225,8 +224,7 @@ void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 clie
} else {
failure_callback();
}
- })
- .detach();
+ }).detach();
}
CalibrationConfigurationJob::CalibrationConfigurationJob(
@@ -280,8 +278,7 @@ CalibrationConfigurationJob::CalibrationConfigurationJob(
complete_event.Wait();
socket.Stop();
worker_thread.join();
- })
- .detach();
+ }).detach();
}
CalibrationConfigurationJob::~CalibrationConfigurationJob() {
diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp
index 8c6ef1394..4b347e47e 100644
--- a/src/input_common/udp/udp.cpp
+++ b/src/input_common/udp/udp.cpp
@@ -77,10 +77,11 @@ State::State() {
std::make_unique<Client>(status, Settings::values.udp_input_address,
Settings::values.udp_input_port, Settings::values.udp_pad_index);
- Input::RegisterFactory<Input::TouchDevice>("cemuhookudp",
- std::make_shared<UDPTouchFactory>(status));
- Input::RegisterFactory<Input::MotionDevice>("cemuhookudp",
- std::make_shared<UDPMotionFactory>(status));
+ motion_factory = std::make_shared<UDPMotionFactory>(status);
+ touch_factory = std::make_shared<UDPTouchFactory>(status);
+
+ Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", motion_factory);
+ Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", touch_factory);
}
State::~State() {
@@ -88,6 +89,11 @@ State::~State() {
Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
}
+std::vector<Common::ParamPackage> State::GetInputDevices() const {
+ // TODO support binding udp devices
+ return {};
+}
+
void State::ReloadUDPClient() {
client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port,
Settings::values.udp_pad_index);
diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h
index 4f83f0441..672a5c812 100644
--- a/src/input_common/udp/udp.h
+++ b/src/input_common/udp/udp.h
@@ -5,19 +5,26 @@
#pragma once
#include <memory>
+#include <vector>
+#include "common/param_package.h"
namespace InputCommon::CemuhookUDP {
class Client;
+class UDPMotionFactory;
+class UDPTouchFactory;
class State {
public:
State();
~State();
void ReloadUDPClient();
+ std::vector<Common::ParamPackage> GetInputDevices() const;
private:
std::unique_ptr<Client> client;
+ std::shared_ptr<UDPMotionFactory> motion_factory;
+ std::shared_ptr<UDPTouchFactory> touch_factory;
};
std::unique_ptr<State> Init();
diff --git a/src/tests/core/core_timing.cpp b/src/tests/core/core_timing.cpp
index 244463a47..b35459152 100644
--- a/src/tests/core/core_timing.cpp
+++ b/src/tests/core/core_timing.cpp
@@ -25,10 +25,10 @@ std::bitset<CB_IDS.size()> callbacks_ran_flags;
u64 expected_callback = 0;
template <unsigned int IDX>
-void HostCallbackTemplate(u64 userdata, std::chrono::nanoseconds ns_late) {
+void HostCallbackTemplate(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
static_assert(IDX < CB_IDS.size(), "IDX out of range");
callbacks_ran_flags.set(IDX);
- REQUIRE(CB_IDS[IDX] == userdata);
+ REQUIRE(CB_IDS[IDX] == user_data);
REQUIRE(CB_IDS[IDX] == CB_IDS[calls_order[expected_callback]]);
delays[IDX] = ns_late.count();
++expected_callback;
@@ -46,20 +46,16 @@ struct ScopeInit final {
Core::Timing::CoreTiming core_timing;
};
-#pragma optimize("", off)
-
u64 TestTimerSpeed(Core::Timing::CoreTiming& core_timing) {
- u64 start = core_timing.GetGlobalTimeNs().count();
- u64 placebo = 0;
+ const u64 start = core_timing.GetGlobalTimeNs().count();
+ volatile u64 placebo = 0;
for (std::size_t i = 0; i < 1000; i++) {
- placebo += core_timing.GetGlobalTimeNs().count();
+ placebo = placebo + core_timing.GetGlobalTimeNs().count();
}
- u64 end = core_timing.GetGlobalTimeNs().count();
- return (end - start);
+ const u64 end = core_timing.GetGlobalTimeNs().count();
+ return end - start;
}
-#pragma optimize("", on)
-
} // Anonymous namespace
TEST_CASE("CoreTiming[BasicOrder]", "[core]") {
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 3cd896a0f..d85f1e9d1 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -1,3 +1,5 @@
+add_subdirectory(host_shaders)
+
add_library(video_core STATIC
buffer_cache/buffer_block.h
buffer_cache/buffer_cache.h
@@ -244,6 +246,9 @@ create_target_directory_groups(video_core)
target_link_libraries(video_core PUBLIC common core)
target_link_libraries(video_core PRIVATE glad xbyak)
+add_dependencies(video_core host_shaders)
+target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE})
+
if (ENABLE_VULKAN)
target_include_directories(video_core PRIVATE sirit ../../externals/Vulkan-Headers/include)
target_compile_definitions(video_core PRIVATE HAS_VULKAN)
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index b5dc68902..e7edd733f 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -51,46 +51,43 @@ public:
bool is_written = false, bool use_fast_cbuf = false) {
std::lock_guard lock{mutex};
- auto& memory_manager = system.GPU().MemoryManager();
- const std::optional<VAddr> cpu_addr_opt = memory_manager.GpuToCpuAddress(gpu_addr);
- if (!cpu_addr_opt) {
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+ if (!cpu_addr) {
return GetEmptyBuffer(size);
}
- const VAddr cpu_addr = *cpu_addr_opt;
// Cache management is a big overhead, so only cache entries with a given size.
// TODO: Figure out which size is the best for given games.
constexpr std::size_t max_stream_size = 0x800;
if (use_fast_cbuf || size < max_stream_size) {
- if (!is_written && !IsRegionWritten(cpu_addr, cpu_addr + size - 1)) {
- const bool is_granular = memory_manager.IsGranularRange(gpu_addr, size);
+ if (!is_written && !IsRegionWritten(*cpu_addr, *cpu_addr + size - 1)) {
+ const bool is_granular = gpu_memory.IsGranularRange(gpu_addr, size);
if (use_fast_cbuf) {
u8* dest;
if (is_granular) {
- dest = memory_manager.GetPointer(gpu_addr);
+ dest = gpu_memory.GetPointer(gpu_addr);
} else {
staging_buffer.resize(size);
dest = staging_buffer.data();
- memory_manager.ReadBlockUnsafe(gpu_addr, dest, size);
+ gpu_memory.ReadBlockUnsafe(gpu_addr, dest, size);
}
return ConstBufferUpload(dest, size);
}
if (is_granular) {
- u8* const host_ptr = memory_manager.GetPointer(gpu_addr);
+ u8* const host_ptr = gpu_memory.GetPointer(gpu_addr);
return StreamBufferUpload(size, alignment, [host_ptr, size](u8* dest) {
std::memcpy(dest, host_ptr, size);
});
} else {
- return StreamBufferUpload(
- size, alignment, [&memory_manager, gpu_addr, size](u8* dest) {
- memory_manager.ReadBlockUnsafe(gpu_addr, dest, size);
- });
+ return StreamBufferUpload(size, alignment, [this, gpu_addr, size](u8* dest) {
+ gpu_memory.ReadBlockUnsafe(gpu_addr, dest, size);
+ });
}
}
}
- Buffer* const block = GetBlock(cpu_addr, size);
- MapInterval* const map = MapAddress(block, gpu_addr, cpu_addr, size);
+ Buffer* const block = GetBlock(*cpu_addr, size);
+ MapInterval* const map = MapAddress(block, gpu_addr, *cpu_addr, size);
if (!map) {
return GetEmptyBuffer(size);
}
@@ -106,7 +103,7 @@ public:
}
}
- return BufferInfo{block->Handle(), block->Offset(cpu_addr), block->Address()};
+ return BufferInfo{block->Handle(), block->Offset(*cpu_addr), block->Address()};
}
/// Uploads from a host memory. Returns the OpenGL buffer where it's located and its offset.
@@ -262,9 +259,11 @@ public:
virtual BufferInfo GetEmptyBuffer(std::size_t size) = 0;
protected:
- explicit BufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system,
- std::unique_ptr<StreamBuffer> stream_buffer)
- : rasterizer{rasterizer}, system{system}, stream_buffer{std::move(stream_buffer)} {}
+ explicit BufferCache(VideoCore::RasterizerInterface& rasterizer_,
+ Tegra::MemoryManager& gpu_memory_, Core::Memory::Memory& cpu_memory_,
+ std::unique_ptr<StreamBuffer> stream_buffer_)
+ : rasterizer{rasterizer_}, gpu_memory{gpu_memory_}, cpu_memory{cpu_memory_},
+ stream_buffer{std::move(stream_buffer_)}, stream_buffer_handle{stream_buffer->Handle()} {}
~BufferCache() = default;
@@ -326,14 +325,13 @@ private:
MapInterval* MapAddress(Buffer* block, GPUVAddr gpu_addr, VAddr cpu_addr, std::size_t size) {
const VectorMapInterval overlaps = GetMapsInRange(cpu_addr, size);
if (overlaps.empty()) {
- auto& memory_manager = system.GPU().MemoryManager();
const VAddr cpu_addr_end = cpu_addr + size;
- if (memory_manager.IsGranularRange(gpu_addr, size)) {
- u8* host_ptr = memory_manager.GetPointer(gpu_addr);
+ if (gpu_memory.IsGranularRange(gpu_addr, size)) {
+ u8* const host_ptr = gpu_memory.GetPointer(gpu_addr);
block->Upload(block->Offset(cpu_addr), size, host_ptr);
} else {
staging_buffer.resize(size);
- memory_manager.ReadBlockUnsafe(gpu_addr, staging_buffer.data(), size);
+ gpu_memory.ReadBlockUnsafe(gpu_addr, staging_buffer.data(), size);
block->Upload(block->Offset(cpu_addr), size, staging_buffer.data());
}
return Register(MapInterval(cpu_addr, cpu_addr_end, gpu_addr));
@@ -392,7 +390,7 @@ private:
continue;
}
staging_buffer.resize(size);
- system.Memory().ReadBlockUnsafe(interval.lower(), staging_buffer.data(), size);
+ cpu_memory.ReadBlockUnsafe(interval.lower(), staging_buffer.data(), size);
block->Upload(block->Offset(interval.lower()), size, staging_buffer.data());
}
}
@@ -431,7 +429,7 @@ private:
const std::size_t size = map->end - map->start;
staging_buffer.resize(size);
block->Download(block->Offset(map->start), size, staging_buffer.data());
- system.Memory().WriteBlockUnsafe(map->start, staging_buffer.data(), size);
+ cpu_memory.WriteBlockUnsafe(map->start, staging_buffer.data(), size);
map->MarkAsModified(false, 0);
}
@@ -567,7 +565,8 @@ private:
}
VideoCore::RasterizerInterface& rasterizer;
- Core::System& system;
+ Tegra::MemoryManager& gpu_memory;
+ Core::Memory::Memory& cpu_memory;
std::unique_ptr<StreamBuffer> stream_buffer;
BufferType stream_buffer_handle;
diff --git a/src/video_core/compatible_formats.h b/src/video_core/compatible_formats.h
index d1082566d..51766349b 100644
--- a/src/video_core/compatible_formats.h
+++ b/src/video_core/compatible_formats.h
@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#pragma once
+
#include <array>
#include <bitset>
#include <cstddef>
diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp
index ff10ff40d..6e50661a3 100644
--- a/src/video_core/engines/fermi_2d.cpp
+++ b/src/video_core/engines/fermi_2d.cpp
@@ -10,7 +10,13 @@
namespace Tegra::Engines {
-Fermi2D::Fermi2D(VideoCore::RasterizerInterface& rasterizer) : rasterizer{rasterizer} {}
+Fermi2D::Fermi2D() = default;
+
+Fermi2D::~Fermi2D() = default;
+
+void Fermi2D::BindRasterizer(VideoCore::RasterizerInterface& rasterizer_) {
+ rasterizer = &rasterizer_;
+}
void Fermi2D::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
ASSERT_MSG(method < Regs::NUM_REGS,
@@ -87,7 +93,7 @@ void Fermi2D::HandleSurfaceCopy() {
copy_config.src_rect = src_rect;
copy_config.dst_rect = dst_rect;
- if (!rasterizer.AccelerateSurfaceCopy(regs.src, regs.dst, copy_config)) {
+ if (!rasterizer->AccelerateSurfaceCopy(regs.src, regs.dst, copy_config)) {
UNIMPLEMENTED();
}
}
diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h
index 8f37d053f..213abfaae 100644
--- a/src/video_core/engines/fermi_2d.h
+++ b/src/video_core/engines/fermi_2d.h
@@ -34,8 +34,11 @@ namespace Tegra::Engines {
class Fermi2D final : public EngineInterface {
public:
- explicit Fermi2D(VideoCore::RasterizerInterface& rasterizer);
- ~Fermi2D() = default;
+ explicit Fermi2D();
+ ~Fermi2D();
+
+ /// Binds a rasterizer to this engine.
+ void BindRasterizer(VideoCore::RasterizerInterface& rasterizer);
/// Write the value to the register identified by method.
void CallMethod(u32 method, u32 method_argument, bool is_last_call) override;
@@ -149,7 +152,7 @@ public:
};
private:
- VideoCore::RasterizerInterface& rasterizer;
+ VideoCore::RasterizerInterface* rasterizer;
/// Performs the copy from the source surface to the destination surface as configured in the
/// registers.
diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp
index a82b06a38..898370739 100644
--- a/src/video_core/engines/kepler_compute.cpp
+++ b/src/video_core/engines/kepler_compute.cpp
@@ -16,14 +16,15 @@
namespace Tegra::Engines {
-KeplerCompute::KeplerCompute(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- MemoryManager& memory_manager)
- : system{system}, rasterizer{rasterizer}, memory_manager{memory_manager}, upload_state{
- memory_manager,
- regs.upload} {}
+KeplerCompute::KeplerCompute(Core::System& system_, MemoryManager& memory_manager_)
+ : system{system_}, memory_manager{memory_manager_}, upload_state{memory_manager, regs.upload} {}
KeplerCompute::~KeplerCompute() = default;
+void KeplerCompute::BindRasterizer(VideoCore::RasterizerInterface& rasterizer_) {
+ rasterizer = &rasterizer_;
+}
+
void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
ASSERT_MSG(method < Regs::NUM_REGS,
"Invalid KeplerCompute register, increase the size of the Regs structure");
@@ -104,11 +105,11 @@ SamplerDescriptor KeplerCompute::AccessSampler(u32 handle) const {
}
VideoCore::GuestDriverProfile& KeplerCompute::AccessGuestDriverProfile() {
- return rasterizer.AccessGuestDriverProfile();
+ return rasterizer->AccessGuestDriverProfile();
}
const VideoCore::GuestDriverProfile& KeplerCompute::AccessGuestDriverProfile() const {
- return rasterizer.AccessGuestDriverProfile();
+ return rasterizer->AccessGuestDriverProfile();
}
void KeplerCompute::ProcessLaunch() {
@@ -119,7 +120,7 @@ void KeplerCompute::ProcessLaunch() {
const GPUVAddr code_addr = regs.code_loc.Address() + launch_description.program_start;
LOG_TRACE(HW_GPU, "Compute invocation launched at address 0x{:016x}", code_addr);
- rasterizer.DispatchCompute(code_addr);
+ rasterizer->DispatchCompute(code_addr);
}
Texture::TICEntry KeplerCompute::GetTICEntry(u32 tic_index) const {
diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h
index b7f668d88..7f2500aab 100644
--- a/src/video_core/engines/kepler_compute.h
+++ b/src/video_core/engines/kepler_compute.h
@@ -42,10 +42,12 @@ namespace Tegra::Engines {
class KeplerCompute final : public ConstBufferEngineInterface, public EngineInterface {
public:
- explicit KeplerCompute(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- MemoryManager& memory_manager);
+ explicit KeplerCompute(Core::System& system, MemoryManager& memory_manager);
~KeplerCompute();
+ /// Binds a rasterizer to this engine.
+ void BindRasterizer(VideoCore::RasterizerInterface& rasterizer);
+
static constexpr std::size_t NumConstBuffers = 8;
struct Regs {
@@ -230,11 +232,6 @@ public:
const VideoCore::GuestDriverProfile& AccessGuestDriverProfile() const override;
private:
- Core::System& system;
- VideoCore::RasterizerInterface& rasterizer;
- MemoryManager& memory_manager;
- Upload::State upload_state;
-
void ProcessLaunch();
/// Retrieves information about a specific TIC entry from the TIC buffer.
@@ -242,6 +239,11 @@ private:
/// Retrieves information about a specific TSC entry from the TSC buffer.
Texture::TSCEntry GetTSCEntry(u32 tsc_index) const;
+
+ Core::System& system;
+ MemoryManager& memory_manager;
+ VideoCore::RasterizerInterface* rasterizer = nullptr;
+ Upload::State upload_state;
};
#define ASSERT_REG_POSITION(field_name, position) \
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index c01436295..33854445f 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -22,14 +22,19 @@ using VideoCore::QueryType;
/// First register id that is actually a Macro call.
constexpr u32 MacroRegistersStart = 0xE00;
-Maxwell3D::Maxwell3D(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- MemoryManager& memory_manager)
- : system{system}, rasterizer{rasterizer}, memory_manager{memory_manager},
- macro_engine{GetMacroEngine(*this)}, upload_state{memory_manager, regs.upload} {
+Maxwell3D::Maxwell3D(Core::System& system_, MemoryManager& memory_manager_)
+ : system{system_}, memory_manager{memory_manager_}, macro_engine{GetMacroEngine(*this)},
+ upload_state{memory_manager, regs.upload} {
dirty.flags.flip();
InitializeRegisterDefaults();
}
+Maxwell3D::~Maxwell3D() = default;
+
+void Maxwell3D::BindRasterizer(VideoCore::RasterizerInterface& rasterizer_) {
+ rasterizer = &rasterizer_;
+}
+
void Maxwell3D::InitializeRegisterDefaults() {
// Initializes registers to their default values - what games expect them to be at boot. This is
// for certain registers that may not be explicitly set by games.
@@ -192,7 +197,7 @@ void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
switch (method) {
case MAXWELL3D_REG_INDEX(wait_for_idle): {
- rasterizer.WaitForIdle();
+ rasterizer->WaitForIdle();
break;
}
case MAXWELL3D_REG_INDEX(shadow_ram_control): {
@@ -402,7 +407,7 @@ void Maxwell3D::FlushMMEInlineDraw() {
const bool is_indexed = mme_draw.current_mode == MMEDrawMode::Indexed;
if (ShouldExecute()) {
- rasterizer.Draw(is_indexed, true);
+ rasterizer->Draw(is_indexed, true);
}
// TODO(bunnei): Below, we reset vertex count so that we can use these registers to determine if
@@ -465,7 +470,7 @@ void Maxwell3D::ProcessQueryGet() {
switch (regs.query.query_get.operation) {
case Regs::QueryOperation::Release:
if (regs.query.query_get.fence == 1) {
- rasterizer.SignalSemaphore(regs.query.QueryAddress(), regs.query.query_sequence);
+ rasterizer->SignalSemaphore(regs.query.QueryAddress(), regs.query.query_sequence);
} else {
StampQueryResult(regs.query.query_sequence, regs.query.query_get.short_query == 0);
}
@@ -533,7 +538,7 @@ void Maxwell3D::ProcessQueryCondition() {
void Maxwell3D::ProcessCounterReset() {
switch (regs.counter_reset) {
case Regs::CounterReset::SampleCnt:
- rasterizer.ResetCounter(QueryType::SamplesPassed);
+ rasterizer->ResetCounter(QueryType::SamplesPassed);
break;
default:
LOG_DEBUG(Render_OpenGL, "Unimplemented counter reset={}",
@@ -547,7 +552,7 @@ void Maxwell3D::ProcessSyncPoint() {
const u32 increment = regs.sync_info.increment.Value();
[[maybe_unused]] const u32 cache_flush = regs.sync_info.unknown.Value();
if (increment) {
- rasterizer.SignalSyncPoint(sync_point);
+ rasterizer->SignalSyncPoint(sync_point);
}
}
@@ -570,7 +575,7 @@ void Maxwell3D::DrawArrays() {
const bool is_indexed{regs.index_array.count && !regs.vertex_buffer.count};
if (ShouldExecute()) {
- rasterizer.Draw(is_indexed, false);
+ rasterizer->Draw(is_indexed, false);
}
// TODO(bunnei): Below, we reset vertex count so that we can use these registers to determine if
@@ -590,8 +595,8 @@ std::optional<u64> Maxwell3D::GetQueryResult() {
return 0;
case Regs::QuerySelect::SamplesPassed:
// Deferred.
- rasterizer.Query(regs.query.QueryAddress(), VideoCore::QueryType::SamplesPassed,
- system.GPU().GetTicks());
+ rasterizer->Query(regs.query.QueryAddress(), VideoCore::QueryType::SamplesPassed,
+ system.GPU().GetTicks());
return {};
default:
LOG_DEBUG(HW_GPU, "Unimplemented query select type {}",
@@ -718,7 +723,7 @@ void Maxwell3D::ProcessClearBuffers() {
regs.clear_buffers.R == regs.clear_buffers.B &&
regs.clear_buffers.R == regs.clear_buffers.A);
- rasterizer.Clear();
+ rasterizer->Clear();
}
u32 Maxwell3D::AccessConstBuffer32(ShaderType stage, u64 const_buffer, u64 offset) const {
@@ -752,11 +757,11 @@ SamplerDescriptor Maxwell3D::AccessSampler(u32 handle) const {
}
VideoCore::GuestDriverProfile& Maxwell3D::AccessGuestDriverProfile() {
- return rasterizer.AccessGuestDriverProfile();
+ return rasterizer->AccessGuestDriverProfile();
}
const VideoCore::GuestDriverProfile& Maxwell3D::AccessGuestDriverProfile() const {
- return rasterizer.AccessGuestDriverProfile();
+ return rasterizer->AccessGuestDriverProfile();
}
} // namespace Tegra::Engines
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index ef1618990..bc289c55d 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -51,9 +51,11 @@ namespace Tegra::Engines {
class Maxwell3D final : public ConstBufferEngineInterface, public EngineInterface {
public:
- explicit Maxwell3D(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- MemoryManager& memory_manager);
- ~Maxwell3D() = default;
+ explicit Maxwell3D(Core::System& system, MemoryManager& memory_manager);
+ ~Maxwell3D();
+
+ /// Binds a rasterizer to this engine.
+ void BindRasterizer(VideoCore::RasterizerInterface& rasterizer);
/// Register structure of the Maxwell3D engine.
/// TODO(Subv): This structure will need to be made bigger as more registers are discovered.
@@ -647,7 +649,7 @@ public:
GetX() + GetWidth(), // right
GetY() // bottom
};
- };
+ }
f32 GetX() const {
return std::max(0.0f, translate_x - std::fabs(scale_x));
@@ -1418,12 +1420,12 @@ public:
return execute_on;
}
- VideoCore::RasterizerInterface& GetRasterizer() {
- return rasterizer;
+ VideoCore::RasterizerInterface& Rasterizer() {
+ return *rasterizer;
}
- const VideoCore::RasterizerInterface& GetRasterizer() const {
- return rasterizer;
+ const VideoCore::RasterizerInterface& Rasterizer() const {
+ return *rasterizer;
}
/// Notify a memory write has happened.
@@ -1460,11 +1462,10 @@ private:
void InitializeRegisterDefaults();
Core::System& system;
-
- VideoCore::RasterizerInterface& rasterizer;
-
MemoryManager& memory_manager;
+ VideoCore::RasterizerInterface* rasterizer = nullptr;
+
/// Start offsets of each macro in macro_memory
std::array<u32, 0x80> macro_positions = {};
diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp
index a2d3d7823..e88290754 100644
--- a/src/video_core/engines/maxwell_dma.cpp
+++ b/src/video_core/engines/maxwell_dma.cpp
@@ -94,7 +94,8 @@ void MaxwellDMA::CopyPitchToPitch() {
}
void MaxwellDMA::CopyBlockLinearToPitch() {
- ASSERT(regs.src_params.block_size.depth == 0);
+ UNIMPLEMENTED_IF(regs.src_params.block_size.depth != 0);
+ UNIMPLEMENTED_IF(regs.src_params.layer != 0);
// Optimized path for micro copies.
const size_t dst_size = static_cast<size_t>(regs.pitch_out) * regs.line_count;
@@ -123,17 +124,12 @@ void MaxwellDMA::CopyBlockLinearToPitch() {
write_buffer.resize(dst_size);
}
- if (Settings::IsGPULevelExtreme()) {
- memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
- memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
- } else {
- memory_manager.ReadBlockUnsafe(regs.offset_in, read_buffer.data(), src_size);
- memory_manager.ReadBlockUnsafe(regs.offset_out, write_buffer.data(), dst_size);
- }
+ memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
+ memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
UnswizzleSubrect(regs.line_length_in, regs.line_count, regs.pitch_out, width, bytes_per_pixel,
- read_buffer.data() + src_layer_size * src_params.layer, write_buffer.data(),
- block_height, src_params.origin.x, src_params.origin.y);
+ block_height, src_params.origin.x, src_params.origin.y, write_buffer.data(),
+ read_buffer.data());
memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
}
@@ -198,7 +194,6 @@ void MaxwellDMA::FastCopyBlockLinearToPitch() {
if (read_buffer.size() < src_size) {
read_buffer.resize(src_size);
}
-
if (write_buffer.size() < dst_size) {
write_buffer.resize(dst_size);
}
@@ -212,8 +207,8 @@ void MaxwellDMA::FastCopyBlockLinearToPitch() {
}
UnswizzleSubrect(regs.line_length_in, regs.line_count, regs.pitch_out, regs.src_params.width,
- bytes_per_pixel, read_buffer.data(), write_buffer.data(),
- regs.src_params.block_size.height, pos_x, pos_y);
+ bytes_per_pixel, regs.src_params.block_size.height, pos_x, pos_y,
+ write_buffer.data(), read_buffer.data());
memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
}
diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h
index 8b2a6a42c..de6991ef6 100644
--- a/src/video_core/fence_manager.h
+++ b/src/video_core/fence_manager.h
@@ -5,15 +5,10 @@
#pragma once
#include <algorithm>
-#include <array>
-#include <memory>
#include <queue>
-#include "common/assert.h"
#include "common/common_types.h"
#include "core/core.h"
-#include "core/memory.h"
-#include "core/settings.h"
#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
@@ -79,8 +74,6 @@ public:
}
void WaitPendingFences() {
- auto& gpu{system.GPU()};
- auto& memory_manager{gpu.MemoryManager()};
while (!fences.empty()) {
TFence& current_fence = fences.front();
if (ShouldWait()) {
@@ -88,8 +81,8 @@ public:
}
PopAsyncFlushes();
if (current_fence->IsSemaphore()) {
- memory_manager.template Write<u32>(current_fence->GetAddress(),
- current_fence->GetPayload());
+ gpu_memory.template Write<u32>(current_fence->GetAddress(),
+ current_fence->GetPayload());
} else {
gpu.IncrementSyncPoint(current_fence->GetPayload());
}
@@ -98,13 +91,13 @@ public:
}
protected:
- FenceManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- TTextureCache& texture_cache, TTBufferCache& buffer_cache,
- TQueryCache& query_cache)
- : system{system}, rasterizer{rasterizer}, texture_cache{texture_cache},
- buffer_cache{buffer_cache}, query_cache{query_cache} {}
+ explicit FenceManager(VideoCore::RasterizerInterface& rasterizer_, Tegra::GPU& gpu_,
+ TTextureCache& texture_cache_, TTBufferCache& buffer_cache_,
+ TQueryCache& query_cache_)
+ : rasterizer{rasterizer_}, gpu{gpu_}, gpu_memory{gpu.MemoryManager()},
+ texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, query_cache{query_cache_} {}
- virtual ~FenceManager() {}
+ virtual ~FenceManager() = default;
/// Creates a Sync Point Fence Interface, does not create a backend fence if 'is_stubbed' is
/// true
@@ -118,16 +111,15 @@ protected:
/// Waits until a fence has been signalled by the host GPU.
virtual void WaitFence(TFence& fence) = 0;
- Core::System& system;
VideoCore::RasterizerInterface& rasterizer;
+ Tegra::GPU& gpu;
+ Tegra::MemoryManager& gpu_memory;
TTextureCache& texture_cache;
TTBufferCache& buffer_cache;
TQueryCache& query_cache;
private:
void TryReleasePendingFences() {
- auto& gpu{system.GPU()};
- auto& memory_manager{gpu.MemoryManager()};
while (!fences.empty()) {
TFence& current_fence = fences.front();
if (ShouldWait() && !IsFenceSignaled(current_fence)) {
@@ -135,8 +127,8 @@ private:
}
PopAsyncFlushes();
if (current_fence->IsSemaphore()) {
- memory_manager.template Write<u32>(current_fence->GetAddress(),
- current_fence->GetPayload());
+ gpu_memory.template Write<u32>(current_fence->GetAddress(),
+ current_fence->GetPayload());
} else {
gpu.IncrementSyncPoint(current_fence->GetPayload());
}
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index 8e19c3373..4bb9256e9 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -27,21 +27,28 @@ namespace Tegra {
MICROPROFILE_DEFINE(GPU_wait, "GPU", "Wait for the GPU", MP_RGB(128, 128, 192));
-GPU::GPU(Core::System& system, std::unique_ptr<VideoCore::RendererBase>&& renderer_, bool is_async)
- : system{system}, renderer{std::move(renderer_)}, is_async{is_async} {
- auto& rasterizer{renderer->Rasterizer()};
- memory_manager = std::make_unique<Tegra::MemoryManager>(system, rasterizer);
- dma_pusher = std::make_unique<Tegra::DmaPusher>(system, *this);
- maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, rasterizer, *memory_manager);
- fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer);
- kepler_compute = std::make_unique<Engines::KeplerCompute>(system, rasterizer, *memory_manager);
- maxwell_dma = std::make_unique<Engines::MaxwellDMA>(system, *memory_manager);
- kepler_memory = std::make_unique<Engines::KeplerMemory>(system, *memory_manager);
- shader_notify = std::make_unique<VideoCore::ShaderNotify>();
-}
+GPU::GPU(Core::System& system_, bool is_async_)
+ : system{system_}, memory_manager{std::make_unique<Tegra::MemoryManager>(system)},
+ dma_pusher{std::make_unique<Tegra::DmaPusher>(system, *this)},
+ maxwell_3d{std::make_unique<Engines::Maxwell3D>(system, *memory_manager)},
+ fermi_2d{std::make_unique<Engines::Fermi2D>()},
+ kepler_compute{std::make_unique<Engines::KeplerCompute>(system, *memory_manager)},
+ maxwell_dma{std::make_unique<Engines::MaxwellDMA>(system, *memory_manager)},
+ kepler_memory{std::make_unique<Engines::KeplerMemory>(system, *memory_manager)},
+ shader_notify{std::make_unique<VideoCore::ShaderNotify>()}, is_async{is_async_} {}
GPU::~GPU() = default;
+void GPU::BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer_) {
+ renderer = std::move(renderer_);
+
+ VideoCore::RasterizerInterface& rasterizer = renderer->Rasterizer();
+ memory_manager->BindRasterizer(rasterizer);
+ maxwell_3d->BindRasterizer(rasterizer);
+ fermi_2d->BindRasterizer(rasterizer);
+ kepler_compute->BindRasterizer(rasterizer);
+}
+
Engines::Maxwell3D& GPU::Maxwell3D() {
return *maxwell_3d;
}
@@ -81,7 +88,7 @@ void GPU::WaitFence(u32 syncpoint_id, u32 value) {
}
MICROPROFILE_SCOPE(GPU_wait);
std::unique_lock lock{sync_mutex};
- sync_cv.wait(lock, [=]() { return syncpoints[syncpoint_id].load() >= value; });
+ sync_cv.wait(lock, [=, this] { return syncpoints[syncpoint_id].load() >= value; });
}
void GPU::IncrementSyncPoint(const u32 syncpoint_id) {
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h
index 19a34c402..2d15d1c6f 100644
--- a/src/video_core/gpu.h
+++ b/src/video_core/gpu.h
@@ -142,11 +142,6 @@ class MemoryManager;
class GPU {
public:
- explicit GPU(Core::System& system, std::unique_ptr<VideoCore::RendererBase>&& renderer,
- bool is_async);
-
- virtual ~GPU();
-
struct MethodCall {
u32 method{};
u32 argument{};
@@ -162,6 +157,12 @@ public:
method_count(method_count) {}
};
+ explicit GPU(Core::System& system, bool is_async);
+ virtual ~GPU();
+
+ /// Binds a renderer to the GPU.
+ void BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer);
+
/// Calls a GPU method.
void CallMethod(const MethodCall& method_call);
@@ -252,7 +253,7 @@ public:
const Tegra::DmaPusher& DmaPusher() const;
struct Regs {
- static constexpr size_t NUM_REGS = 0x100;
+ static constexpr size_t NUM_REGS = 0x40;
union {
struct {
@@ -271,7 +272,7 @@ public:
u32 semaphore_trigger;
INSERT_UNION_PADDING_WORDS(0xC);
- // The puser and the puller share the reference counter, the pusher only has read
+ // The pusher and the puller share the reference counter, the pusher only has read
// access
u32 reference_count;
INSERT_UNION_PADDING_WORDS(0x5);
@@ -345,13 +346,12 @@ private:
bool ExecuteMethodOnEngine(u32 method);
protected:
- std::unique_ptr<Tegra::DmaPusher> dma_pusher;
Core::System& system;
+ std::unique_ptr<Tegra::MemoryManager> memory_manager;
+ std::unique_ptr<Tegra::DmaPusher> dma_pusher;
std::unique_ptr<VideoCore::RendererBase> renderer;
private:
- std::unique_ptr<Tegra::MemoryManager> memory_manager;
-
/// Mapping of command subchannels to their bound engine ids
std::array<EngineID, 8> bound_engines = {};
/// 3D engine
diff --git a/src/video_core/gpu_asynch.cpp b/src/video_core/gpu_asynch.cpp
index 7b855f63e..70a3d5738 100644
--- a/src/video_core/gpu_asynch.cpp
+++ b/src/video_core/gpu_asynch.cpp
@@ -10,16 +10,14 @@
namespace VideoCommon {
-GPUAsynch::GPUAsynch(Core::System& system, std::unique_ptr<VideoCore::RendererBase>&& renderer_,
- std::unique_ptr<Core::Frontend::GraphicsContext>&& context)
- : GPU(system, std::move(renderer_), true), gpu_thread{system},
- cpu_context(renderer->GetRenderWindow().CreateSharedContext()),
- gpu_context(std::move(context)) {}
+GPUAsynch::GPUAsynch(Core::System& system) : GPU{system, true}, gpu_thread{system} {}
GPUAsynch::~GPUAsynch() = default;
void GPUAsynch::Start() {
- gpu_thread.StartThread(*renderer, *gpu_context, *dma_pusher);
+ gpu_thread.StartThread(*renderer, renderer->Context(), *dma_pusher);
+ cpu_context = renderer->GetRenderWindow().CreateSharedContext();
+ cpu_context->MakeCurrent();
}
void GPUAsynch::ObtainContext() {
diff --git a/src/video_core/gpu_asynch.h b/src/video_core/gpu_asynch.h
index 15e9f1d38..f89c855a5 100644
--- a/src/video_core/gpu_asynch.h
+++ b/src/video_core/gpu_asynch.h
@@ -20,8 +20,7 @@ namespace VideoCommon {
/// Implementation of GPU interface that runs the GPU asynchronously
class GPUAsynch final : public Tegra::GPU {
public:
- explicit GPUAsynch(Core::System& system, std::unique_ptr<VideoCore::RendererBase>&& renderer,
- std::unique_ptr<Core::Frontend::GraphicsContext>&& context);
+ explicit GPUAsynch(Core::System& system);
~GPUAsynch() override;
void Start() override;
@@ -42,7 +41,6 @@ protected:
private:
GPUThread::ThreadManager gpu_thread;
std::unique_ptr<Core::Frontend::GraphicsContext> cpu_context;
- std::unique_ptr<Core::Frontend::GraphicsContext> gpu_context;
};
} // namespace VideoCommon
diff --git a/src/video_core/gpu_synch.cpp b/src/video_core/gpu_synch.cpp
index aaeb9811d..1ca47ddef 100644
--- a/src/video_core/gpu_synch.cpp
+++ b/src/video_core/gpu_synch.cpp
@@ -7,20 +7,18 @@
namespace VideoCommon {
-GPUSynch::GPUSynch(Core::System& system, std::unique_ptr<VideoCore::RendererBase>&& renderer,
- std::unique_ptr<Core::Frontend::GraphicsContext>&& context)
- : GPU(system, std::move(renderer), false), context{std::move(context)} {}
+GPUSynch::GPUSynch(Core::System& system) : GPU{system, false} {}
GPUSynch::~GPUSynch() = default;
void GPUSynch::Start() {}
void GPUSynch::ObtainContext() {
- context->MakeCurrent();
+ renderer->Context().MakeCurrent();
}
void GPUSynch::ReleaseContext() {
- context->DoneCurrent();
+ renderer->Context().DoneCurrent();
}
void GPUSynch::PushGPUEntries(Tegra::CommandList&& entries) {
diff --git a/src/video_core/gpu_synch.h b/src/video_core/gpu_synch.h
index 762c20aa5..297258cb1 100644
--- a/src/video_core/gpu_synch.h
+++ b/src/video_core/gpu_synch.h
@@ -19,8 +19,7 @@ namespace VideoCommon {
/// Implementation of GPU interface that runs the GPU synchronously
class GPUSynch final : public Tegra::GPU {
public:
- explicit GPUSynch(Core::System& system, std::unique_ptr<VideoCore::RendererBase>&& renderer,
- std::unique_ptr<Core::Frontend::GraphicsContext>&& context);
+ explicit GPUSynch(Core::System& system);
~GPUSynch() override;
void Start() override;
@@ -36,9 +35,6 @@ public:
protected:
void TriggerCpuInterrupt([[maybe_unused]] u32 syncpoint_id,
[[maybe_unused]] u32 value) const override {}
-
-private:
- std::unique_ptr<Core::Frontend::GraphicsContext> context;
};
} // namespace VideoCommon
diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt
new file mode 100644
index 000000000..aa62363a7
--- /dev/null
+++ b/src/video_core/host_shaders/CMakeLists.txt
@@ -0,0 +1,43 @@
+set(SHADER_FILES
+ opengl_present.frag
+ opengl_present.vert
+)
+
+set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include)
+set(HOST_SHADERS_INCLUDE ${SHADER_INCLUDE} PARENT_SCOPE)
+
+set(SHADER_DIR ${SHADER_INCLUDE}/video_core/host_shaders)
+add_custom_command(
+ OUTPUT
+ ${SHADER_DIR}
+ COMMAND
+ ${CMAKE_COMMAND} -E make_directory ${SHADER_DIR}
+)
+
+set(INPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/source_shader.h.in)
+set(HEADER_GENERATOR ${CMAKE_CURRENT_SOURCE_DIR}/StringShaderHeader.cmake)
+
+foreach(FILENAME IN ITEMS ${SHADER_FILES})
+ string(REPLACE "." "_" SHADER_NAME ${FILENAME})
+ set(SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME})
+ set(HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}.h)
+ add_custom_command(
+ OUTPUT
+ ${HEADER_FILE}
+ COMMAND
+ ${CMAKE_COMMAND} -P ${HEADER_GENERATOR} ${SOURCE_FILE} ${HEADER_FILE} ${INPUT_FILE}
+ MAIN_DEPENDENCY
+ ${SOURCE_FILE}
+ DEPENDS
+ ${HEADER_GENERATOR}
+ ${INPUT_FILE}
+ )
+ set(SHADER_HEADERS ${SHADER_HEADERS} ${HEADER_FILE})
+endforeach()
+
+add_custom_target(host_shaders
+ DEPENDS
+ ${SHADER_HEADERS}
+ SOURCES
+ ${SHADER_FILES}
+)
diff --git a/src/video_core/host_shaders/StringShaderHeader.cmake b/src/video_core/host_shaders/StringShaderHeader.cmake
new file mode 100644
index 000000000..368bce0ed
--- /dev/null
+++ b/src/video_core/host_shaders/StringShaderHeader.cmake
@@ -0,0 +1,11 @@
+set(SOURCE_FILE ${CMAKE_ARGV3})
+set(HEADER_FILE ${CMAKE_ARGV4})
+set(INPUT_FILE ${CMAKE_ARGV5})
+
+get_filename_component(CONTENTS_NAME ${SOURCE_FILE} NAME)
+string(REPLACE "." "_" CONTENTS_NAME ${CONTENTS_NAME})
+string(TOUPPER ${CONTENTS_NAME} CONTENTS_NAME)
+
+file(READ ${SOURCE_FILE} CONTENTS)
+
+configure_file(${INPUT_FILE} ${HEADER_FILE} @ONLY)
diff --git a/src/video_core/host_shaders/opengl_present.frag b/src/video_core/host_shaders/opengl_present.frag
new file mode 100644
index 000000000..8a4cb024b
--- /dev/null
+++ b/src/video_core/host_shaders/opengl_present.frag
@@ -0,0 +1,10 @@
+#version 430 core
+
+layout (location = 0) in vec2 frag_tex_coord;
+layout (location = 0) out vec4 color;
+
+layout (binding = 0) uniform sampler2D color_texture;
+
+void main() {
+ color = vec4(texture(color_texture, frag_tex_coord).rgb, 1.0f);
+}
diff --git a/src/video_core/host_shaders/opengl_present.vert b/src/video_core/host_shaders/opengl_present.vert
new file mode 100644
index 000000000..2235d31a4
--- /dev/null
+++ b/src/video_core/host_shaders/opengl_present.vert
@@ -0,0 +1,24 @@
+#version 430 core
+
+out gl_PerVertex {
+ vec4 gl_Position;
+};
+
+layout (location = 0) in vec2 vert_position;
+layout (location = 1) in vec2 vert_tex_coord;
+layout (location = 0) out vec2 frag_tex_coord;
+
+// This is a truncated 3x3 matrix for 2D transformations:
+// The upper-left 2x2 submatrix performs scaling/rotation/mirroring.
+// The third column performs translation.
+// The third row could be used for projection, which we don't need in 2D. It hence is assumed to
+// implicitly be [0, 0, 1]
+layout (location = 0) uniform mat3x2 modelview_matrix;
+
+void main() {
+ // Multiply input position by the rotscale part of the matrix and then manually translate by
+ // the last column. This is equivalent to using a full 3x3 matrix and expanding the vector
+ // to `vec3(vert_position.xy, 1.0)`
+ gl_Position = vec4(mat2(modelview_matrix) * vert_position + modelview_matrix[2], 0.0, 1.0);
+ frag_tex_coord = vert_tex_coord;
+}
diff --git a/src/video_core/host_shaders/source_shader.h.in b/src/video_core/host_shaders/source_shader.h.in
new file mode 100644
index 000000000..ccdb0d2a9
--- /dev/null
+++ b/src/video_core/host_shaders/source_shader.h.in
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <string_view>
+
+namespace HostShaders {
+
+constexpr std::string_view @CONTENTS_NAME@ = R"(@CONTENTS@)";
+
+} // namespace HostShaders
diff --git a/src/video_core/macro/macro_hle.cpp b/src/video_core/macro/macro_hle.cpp
index 0c9ff59a4..df00b57df 100644
--- a/src/video_core/macro/macro_hle.cpp
+++ b/src/video_core/macro/macro_hle.cpp
@@ -24,7 +24,7 @@ void HLE_771BB18C62444DA0(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&
maxwell3d.regs.index_array.first = parameters[4];
if (maxwell3d.ShouldExecute()) {
- maxwell3d.GetRasterizer().Draw(true, true);
+ maxwell3d.Rasterizer().Draw(true, true);
}
maxwell3d.regs.index_array.count = 0;
maxwell3d.mme_draw.instance_count = 0;
@@ -42,7 +42,7 @@ void HLE_0D61FC9FAAC9FCAD(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&
maxwell3d.mme_draw.instance_count = count;
if (maxwell3d.ShouldExecute()) {
- maxwell3d.GetRasterizer().Draw(false, true);
+ maxwell3d.Rasterizer().Draw(false, true);
}
maxwell3d.regs.vertex_buffer.count = 0;
maxwell3d.mme_draw.instance_count = 0;
@@ -65,7 +65,7 @@ void HLE_0217920100488FF7(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&
maxwell3d.regs.draw.topology.Assign(
static_cast<Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology>(parameters[0]));
if (maxwell3d.ShouldExecute()) {
- maxwell3d.GetRasterizer().Draw(true, true);
+ maxwell3d.Rasterizer().Draw(true, true);
}
maxwell3d.regs.reg_array[0x446] = 0x0; // vertex id base?
maxwell3d.regs.index_array.count = 0;
diff --git a/src/video_core/macro/macro_interpreter.cpp b/src/video_core/macro/macro_interpreter.cpp
index aa5256419..bd01fd1f2 100644
--- a/src/video_core/macro/macro_interpreter.cpp
+++ b/src/video_core/macro/macro_interpreter.cpp
@@ -34,7 +34,6 @@ void MacroInterpreterImpl::Execute(const std::vector<u32>& parameters, u32 metho
this->parameters = std::make_unique<u32[]>(num_parameters);
}
std::memcpy(this->parameters.get(), parameters.data(), num_parameters * sizeof(u32));
- this->num_parameters = num_parameters;
// Execute the code until we hit an exit condition.
bool keep_executing = true;
diff --git a/src/video_core/macro/macro_jit_x64.cpp b/src/video_core/macro/macro_jit_x64.cpp
index c1b9e4ad9..954b87515 100644
--- a/src/video_core/macro/macro_jit_x64.cpp
+++ b/src/video_core/macro/macro_jit_x64.cpp
@@ -14,11 +14,11 @@ MICROPROFILE_DEFINE(MacroJitCompile, "GPU", "Compile macro JIT", MP_RGB(173, 255
MICROPROFILE_DEFINE(MacroJitExecute, "GPU", "Execute macro JIT", MP_RGB(255, 255, 0));
namespace Tegra {
-static const Xbyak::Reg64 STATE = Xbyak::util::rbx;
-static const Xbyak::Reg32 RESULT = Xbyak::util::ebp;
-static const Xbyak::Reg64 PARAMETERS = Xbyak::util::r12;
-static const Xbyak::Reg32 METHOD_ADDRESS = Xbyak::util::r14d;
-static const Xbyak::Reg64 BRANCH_HOLDER = Xbyak::util::r15;
+constexpr Xbyak::Reg64 STATE = Xbyak::util::rbx;
+constexpr Xbyak::Reg32 RESULT = Xbyak::util::ebp;
+constexpr Xbyak::Reg64 PARAMETERS = Xbyak::util::r12;
+constexpr Xbyak::Reg32 METHOD_ADDRESS = Xbyak::util::r14d;
+constexpr Xbyak::Reg64 BRANCH_HOLDER = Xbyak::util::r15;
static const std::bitset<32> PERSISTENT_REGISTERS = Common::X64::BuildRegSet({
STATE,
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
index ff5505d12..16b2aaa27 100644
--- a/src/video_core/memory_manager.cpp
+++ b/src/video_core/memory_manager.cpp
@@ -4,7 +4,6 @@
#include "common/alignment.h"
#include "common/assert.h"
-#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process.h"
@@ -15,122 +14,142 @@
namespace Tegra {
-MemoryManager::MemoryManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer)
- : rasterizer{rasterizer}, system{system} {
- page_table.Resize(address_space_width, page_bits, false);
-
- // Initialize the map with a single free region covering the entire managed space.
- VirtualMemoryArea initial_vma;
- initial_vma.size = address_space_end;
- vma_map.emplace(initial_vma.base, initial_vma);
-
- UpdatePageTableForVMA(initial_vma);
-}
+MemoryManager::MemoryManager(Core::System& system_)
+ : system{system_}, page_table(page_table_size) {}
MemoryManager::~MemoryManager() = default;
-GPUVAddr MemoryManager::AllocateSpace(u64 size, u64 align) {
- const u64 aligned_size{Common::AlignUp(size, page_size)};
- const GPUVAddr gpu_addr{FindFreeRegion(address_space_base, aligned_size)};
-
- AllocateMemory(gpu_addr, 0, aligned_size);
+void MemoryManager::BindRasterizer(VideoCore::RasterizerInterface& rasterizer_) {
+ rasterizer = &rasterizer_;
+}
+GPUVAddr MemoryManager::UpdateRange(GPUVAddr gpu_addr, PageEntry page_entry, std::size_t size) {
+ u64 remaining_size{size};
+ for (u64 offset{}; offset < size; offset += page_size) {
+ if (remaining_size < page_size) {
+ SetPageEntry(gpu_addr + offset, page_entry + offset, remaining_size);
+ } else {
+ SetPageEntry(gpu_addr + offset, page_entry + offset);
+ }
+ remaining_size -= page_size;
+ }
return gpu_addr;
}
-GPUVAddr MemoryManager::AllocateSpace(GPUVAddr gpu_addr, u64 size, u64 align) {
- const u64 aligned_size{Common::AlignUp(size, page_size)};
-
- AllocateMemory(gpu_addr, 0, aligned_size);
+GPUVAddr MemoryManager::Map(VAddr cpu_addr, GPUVAddr gpu_addr, std::size_t size) {
+ return UpdateRange(gpu_addr, cpu_addr, size);
+}
- return gpu_addr;
+GPUVAddr MemoryManager::MapAllocate(VAddr cpu_addr, std::size_t size, std::size_t align) {
+ return Map(cpu_addr, *FindFreeRange(size, align), size);
}
-GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, u64 size) {
- const u64 aligned_size{Common::AlignUp(size, page_size)};
- const GPUVAddr gpu_addr{FindFreeRegion(address_space_base, aligned_size)};
+void MemoryManager::Unmap(GPUVAddr gpu_addr, std::size_t size) {
+ if (!size) {
+ return;
+ }
- MapBackingMemory(gpu_addr, system.Memory().GetPointer(cpu_addr), aligned_size, cpu_addr);
- ASSERT(
- system.CurrentProcess()->PageTable().LockForDeviceAddressSpace(cpu_addr, size).IsSuccess());
+ // Flush and invalidate through the GPU interface, to be asynchronous if possible.
+ system.GPU().FlushAndInvalidateRegion(*GpuToCpuAddress(gpu_addr), size);
- return gpu_addr;
+ UpdateRange(gpu_addr, PageEntry::State::Unmapped, size);
}
-GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size) {
- ASSERT((gpu_addr & page_mask) == 0);
+std::optional<GPUVAddr> MemoryManager::AllocateFixed(GPUVAddr gpu_addr, std::size_t size) {
+ for (u64 offset{}; offset < size; offset += page_size) {
+ if (!GetPageEntry(gpu_addr + offset).IsUnmapped()) {
+ return {};
+ }
+ }
- const u64 aligned_size{Common::AlignUp(size, page_size)};
+ return UpdateRange(gpu_addr, PageEntry::State::Allocated, size);
+}
- MapBackingMemory(gpu_addr, system.Memory().GetPointer(cpu_addr), aligned_size, cpu_addr);
- ASSERT(
- system.CurrentProcess()->PageTable().LockForDeviceAddressSpace(cpu_addr, size).IsSuccess());
- return gpu_addr;
+GPUVAddr MemoryManager::Allocate(std::size_t size, std::size_t align) {
+ return *AllocateFixed(*FindFreeRange(size, align), size);
}
-GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) {
- ASSERT((gpu_addr & page_mask) == 0);
+void MemoryManager::TryLockPage(PageEntry page_entry, std::size_t size) {
+ if (!page_entry.IsValid()) {
+ return;
+ }
- const u64 aligned_size{Common::AlignUp(size, page_size)};
- const auto cpu_addr = GpuToCpuAddress(gpu_addr);
- ASSERT(cpu_addr);
+ ASSERT(system.CurrentProcess()
+ ->PageTable()
+ .LockForDeviceAddressSpace(page_entry.ToAddress(), size)
+ .IsSuccess());
+}
- // Flush and invalidate through the GPU interface, to be asynchronous if possible.
- system.GPU().FlushAndInvalidateRegion(*cpu_addr, aligned_size);
+void MemoryManager::TryUnlockPage(PageEntry page_entry, std::size_t size) {
+ if (!page_entry.IsValid()) {
+ return;
+ }
- UnmapRange(gpu_addr, aligned_size);
ASSERT(system.CurrentProcess()
->PageTable()
- .UnlockForDeviceAddressSpace(cpu_addr.value(), size)
+ .UnlockForDeviceAddressSpace(page_entry.ToAddress(), size)
.IsSuccess());
-
- return gpu_addr;
}
-GPUVAddr MemoryManager::FindFreeRegion(GPUVAddr region_start, u64 size) const {
- // Find the first Free VMA.
- const VMAHandle vma_handle{
- std::find_if(vma_map.begin(), vma_map.end(), [region_start, size](const auto& vma) {
- if (vma.second.type != VirtualMemoryArea::Type::Unmapped) {
- return false;
- }
+PageEntry MemoryManager::GetPageEntry(GPUVAddr gpu_addr) const {
+ return page_table[PageEntryIndex(gpu_addr)];
+}
- const VAddr vma_end{vma.second.base + vma.second.size};
- return vma_end > region_start && vma_end >= region_start + size;
- })};
+void MemoryManager::SetPageEntry(GPUVAddr gpu_addr, PageEntry page_entry, std::size_t size) {
+ // TODO(bunnei): We should lock/unlock device regions. This currently causes issues due to
+ // improper tracking, but should be fixed in the future.
- if (vma_handle == vma_map.end()) {
- return {};
- }
+ //// Unlock the old page
+ // TryUnlockPage(page_table[PageEntryIndex(gpu_addr)], size);
- return std::max(region_start, vma_handle->second.base);
-}
+ //// Lock the new page
+ // TryLockPage(page_entry, size);
-bool MemoryManager::IsAddressValid(GPUVAddr addr) const {
- return (addr >> page_bits) < page_table.pointers.size();
+ page_table[PageEntryIndex(gpu_addr)] = page_entry;
}
-std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr addr) const {
- if (!IsAddressValid(addr)) {
- return {};
+std::optional<GPUVAddr> MemoryManager::FindFreeRange(std::size_t size, std::size_t align) const {
+ if (!align) {
+ align = page_size;
+ } else {
+ align = Common::AlignUp(align, page_size);
}
- const VAddr cpu_addr{page_table.backing_addr[addr >> page_bits]};
- if (cpu_addr) {
- return cpu_addr + (addr & page_mask);
+ u64 available_size{};
+ GPUVAddr gpu_addr{address_space_start};
+ while (gpu_addr + available_size < address_space_size) {
+ if (GetPageEntry(gpu_addr + available_size).IsUnmapped()) {
+ available_size += page_size;
+
+ if (available_size >= size) {
+ return gpu_addr;
+ }
+ } else {
+ gpu_addr += available_size + page_size;
+ available_size = 0;
+
+ const auto remainder{gpu_addr % align};
+ if (remainder) {
+ gpu_addr = (gpu_addr - remainder) + align;
+ }
+ }
}
return {};
}
-template <typename T>
-T MemoryManager::Read(GPUVAddr addr) const {
- if (!IsAddressValid(addr)) {
+std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) const {
+ const auto page_entry{GetPageEntry(gpu_addr)};
+ if (!page_entry.IsValid()) {
return {};
}
- const u8* page_pointer{GetPointer(addr)};
- if (page_pointer) {
+ return page_entry.ToAddress() + (gpu_addr & page_mask);
+}
+
+template <typename T>
+T MemoryManager::Read(GPUVAddr addr) const {
+ if (auto page_pointer{GetPointer(addr)}; page_pointer) {
// NOTE: Avoid adding any extra logic to this fast-path block
T value;
std::memcpy(&value, page_pointer, sizeof(T));
@@ -144,12 +163,7 @@ T MemoryManager::Read(GPUVAddr addr) const {
template <typename T>
void MemoryManager::Write(GPUVAddr addr, T data) {
- if (!IsAddressValid(addr)) {
- return;
- }
-
- u8* page_pointer{GetPointer(addr)};
- if (page_pointer) {
+ if (auto page_pointer{GetPointer(addr)}; page_pointer) {
// NOTE: Avoid adding any extra logic to this fast-path block
std::memcpy(page_pointer, &data, sizeof(T));
return;
@@ -167,66 +181,49 @@ template void MemoryManager::Write<u16>(GPUVAddr addr, u16 data);
template void MemoryManager::Write<u32>(GPUVAddr addr, u32 data);
template void MemoryManager::Write<u64>(GPUVAddr addr, u64 data);
-u8* MemoryManager::GetPointer(GPUVAddr addr) {
- if (!IsAddressValid(addr)) {
+u8* MemoryManager::GetPointer(GPUVAddr gpu_addr) {
+ if (!GetPageEntry(gpu_addr).IsValid()) {
return {};
}
- auto& memory = system.Memory();
-
- const VAddr page_addr{page_table.backing_addr[addr >> page_bits]};
-
- if (page_addr != 0) {
- return memory.GetPointer(page_addr + (addr & page_mask));
+ const auto address{GpuToCpuAddress(gpu_addr)};
+ if (!address) {
+ return {};
}
- LOG_ERROR(HW_GPU, "Unknown GetPointer @ 0x{:016X}", addr);
- return {};
+ return system.Memory().GetPointer(*address);
}
-const u8* MemoryManager::GetPointer(GPUVAddr addr) const {
- if (!IsAddressValid(addr)) {
+const u8* MemoryManager::GetPointer(GPUVAddr gpu_addr) const {
+ if (!GetPageEntry(gpu_addr).IsValid()) {
return {};
}
- const auto& memory = system.Memory();
-
- const VAddr page_addr{page_table.backing_addr[addr >> page_bits]};
-
- if (page_addr != 0) {
- return memory.GetPointer(page_addr + (addr & page_mask));
+ const auto address{GpuToCpuAddress(gpu_addr)};
+ if (!address) {
+ return {};
}
- LOG_ERROR(HW_GPU, "Unknown GetPointer @ 0x{:016X}", addr);
- return {};
-}
-
-bool MemoryManager::IsBlockContinuous(const GPUVAddr start, const std::size_t size) const {
- const std::size_t inner_size = size - 1;
- const GPUVAddr end = start + inner_size;
- const auto host_ptr_start = reinterpret_cast<std::uintptr_t>(GetPointer(start));
- const auto host_ptr_end = reinterpret_cast<std::uintptr_t>(GetPointer(end));
- const auto range = static_cast<std::size_t>(host_ptr_end - host_ptr_start);
- return range == inner_size;
+ return system.Memory().GetPointer(*address);
}
-void MemoryManager::ReadBlock(GPUVAddr gpu_src_addr, void* dest_buffer,
- const std::size_t size) const {
+void MemoryManager::ReadBlock(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size) const {
std::size_t remaining_size{size};
std::size_t page_index{gpu_src_addr >> page_bits};
std::size_t page_offset{gpu_src_addr & page_mask};
- auto& memory = system.Memory();
-
while (remaining_size > 0) {
const std::size_t copy_amount{
std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
- const VAddr src_addr{page_table.backing_addr[page_index] + page_offset};
- // Flush must happen on the rasterizer interface, such that memory is always synchronous
- // when it is read (even when in asynchronous GPU mode). Fixes Dead Cells title menu.
- rasterizer.FlushRegion(src_addr, copy_amount);
- memory.ReadBlockUnsafe(src_addr, dest_buffer, copy_amount);
+ if (const auto page_addr{GpuToCpuAddress(page_index << page_bits)}; page_addr) {
+ const auto src_addr{*page_addr + page_offset};
+
+ // Flush must happen on the rasterizer interface, such that memory is always synchronous
+ // when it is read (even when in asynchronous GPU mode). Fixes Dead Cells title menu.
+ rasterizer->FlushRegion(src_addr, copy_amount);
+ system.Memory().ReadBlockUnsafe(src_addr, dest_buffer, copy_amount);
+ }
page_index++;
page_offset = 0;
@@ -241,18 +238,17 @@ void MemoryManager::ReadBlockUnsafe(GPUVAddr gpu_src_addr, void* dest_buffer,
std::size_t page_index{gpu_src_addr >> page_bits};
std::size_t page_offset{gpu_src_addr & page_mask};
- auto& memory = system.Memory();
-
while (remaining_size > 0) {
const std::size_t copy_amount{
std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
- const u8* page_pointer = page_table.pointers[page_index];
- if (page_pointer) {
- const VAddr src_addr{page_table.backing_addr[page_index] + page_offset};
- memory.ReadBlockUnsafe(src_addr, dest_buffer, copy_amount);
+
+ if (const auto page_addr{GpuToCpuAddress(page_index << page_bits)}; page_addr) {
+ const auto src_addr{*page_addr + page_offset};
+ system.Memory().ReadBlockUnsafe(src_addr, dest_buffer, copy_amount);
} else {
std::memset(dest_buffer, 0, copy_amount);
}
+
page_index++;
page_offset = 0;
dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount;
@@ -260,23 +256,23 @@ void MemoryManager::ReadBlockUnsafe(GPUVAddr gpu_src_addr, void* dest_buffer,
}
}
-void MemoryManager::WriteBlock(GPUVAddr gpu_dest_addr, const void* src_buffer,
- const std::size_t size) {
+void MemoryManager::WriteBlock(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size) {
std::size_t remaining_size{size};
std::size_t page_index{gpu_dest_addr >> page_bits};
std::size_t page_offset{gpu_dest_addr & page_mask};
- auto& memory = system.Memory();
-
while (remaining_size > 0) {
const std::size_t copy_amount{
std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
- const VAddr dest_addr{page_table.backing_addr[page_index] + page_offset};
- // Invalidate must happen on the rasterizer interface, such that memory is always
- // synchronous when it is written (even when in asynchronous GPU mode).
- rasterizer.InvalidateRegion(dest_addr, copy_amount);
- memory.WriteBlockUnsafe(dest_addr, src_buffer, copy_amount);
+ if (const auto page_addr{GpuToCpuAddress(page_index << page_bits)}; page_addr) {
+ const auto dest_addr{*page_addr + page_offset};
+
+ // Invalidate must happen on the rasterizer interface, such that memory is always
+ // synchronous when it is written (even when in asynchronous GPU mode).
+ rasterizer->InvalidateRegion(dest_addr, copy_amount);
+ system.Memory().WriteBlockUnsafe(dest_addr, src_buffer, copy_amount);
+ }
page_index++;
page_offset = 0;
@@ -286,21 +282,20 @@ void MemoryManager::WriteBlock(GPUVAddr gpu_dest_addr, const void* src_buffer,
}
void MemoryManager::WriteBlockUnsafe(GPUVAddr gpu_dest_addr, const void* src_buffer,
- const std::size_t size) {
+ std::size_t size) {
std::size_t remaining_size{size};
std::size_t page_index{gpu_dest_addr >> page_bits};
std::size_t page_offset{gpu_dest_addr & page_mask};
- auto& memory = system.Memory();
-
while (remaining_size > 0) {
const std::size_t copy_amount{
std::min(static_cast<std::size_t>(page_size) - page_offset, remaining_size)};
- u8* page_pointer = page_table.pointers[page_index];
- if (page_pointer) {
- const VAddr dest_addr{page_table.backing_addr[page_index] + page_offset};
- memory.WriteBlockUnsafe(dest_addr, src_buffer, copy_amount);
+
+ if (const auto page_addr{GpuToCpuAddress(page_index << page_bits)}; page_addr) {
+ const auto dest_addr{*page_addr + page_offset};
+ system.Memory().WriteBlockUnsafe(dest_addr, src_buffer, copy_amount);
}
+
page_index++;
page_offset = 0;
src_buffer = static_cast<const u8*>(src_buffer) + copy_amount;
@@ -308,273 +303,26 @@ void MemoryManager::WriteBlockUnsafe(GPUVAddr gpu_dest_addr, const void* src_buf
}
}
-void MemoryManager::CopyBlock(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr,
- const std::size_t size) {
+void MemoryManager::CopyBlock(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr, std::size_t size) {
std::vector<u8> tmp_buffer(size);
ReadBlock(gpu_src_addr, tmp_buffer.data(), size);
WriteBlock(gpu_dest_addr, tmp_buffer.data(), size);
}
void MemoryManager::CopyBlockUnsafe(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr,
- const std::size_t size) {
+ std::size_t size) {
std::vector<u8> tmp_buffer(size);
ReadBlockUnsafe(gpu_src_addr, tmp_buffer.data(), size);
WriteBlockUnsafe(gpu_dest_addr, tmp_buffer.data(), size);
}
-bool MemoryManager::IsGranularRange(GPUVAddr gpu_addr, std::size_t size) {
- const VAddr addr = page_table.backing_addr[gpu_addr >> page_bits];
- const std::size_t page = (addr & Core::Memory::PAGE_MASK) + size;
- return page <= Core::Memory::PAGE_SIZE;
-}
-
-void MemoryManager::MapPages(GPUVAddr base, u64 size, u8* memory, Common::PageType type,
- VAddr backing_addr) {
- LOG_DEBUG(HW_GPU, "Mapping {} onto {:016X}-{:016X}", fmt::ptr(memory), base * page_size,
- (base + size) * page_size);
-
- const VAddr end{base + size};
- ASSERT_MSG(end <= page_table.pointers.size(), "out of range mapping at {:016X}",
- base + page_table.pointers.size());
-
- if (memory == nullptr) {
- while (base != end) {
- page_table.pointers[base] = nullptr;
- page_table.backing_addr[base] = 0;
-
- base += 1;
- }
- } else {
- while (base != end) {
- page_table.pointers[base] = memory;
- page_table.backing_addr[base] = backing_addr;
-
- base += 1;
- memory += page_size;
- backing_addr += page_size;
- }
- }
-}
-
-void MemoryManager::MapMemoryRegion(GPUVAddr base, u64 size, u8* target, VAddr backing_addr) {
- ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: {:016X}", size);
- ASSERT_MSG((base & page_mask) == 0, "non-page aligned base: {:016X}", base);
- MapPages(base / page_size, size / page_size, target, Common::PageType::Memory, backing_addr);
-}
-
-void MemoryManager::UnmapRegion(GPUVAddr base, u64 size) {
- ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: {:016X}", size);
- ASSERT_MSG((base & page_mask) == 0, "non-page aligned base: {:016X}", base);
- MapPages(base / page_size, size / page_size, nullptr, Common::PageType::Unmapped);
-}
-
-bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
- ASSERT(base + size == next.base);
- if (type != next.type) {
- return {};
- }
- if (type == VirtualMemoryArea::Type::Allocated && (offset + size != next.offset)) {
- return {};
- }
- if (type == VirtualMemoryArea::Type::Mapped && backing_memory + size != next.backing_memory) {
- return {};
- }
- return true;
-}
-
-MemoryManager::VMAHandle MemoryManager::FindVMA(GPUVAddr target) const {
- if (target >= address_space_end) {
- return vma_map.end();
- } else {
- return std::prev(vma_map.upper_bound(target));
- }
-}
-
-MemoryManager::VMAIter MemoryManager::Allocate(VMAIter vma_handle) {
- VirtualMemoryArea& vma{vma_handle->second};
-
- vma.type = VirtualMemoryArea::Type::Allocated;
- vma.backing_addr = 0;
- vma.backing_memory = {};
- UpdatePageTableForVMA(vma);
-
- return MergeAdjacent(vma_handle);
-}
-
-MemoryManager::VMAHandle MemoryManager::AllocateMemory(GPUVAddr target, std::size_t offset,
- u64 size) {
-
- // This is the appropriately sized VMA that will turn into our allocation.
- VMAIter vma_handle{CarveVMA(target, size)};
- VirtualMemoryArea& vma{vma_handle->second};
-
- ASSERT(vma.size == size);
-
- vma.offset = offset;
-
- return Allocate(vma_handle);
-}
-
-MemoryManager::VMAHandle MemoryManager::MapBackingMemory(GPUVAddr target, u8* memory, u64 size,
- VAddr backing_addr) {
- // This is the appropriately sized VMA that will turn into our allocation.
- VMAIter vma_handle{CarveVMA(target, size)};
- VirtualMemoryArea& vma{vma_handle->second};
-
- ASSERT(vma.size == size);
-
- vma.type = VirtualMemoryArea::Type::Mapped;
- vma.backing_memory = memory;
- vma.backing_addr = backing_addr;
- UpdatePageTableForVMA(vma);
-
- return MergeAdjacent(vma_handle);
-}
-
-void MemoryManager::UnmapRange(GPUVAddr target, u64 size) {
- VMAIter vma{CarveVMARange(target, size)};
- const VAddr target_end{target + size};
- const VMAIter end{vma_map.end()};
-
- // The comparison against the end of the range must be done using addresses since VMAs can be
- // merged during this process, causing invalidation of the iterators.
- while (vma != end && vma->second.base < target_end) {
- // Unmapped ranges return to allocated state and can be reused
- // This behavior is used by Super Mario Odyssey, Sonic Forces, and likely other games
- vma = std::next(Allocate(vma));
- }
-
- ASSERT(FindVMA(target)->second.size >= size);
-}
-
-MemoryManager::VMAIter MemoryManager::StripIterConstness(const VMAHandle& iter) {
- // This uses a neat C++ trick to convert a const_iterator to a regular iterator, given
- // non-const access to its container.
- return vma_map.erase(iter, iter); // Erases an empty range of elements
-}
-
-MemoryManager::VMAIter MemoryManager::CarveVMA(GPUVAddr base, u64 size) {
- ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: 0x{:016X}", size);
- ASSERT_MSG((base & page_mask) == 0, "non-page aligned base: 0x{:016X}", base);
-
- VMAIter vma_handle{StripIterConstness(FindVMA(base))};
- if (vma_handle == vma_map.end()) {
- // Target address is outside the managed range
- return {};
- }
-
- const VirtualMemoryArea& vma{vma_handle->second};
- if (vma.type == VirtualMemoryArea::Type::Mapped) {
- // Region is already allocated
- return vma_handle;
- }
-
- const VAddr start_in_vma{base - vma.base};
- const VAddr end_in_vma{start_in_vma + size};
-
- ASSERT_MSG(end_in_vma <= vma.size, "region size 0x{:016X} is less than required size 0x{:016X}",
- vma.size, end_in_vma);
-
- if (end_in_vma < vma.size) {
- // Split VMA at the end of the allocated region
- SplitVMA(vma_handle, end_in_vma);
- }
- if (start_in_vma != 0) {
- // Split VMA at the start of the allocated region
- vma_handle = SplitVMA(vma_handle, start_in_vma);
- }
-
- return vma_handle;
-}
-
-MemoryManager::VMAIter MemoryManager::CarveVMARange(GPUVAddr target, u64 size) {
- ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: 0x{:016X}", size);
- ASSERT_MSG((target & page_mask) == 0, "non-page aligned base: 0x{:016X}", target);
-
- const VAddr target_end{target + size};
- ASSERT(target_end >= target);
- ASSERT(size > 0);
-
- VMAIter begin_vma{StripIterConstness(FindVMA(target))};
- const VMAIter i_end{vma_map.lower_bound(target_end)};
- if (std::any_of(begin_vma, i_end, [](const auto& entry) {
- return entry.second.type == VirtualMemoryArea::Type::Unmapped;
- })) {
- return {};
- }
-
- if (target != begin_vma->second.base) {
- begin_vma = SplitVMA(begin_vma, target - begin_vma->second.base);
- }
-
- VMAIter end_vma{StripIterConstness(FindVMA(target_end))};
- if (end_vma != vma_map.end() && target_end != end_vma->second.base) {
- end_vma = SplitVMA(end_vma, target_end - end_vma->second.base);
- }
-
- return begin_vma;
-}
-
-MemoryManager::VMAIter MemoryManager::SplitVMA(VMAIter vma_handle, u64 offset_in_vma) {
- VirtualMemoryArea& old_vma{vma_handle->second};
- VirtualMemoryArea new_vma{old_vma}; // Make a copy of the VMA
-
- // For now, don't allow no-op VMA splits (trying to split at a boundary) because it's probably
- // a bug. This restriction might be removed later.
- ASSERT(offset_in_vma < old_vma.size);
- ASSERT(offset_in_vma > 0);
-
- old_vma.size = offset_in_vma;
- new_vma.base += offset_in_vma;
- new_vma.size -= offset_in_vma;
-
- switch (new_vma.type) {
- case VirtualMemoryArea::Type::Unmapped:
- break;
- case VirtualMemoryArea::Type::Allocated:
- new_vma.offset += offset_in_vma;
- break;
- case VirtualMemoryArea::Type::Mapped:
- new_vma.backing_memory += offset_in_vma;
- break;
- }
-
- ASSERT(old_vma.CanBeMergedWith(new_vma));
-
- return vma_map.emplace_hint(std::next(vma_handle), new_vma.base, new_vma);
-}
-
-MemoryManager::VMAIter MemoryManager::MergeAdjacent(VMAIter iter) {
- const VMAIter next_vma{std::next(iter)};
- if (next_vma != vma_map.end() && iter->second.CanBeMergedWith(next_vma->second)) {
- iter->second.size += next_vma->second.size;
- vma_map.erase(next_vma);
- }
-
- if (iter != vma_map.begin()) {
- VMAIter prev_vma{std::prev(iter)};
- if (prev_vma->second.CanBeMergedWith(iter->second)) {
- prev_vma->second.size += iter->second.size;
- vma_map.erase(iter);
- iter = prev_vma;
- }
- }
-
- return iter;
-}
-
-void MemoryManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) {
- switch (vma.type) {
- case VirtualMemoryArea::Type::Unmapped:
- UnmapRegion(vma.base, vma.size);
- break;
- case VirtualMemoryArea::Type::Allocated:
- MapMemoryRegion(vma.base, vma.size, nullptr, vma.backing_addr);
- break;
- case VirtualMemoryArea::Type::Mapped:
- MapMemoryRegion(vma.base, vma.size, vma.backing_memory, vma.backing_addr);
- break;
+bool MemoryManager::IsGranularRange(GPUVAddr gpu_addr, std::size_t size) const {
+ const auto cpu_addr{GpuToCpuAddress(gpu_addr)};
+ if (!cpu_addr) {
+ return false;
}
+ const std::size_t page{(*cpu_addr & Core::Memory::PAGE_MASK) + size};
+ return page <= Core::Memory::PAGE_SIZE;
}
} // namespace Tegra
diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h
index 87658e87a..53c8d122a 100644
--- a/src/video_core/memory_manager.h
+++ b/src/video_core/memory_manager.h
@@ -6,9 +6,9 @@
#include <map>
#include <optional>
+#include <vector>
#include "common/common_types.h"
-#include "common/page_table.h"
namespace VideoCore {
class RasterizerInterface;
@@ -20,58 +20,70 @@ class System;
namespace Tegra {
-/**
- * Represents a VMA in an address space. A VMA is a contiguous region of virtual addressing space
- * with homogeneous attributes across its extents. In this particular implementation each VMA is
- * also backed by a single host memory allocation.
- */
-struct VirtualMemoryArea {
- enum class Type : u8 {
- Unmapped,
- Allocated,
- Mapped,
+class PageEntry final {
+public:
+ enum class State : u32 {
+ Unmapped = static_cast<u32>(-1),
+ Allocated = static_cast<u32>(-2),
};
- /// Virtual base address of the region.
- GPUVAddr base{};
- /// Size of the region.
- u64 size{};
- /// Memory area mapping type.
- Type type{Type::Unmapped};
- /// CPU memory mapped address corresponding to this memory area.
- VAddr backing_addr{};
- /// Offset into the backing_memory the mapping starts from.
- std::size_t offset{};
- /// Pointer backing this VMA.
- u8* backing_memory{};
-
- /// Tests if this area can be merged to the right with `next`.
- bool CanBeMergedWith(const VirtualMemoryArea& next) const;
+ constexpr PageEntry() = default;
+ constexpr PageEntry(State state) : state{state} {}
+ constexpr PageEntry(VAddr addr) : state{static_cast<State>(addr >> ShiftBits)} {}
+
+ [[nodiscard]] constexpr bool IsUnmapped() const {
+ return state == State::Unmapped;
+ }
+
+ [[nodiscard]] constexpr bool IsAllocated() const {
+ return state == State::Allocated;
+ }
+
+ [[nodiscard]] constexpr bool IsValid() const {
+ return !IsUnmapped() && !IsAllocated();
+ }
+
+ [[nodiscard]] constexpr VAddr ToAddress() const {
+ if (!IsValid()) {
+ return {};
+ }
+
+ return static_cast<VAddr>(state) << ShiftBits;
+ }
+
+ [[nodiscard]] constexpr PageEntry operator+(u64 offset) const {
+ // If this is a reserved value, offsets do not apply
+ if (!IsValid()) {
+ return *this;
+ }
+ return PageEntry{(static_cast<VAddr>(state) << ShiftBits) + offset};
+ }
+
+private:
+ static constexpr std::size_t ShiftBits{12};
+
+ State state{State::Unmapped};
};
+static_assert(sizeof(PageEntry) == 4, "PageEntry is too large");
class MemoryManager final {
public:
- explicit MemoryManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer);
+ explicit MemoryManager(Core::System& system);
~MemoryManager();
- GPUVAddr AllocateSpace(u64 size, u64 align);
- GPUVAddr AllocateSpace(GPUVAddr addr, u64 size, u64 align);
- GPUVAddr MapBufferEx(VAddr cpu_addr, u64 size);
- GPUVAddr MapBufferEx(VAddr cpu_addr, GPUVAddr addr, u64 size);
- GPUVAddr UnmapBuffer(GPUVAddr addr, u64 size);
- std::optional<VAddr> GpuToCpuAddress(GPUVAddr addr) const;
+ /// Binds a renderer to the memory manager.
+ void BindRasterizer(VideoCore::RasterizerInterface& rasterizer);
+
+ [[nodiscard]] std::optional<VAddr> GpuToCpuAddress(GPUVAddr addr) const;
template <typename T>
- T Read(GPUVAddr addr) const;
+ [[nodiscard]] T Read(GPUVAddr addr) const;
template <typename T>
void Write(GPUVAddr addr, T data);
- u8* GetPointer(GPUVAddr addr);
- const u8* GetPointer(GPUVAddr addr) const;
-
- /// Returns true if the block is continuous in host memory, false otherwise
- bool IsBlockContinuous(GPUVAddr start, std::size_t size) const;
+ [[nodiscard]] u8* GetPointer(GPUVAddr addr);
+ [[nodiscard]] const u8* GetPointer(GPUVAddr addr) const;
/**
* ReadBlock and WriteBlock are full read and write operations over virtual
@@ -98,92 +110,43 @@ public:
void CopyBlockUnsafe(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr, std::size_t size);
/**
- * IsGranularRange checks if a gpu region can be simply read with a pointer
+ * IsGranularRange checks if a gpu region can be simply read with a pointer.
*/
- bool IsGranularRange(GPUVAddr gpu_addr, std::size_t size);
-
-private:
- using VMAMap = std::map<GPUVAddr, VirtualMemoryArea>;
- using VMAHandle = VMAMap::const_iterator;
- using VMAIter = VMAMap::iterator;
-
- bool IsAddressValid(GPUVAddr addr) const;
- void MapPages(GPUVAddr base, u64 size, u8* memory, Common::PageType type,
- VAddr backing_addr = 0);
- void MapMemoryRegion(GPUVAddr base, u64 size, u8* target, VAddr backing_addr);
- void UnmapRegion(GPUVAddr base, u64 size);
+ [[nodiscard]] bool IsGranularRange(GPUVAddr gpu_addr, std::size_t size) const;
- /// Finds the VMA in which the given address is included in, or `vma_map.end()`.
- VMAHandle FindVMA(GPUVAddr target) const;
-
- VMAHandle AllocateMemory(GPUVAddr target, std::size_t offset, u64 size);
-
- /**
- * Maps an unmanaged host memory pointer at a given address.
- *
- * @param target The guest address to start the mapping at.
- * @param memory The memory to be mapped.
- * @param size Size of the mapping in bytes.
- * @param backing_addr The base address of the range to back this mapping.
- */
- VMAHandle MapBackingMemory(GPUVAddr target, u8* memory, u64 size, VAddr backing_addr);
+ [[nodiscard]] GPUVAddr Map(VAddr cpu_addr, GPUVAddr gpu_addr, std::size_t size);
+ [[nodiscard]] GPUVAddr MapAllocate(VAddr cpu_addr, std::size_t size, std::size_t align);
+ [[nodiscard]] std::optional<GPUVAddr> AllocateFixed(GPUVAddr gpu_addr, std::size_t size);
+ [[nodiscard]] GPUVAddr Allocate(std::size_t size, std::size_t align);
+ void Unmap(GPUVAddr gpu_addr, std::size_t size);
- /// Unmaps a range of addresses, splitting VMAs as necessary.
- void UnmapRange(GPUVAddr target, u64 size);
-
- /// Converts a VMAHandle to a mutable VMAIter.
- VMAIter StripIterConstness(const VMAHandle& iter);
-
- /// Marks as the specified VMA as allocated.
- VMAIter Allocate(VMAIter vma);
-
- /**
- * Carves a VMA of a specific size at the specified address by splitting Free VMAs while doing
- * the appropriate error checking.
- */
- VMAIter CarveVMA(GPUVAddr base, u64 size);
-
- /**
- * Splits the edges of the given range of non-Free VMAs so that there is a VMA split at each
- * end of the range.
- */
- VMAIter CarveVMARange(GPUVAddr base, u64 size);
-
- /**
- * Splits a VMA in two, at the specified offset.
- * @returns the right side of the split, with the original iterator becoming the left side.
- */
- VMAIter SplitVMA(VMAIter vma, u64 offset_in_vma);
-
- /**
- * Checks for and merges the specified VMA with adjacent ones if possible.
- * @returns the merged VMA or the original if no merging was possible.
- */
- VMAIter MergeAdjacent(VMAIter vma);
+private:
+ [[nodiscard]] PageEntry GetPageEntry(GPUVAddr gpu_addr) const;
+ void SetPageEntry(GPUVAddr gpu_addr, PageEntry page_entry, std::size_t size = page_size);
+ GPUVAddr UpdateRange(GPUVAddr gpu_addr, PageEntry page_entry, std::size_t size);
+ [[nodiscard]] std::optional<GPUVAddr> FindFreeRange(std::size_t size, std::size_t align) const;
- /// Updates the pages corresponding to this VMA so they match the VMA's attributes.
- void UpdatePageTableForVMA(const VirtualMemoryArea& vma);
+ void TryLockPage(PageEntry page_entry, std::size_t size);
+ void TryUnlockPage(PageEntry page_entry, std::size_t size);
- /// Finds a free (unmapped region) of the specified size starting at the specified address.
- GPUVAddr FindFreeRegion(GPUVAddr region_start, u64 size) const;
+ [[nodiscard]] static constexpr std::size_t PageEntryIndex(GPUVAddr gpu_addr) {
+ return (gpu_addr >> page_bits) & page_table_mask;
+ }
-private:
+ static constexpr u64 address_space_size = 1ULL << 40;
+ static constexpr u64 address_space_start = 1ULL << 32;
static constexpr u64 page_bits{16};
static constexpr u64 page_size{1 << page_bits};
static constexpr u64 page_mask{page_size - 1};
+ static constexpr u64 page_table_bits{24};
+ static constexpr u64 page_table_size{1 << page_table_bits};
+ static constexpr u64 page_table_mask{page_table_size - 1};
- /// Address space in bits, according to Tegra X1 TRM
- static constexpr u32 address_space_width{40};
- /// Start address for mapping, this is fairly arbitrary but must be non-zero.
- static constexpr GPUVAddr address_space_base{0x100000};
- /// End of address space, based on address space in bits.
- static constexpr GPUVAddr address_space_end{1ULL << address_space_width};
+ Core::System& system;
- Common::PageTable page_table;
- VMAMap vma_map;
- VideoCore::RasterizerInterface& rasterizer;
+ VideoCore::RasterizerInterface* rasterizer = nullptr;
- Core::System& system;
+ std::vector<PageEntry> page_table;
};
} // namespace Tegra
diff --git a/src/video_core/query_cache.h b/src/video_core/query_cache.h
index 0d3a88765..d13a66bb6 100644
--- a/src/video_core/query_cache.h
+++ b/src/video_core/query_cache.h
@@ -95,10 +95,12 @@ template <class QueryCache, class CachedQuery, class CounterStream, class HostCo
class QueryPool>
class QueryCacheBase {
public:
- explicit QueryCacheBase(Core::System& system, VideoCore::RasterizerInterface& rasterizer)
- : system{system}, rasterizer{rasterizer}, streams{{CounterStream{
- static_cast<QueryCache&>(*this),
- VideoCore::QueryType::SamplesPassed}}} {}
+ explicit QueryCacheBase(VideoCore::RasterizerInterface& rasterizer_,
+ Tegra::Engines::Maxwell3D& maxwell3d_,
+ Tegra::MemoryManager& gpu_memory_)
+ : rasterizer{rasterizer_}, maxwell3d{maxwell3d_},
+ gpu_memory{gpu_memory_}, streams{{CounterStream{static_cast<QueryCache&>(*this),
+ VideoCore::QueryType::SamplesPassed}}} {}
void InvalidateRegion(VAddr addr, std::size_t size) {
std::unique_lock lock{mutex};
@@ -118,29 +120,27 @@ public:
*/
void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) {
std::unique_lock lock{mutex};
- auto& memory_manager = system.GPU().MemoryManager();
- const std::optional<VAddr> cpu_addr_opt = memory_manager.GpuToCpuAddress(gpu_addr);
- ASSERT(cpu_addr_opt);
- VAddr cpu_addr = *cpu_addr_opt;
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+ ASSERT(cpu_addr);
- CachedQuery* query = TryGet(cpu_addr);
+ CachedQuery* query = TryGet(*cpu_addr);
if (!query) {
- ASSERT_OR_EXECUTE(cpu_addr_opt, return;);
- const auto host_ptr = memory_manager.GetPointer(gpu_addr);
+ ASSERT_OR_EXECUTE(cpu_addr, return;);
+ u8* const host_ptr = gpu_memory.GetPointer(gpu_addr);
- query = Register(type, cpu_addr, host_ptr, timestamp.has_value());
+ query = Register(type, *cpu_addr, host_ptr, timestamp.has_value());
}
query->BindCounter(Stream(type).Current(), timestamp);
if (Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
- AsyncFlushQuery(cpu_addr);
+ AsyncFlushQuery(*cpu_addr);
}
}
/// Updates counters from GPU state. Expected to be called once per draw, clear or dispatch.
void UpdateCounters() {
std::unique_lock lock{mutex};
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
Stream(VideoCore::QueryType::SamplesPassed).Update(regs.samplecnt_enable);
}
@@ -270,8 +270,9 @@ private:
static constexpr std::uintptr_t PAGE_SIZE = 4096;
static constexpr unsigned PAGE_BITS = 12;
- Core::System& system;
VideoCore::RasterizerInterface& rasterizer;
+ Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::MemoryManager& gpu_memory;
std::recursive_mutex mutex;
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index 3cbdac8e7..b3e0919f8 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -106,11 +106,8 @@ public:
virtual void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {}
/// Initialize disk cached resources for the game being emulated
- virtual void LoadDiskResources(const std::atomic_bool& stop_loading = false,
- const DiskResourceLoadCallback& callback = {}) {}
-
- /// Initializes renderer dirty flags
- virtual void SetupDirtyFlags() {}
+ virtual void LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading,
+ const DiskResourceLoadCallback& callback) {}
/// Grant access to the Guest Driver Profile for recording/obtaining info on the guest driver.
GuestDriverProfile& AccessGuestDriverProfile() {
diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp
index dfb06e87e..a93a1732c 100644
--- a/src/video_core/renderer_base.cpp
+++ b/src/video_core/renderer_base.cpp
@@ -9,7 +9,9 @@
namespace VideoCore {
-RendererBase::RendererBase(Core::Frontend::EmuWindow& window) : render_window{window} {
+RendererBase::RendererBase(Core::Frontend::EmuWindow& window_,
+ std::unique_ptr<Core::Frontend::GraphicsContext> context_)
+ : render_window{window_}, context{std::move(context_)} {
RefreshBaseSettings();
}
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 1d85219b6..649074acd 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -15,7 +15,8 @@
namespace Core::Frontend {
class EmuWindow;
-}
+class GraphicsContext;
+} // namespace Core::Frontend
namespace VideoCore {
@@ -25,14 +26,15 @@ struct RendererSettings {
// Screenshot
std::atomic<bool> screenshot_requested{false};
- void* screenshot_bits;
+ void* screenshot_bits{};
std::function<void()> screenshot_complete_callback;
Layout::FramebufferLayout screenshot_framebuffer_layout;
};
class RendererBase : NonCopyable {
public:
- explicit RendererBase(Core::Frontend::EmuWindow& window);
+ explicit RendererBase(Core::Frontend::EmuWindow& window,
+ std::unique_ptr<Core::Frontend::GraphicsContext> context);
virtual ~RendererBase();
/// Initialize the renderer
@@ -68,6 +70,14 @@ public:
return *rasterizer;
}
+ Core::Frontend::GraphicsContext& Context() {
+ return *context;
+ }
+
+ const Core::Frontend::GraphicsContext& Context() const {
+ return *context;
+ }
+
Core::Frontend::EmuWindow& GetRenderWindow() {
return render_window;
}
@@ -94,6 +104,7 @@ public:
protected:
Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
std::unique_ptr<RasterizerInterface> rasterizer;
+ std::unique_ptr<Core::Frontend::GraphicsContext> context;
f32 m_current_fps = 0.0f; ///< Current framerate, should be set by the renderer
int m_current_frame = 0; ///< Current frame, should be set by the renderer
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index e866d8f2f..b1c4cd62f 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -59,9 +59,10 @@ void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst
static_cast<GLintptr>(dst_offset), static_cast<GLsizeiptr>(size));
}
-OGLBufferCache::OGLBufferCache(RasterizerOpenGL& rasterizer, Core::System& system,
+OGLBufferCache::OGLBufferCache(VideoCore::RasterizerInterface& rasterizer,
+ Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory,
const Device& device_, std::size_t stream_size)
- : GenericBufferCache{rasterizer, system,
+ : GenericBufferCache{rasterizer, gpu_memory, cpu_memory,
std::make_unique<OGLStreamBuffer>(device_, stream_size, true)},
device{device_} {
if (!device.HasFastBufferSubData()) {
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index 88fdc0536..f75b32e31 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -52,7 +52,8 @@ private:
using GenericBufferCache = VideoCommon::BufferCache<Buffer, GLuint, OGLStreamBuffer>;
class OGLBufferCache final : public GenericBufferCache {
public:
- explicit OGLBufferCache(RasterizerOpenGL& rasterizer, Core::System& system,
+ explicit OGLBufferCache(VideoCore::RasterizerInterface& rasterizer,
+ Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory,
const Device& device, std::size_t stream_size);
~OGLBufferCache();
diff --git a/src/video_core/renderer_opengl/gl_fence_manager.cpp b/src/video_core/renderer_opengl/gl_fence_manager.cpp
index ec5421afa..b532fdcc2 100644
--- a/src/video_core/renderer_opengl/gl_fence_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_fence_manager.cpp
@@ -4,16 +4,17 @@
#include "common/assert.h"
+#include <glad/glad.h>
+
#include "video_core/renderer_opengl/gl_buffer_cache.h"
#include "video_core/renderer_opengl/gl_fence_manager.h"
namespace OpenGL {
-GLInnerFence::GLInnerFence(u32 payload, bool is_stubbed)
- : VideoCommon::FenceBase(payload, is_stubbed), sync_object{} {}
+GLInnerFence::GLInnerFence(u32 payload, bool is_stubbed) : FenceBase(payload, is_stubbed) {}
GLInnerFence::GLInnerFence(GPUVAddr address, u32 payload, bool is_stubbed)
- : VideoCommon::FenceBase(address, payload, is_stubbed), sync_object{} {}
+ : FenceBase(address, payload, is_stubbed) {}
GLInnerFence::~GLInnerFence() = default;
@@ -44,11 +45,10 @@ void GLInnerFence::Wait() {
glClientWaitSync(sync_object.handle, 0, GL_TIMEOUT_IGNORED);
}
-FenceManagerOpenGL::FenceManagerOpenGL(Core::System& system,
- VideoCore::RasterizerInterface& rasterizer,
+FenceManagerOpenGL::FenceManagerOpenGL(VideoCore::RasterizerInterface& rasterizer, Tegra::GPU& gpu,
TextureCacheOpenGL& texture_cache,
OGLBufferCache& buffer_cache, QueryCache& query_cache)
- : GenericFenceManager(system, rasterizer, texture_cache, buffer_cache, query_cache) {}
+ : GenericFenceManager{rasterizer, gpu, texture_cache, buffer_cache, query_cache} {}
Fence FenceManagerOpenGL::CreateFence(u32 value, bool is_stubbed) {
return std::make_shared<GLInnerFence>(value, is_stubbed);
diff --git a/src/video_core/renderer_opengl/gl_fence_manager.h b/src/video_core/renderer_opengl/gl_fence_manager.h
index c917b3343..da1dcdace 100644
--- a/src/video_core/renderer_opengl/gl_fence_manager.h
+++ b/src/video_core/renderer_opengl/gl_fence_manager.h
@@ -5,7 +5,6 @@
#pragma once
#include <memory>
-#include <glad/glad.h>
#include "common/common_types.h"
#include "video_core/fence_manager.h"
@@ -38,9 +37,9 @@ using GenericFenceManager =
class FenceManagerOpenGL final : public GenericFenceManager {
public:
- FenceManagerOpenGL(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- TextureCacheOpenGL& texture_cache, OGLBufferCache& buffer_cache,
- QueryCache& query_cache);
+ explicit FenceManagerOpenGL(VideoCore::RasterizerInterface& rasterizer, Tegra::GPU& gpu,
+ TextureCacheOpenGL& texture_cache, OGLBufferCache& buffer_cache,
+ QueryCache& query_cache);
protected:
Fence CreateFence(u32 value, bool is_stubbed) override;
diff --git a/src/video_core/renderer_opengl/gl_query_cache.cpp b/src/video_core/renderer_opengl/gl_query_cache.cpp
index d7ba57aca..2bb8ec2b8 100644
--- a/src/video_core/renderer_opengl/gl_query_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_query_cache.cpp
@@ -30,12 +30,13 @@ constexpr GLenum GetTarget(VideoCore::QueryType type) {
} // Anonymous namespace
-QueryCache::QueryCache(Core::System& system, RasterizerOpenGL& gl_rasterizer)
+QueryCache::QueryCache(RasterizerOpenGL& rasterizer, Tegra::Engines::Maxwell3D& maxwell3d,
+ Tegra::MemoryManager& gpu_memory)
: VideoCommon::QueryCacheBase<
QueryCache, CachedQuery, CounterStream, HostCounter,
- std::vector<OGLQuery>>{system,
- static_cast<VideoCore::RasterizerInterface&>(gl_rasterizer)},
- gl_rasterizer{gl_rasterizer} {}
+ std::vector<OGLQuery>>{static_cast<VideoCore::RasterizerInterface&>(rasterizer),
+ maxwell3d, gpu_memory},
+ gl_rasterizer{rasterizer} {}
QueryCache::~QueryCache() = default;
diff --git a/src/video_core/renderer_opengl/gl_query_cache.h b/src/video_core/renderer_opengl/gl_query_cache.h
index d8e7052a1..dd626b66b 100644
--- a/src/video_core/renderer_opengl/gl_query_cache.h
+++ b/src/video_core/renderer_opengl/gl_query_cache.h
@@ -29,7 +29,8 @@ using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>;
class QueryCache final : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream,
HostCounter, std::vector<OGLQuery>> {
public:
- explicit QueryCache(Core::System& system, RasterizerOpenGL& rasterizer);
+ explicit QueryCache(RasterizerOpenGL& rasterizer, Tegra::Engines::Maxwell3D& maxwell3d,
+ Tegra::MemoryManager& gpu_memory);
~QueryCache();
OGLQuery AllocateQuery(VideoCore::QueryType type);
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 03e82c599..bbb2eb17c 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -153,16 +153,19 @@ void UpdateBindlessPointers(GLenum target, GLuint64EXT* pointers, std::size_t nu
} // Anonymous namespace
-RasterizerOpenGL::RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window,
- const Device& device, ScreenInfo& info,
- ProgramManager& program_manager, StateTracker& state_tracker)
- : RasterizerAccelerated{system.Memory()}, device{device}, texture_cache{system, *this, device,
- state_tracker},
- shader_cache{*this, system, emu_window, device}, query_cache{system, *this},
- buffer_cache{*this, system, device, STREAM_BUFFER_SIZE},
- fence_manager{system, *this, texture_cache, buffer_cache, query_cache}, system{system},
- screen_info{info}, program_manager{program_manager}, state_tracker{state_tracker},
- async_shaders{emu_window} {
+RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu_,
+ Core::Memory::Memory& cpu_memory, const Device& device_,
+ ScreenInfo& screen_info_, ProgramManager& program_manager_,
+ StateTracker& state_tracker_)
+ : RasterizerAccelerated{cpu_memory}, gpu(gpu_), maxwell3d(gpu.Maxwell3D()),
+ kepler_compute(gpu.KeplerCompute()), gpu_memory(gpu.MemoryManager()), device(device_),
+ screen_info(screen_info_), program_manager(program_manager_), state_tracker(state_tracker_),
+ texture_cache(*this, maxwell3d, gpu_memory, device, state_tracker),
+ shader_cache(*this, emu_window, gpu, maxwell3d, kepler_compute, gpu_memory, device),
+ query_cache(*this, maxwell3d, gpu_memory),
+ buffer_cache(*this, gpu_memory, cpu_memory, device, STREAM_BUFFER_SIZE),
+ fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache),
+ async_shaders(emu_window) {
CheckExtensions();
unified_uniform_buffer.Create();
@@ -177,20 +180,7 @@ RasterizerOpenGL::RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWind
}
if (device.UseAsynchronousShaders()) {
- // Max worker threads we should allow
- constexpr auto MAX_THREADS = 2u;
- // Amount of threads we should reserve for other parts of yuzu
- constexpr auto RESERVED_THREADS = 6u;
- // Get the amount of threads we can use(this can return zero)
- const auto cpu_thread_count =
- std::max(RESERVED_THREADS, std::thread::hardware_concurrency());
- // Deduce how many "extra" threads we have to use.
- const auto max_threads_unused = cpu_thread_count - RESERVED_THREADS;
- // Always allow at least 1 thread regardless of our settings
- const auto max_worker_count = std::max(1u, max_threads_unused);
- // Don't use more than MAX_THREADS
- const auto worker_count = std::min(max_worker_count, MAX_THREADS);
- async_shaders.AllocateWorkers(worker_count);
+ async_shaders.AllocateWorkers();
}
}
@@ -209,8 +199,7 @@ void RasterizerOpenGL::CheckExtensions() {
}
void RasterizerOpenGL::SetupVertexFormat() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::VertexFormats]) {
return;
}
@@ -230,7 +219,7 @@ void RasterizerOpenGL::SetupVertexFormat() {
}
flags[Dirty::VertexFormat0 + index] = false;
- const auto attrib = gpu.regs.vertex_attrib_format[index];
+ const auto attrib = maxwell3d.regs.vertex_attrib_format[index];
const auto gl_index = static_cast<GLuint>(index);
// Disable constant attributes.
@@ -254,8 +243,7 @@ void RasterizerOpenGL::SetupVertexFormat() {
}
void RasterizerOpenGL::SetupVertexBuffer() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::VertexBuffers]) {
return;
}
@@ -266,7 +254,7 @@ void RasterizerOpenGL::SetupVertexBuffer() {
const bool use_unified_memory = device.HasVertexBufferUnifiedMemory();
// Upload all guest vertex arrays sequentially to our buffer
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
for (std::size_t index = 0; index < NUM_SUPPORTED_VERTEX_BINDINGS; ++index) {
if (!flags[Dirty::VertexBuffer0 + index]) {
continue;
@@ -303,14 +291,13 @@ void RasterizerOpenGL::SetupVertexBuffer() {
}
void RasterizerOpenGL::SetupVertexInstances() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::VertexInstances]) {
return;
}
flags[Dirty::VertexInstances] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
for (std::size_t index = 0; index < NUM_SUPPORTED_VERTEX_ATTRIBUTES; ++index) {
if (!flags[Dirty::VertexInstance0 + index]) {
continue;
@@ -326,7 +313,7 @@ void RasterizerOpenGL::SetupVertexInstances() {
GLintptr RasterizerOpenGL::SetupIndexBuffer() {
MICROPROFILE_SCOPE(OpenGL_Index);
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
const std::size_t size = CalculateIndexBufferSize();
const auto info = buffer_cache.UploadMemory(regs.index_array.IndexStart(), size);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, info.handle);
@@ -335,15 +322,14 @@ GLintptr RasterizerOpenGL::SetupIndexBuffer() {
void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
MICROPROFILE_SCOPE(OpenGL_Shader);
- auto& gpu = system.GPU().Maxwell3D();
u32 clip_distances = 0;
for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
- const auto& shader_config = gpu.regs.shader_config[index];
+ const auto& shader_config = maxwell3d.regs.shader_config[index];
const auto program{static_cast<Maxwell::ShaderProgram>(index)};
// Skip stages that are not enabled
- if (!gpu.regs.IsShaderConfigEnabled(index)) {
+ if (!maxwell3d.regs.IsShaderConfigEnabled(index)) {
switch (program) {
case Maxwell::ShaderProgram::Geometry:
program_manager.UseGeometryShader(0);
@@ -404,11 +390,11 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
}
SyncClipEnabled(clip_distances);
- gpu.dirty.flags[Dirty::Shaders] = false;
+ maxwell3d.dirty.flags[Dirty::Shaders] = false;
}
std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
std::size_t size = 0;
for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) {
@@ -426,34 +412,27 @@ std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
}
std::size_t RasterizerOpenGL::CalculateIndexBufferSize() const {
- const auto& regs = system.GPU().Maxwell3D().regs;
-
- return static_cast<std::size_t>(regs.index_array.count) *
- static_cast<std::size_t>(regs.index_array.FormatSizeInBytes());
+ return static_cast<std::size_t>(maxwell3d.regs.index_array.count) *
+ static_cast<std::size_t>(maxwell3d.regs.index_array.FormatSizeInBytes());
}
-void RasterizerOpenGL::LoadDiskResources(const std::atomic_bool& stop_loading,
+void RasterizerOpenGL::LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
- shader_cache.LoadDiskCache(stop_loading, callback);
-}
-
-void RasterizerOpenGL::SetupDirtyFlags() {
- state_tracker.Initialize();
+ shader_cache.LoadDiskCache(title_id, stop_loading, callback);
}
void RasterizerOpenGL::ConfigureFramebuffers() {
MICROPROFILE_SCOPE(OpenGL_Framebuffer);
- auto& gpu = system.GPU().Maxwell3D();
- if (!gpu.dirty.flags[VideoCommon::Dirty::RenderTargets]) {
+ if (!maxwell3d.dirty.flags[VideoCommon::Dirty::RenderTargets]) {
return;
}
- gpu.dirty.flags[VideoCommon::Dirty::RenderTargets] = false;
+ maxwell3d.dirty.flags[VideoCommon::Dirty::RenderTargets] = false;
texture_cache.GuardRenderTargets(true);
View depth_surface = texture_cache.GetDepthBufferSurface(true);
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
UNIMPLEMENTED_IF(regs.rt_separate_frag_data == 0);
// Bind the framebuffer surfaces
@@ -485,8 +464,7 @@ void RasterizerOpenGL::ConfigureFramebuffers() {
}
void RasterizerOpenGL::ConfigureClearFramebuffer(bool using_color, bool using_depth_stencil) {
- auto& gpu = system.GPU().Maxwell3D();
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
texture_cache.GuardRenderTargets(true);
View color_surface;
@@ -536,12 +514,11 @@ void RasterizerOpenGL::ConfigureClearFramebuffer(bool using_color, bool using_de
}
void RasterizerOpenGL::Clear() {
- const auto& gpu = system.GPU().Maxwell3D();
- if (!gpu.ShouldExecute()) {
+ if (!maxwell3d.ShouldExecute()) {
return;
}
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
bool use_color{};
bool use_depth{};
bool use_stencil{};
@@ -606,7 +583,6 @@ void RasterizerOpenGL::Clear() {
void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
MICROPROFILE_SCOPE(OpenGL_Drawing);
- auto& gpu = system.GPU().Maxwell3D();
query_cache.UpdateCounters();
@@ -654,7 +630,7 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
if (invalidated) {
// When the stream buffer has been invalidated, we have to consider vertex buffers as dirty
- auto& dirty = gpu.dirty.flags;
+ auto& dirty = maxwell3d.dirty.flags;
dirty[Dirty::VertexBuffers] = true;
for (int index = Dirty::VertexBuffer0; index <= Dirty::VertexBuffer31; ++index) {
dirty[index] = true;
@@ -675,7 +651,7 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
// Setup emulation uniform buffer.
if (!device.UseAssemblyShaders()) {
MaxwellUniformData ubo;
- ubo.SetFromRegs(gpu);
+ ubo.SetFromRegs(maxwell3d);
const auto info =
buffer_cache.UploadHostMemory(&ubo, sizeof(ubo), device.GetUniformBufferAlignment());
glBindBufferRange(GL_UNIFORM_BUFFER, EmulationUniformBlockBinding, info.handle, info.offset,
@@ -684,7 +660,7 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
// Setup shaders and their used resources.
texture_cache.GuardSamplers(true);
- const GLenum primitive_mode = MaxwellToGL::PrimitiveTopology(gpu.regs.draw.topology);
+ const GLenum primitive_mode = MaxwellToGL::PrimitiveTopology(maxwell3d.regs.draw.topology);
SetupShaders(primitive_mode);
texture_cache.GuardSamplers(false);
@@ -701,14 +677,14 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
BeginTransformFeedback(primitive_mode);
- const GLuint base_instance = static_cast<GLuint>(gpu.regs.vb_base_instance);
+ const GLuint base_instance = static_cast<GLuint>(maxwell3d.regs.vb_base_instance);
const GLsizei num_instances =
- static_cast<GLsizei>(is_instanced ? gpu.mme_draw.instance_count : 1);
+ static_cast<GLsizei>(is_instanced ? maxwell3d.mme_draw.instance_count : 1);
if (is_indexed) {
- const GLint base_vertex = static_cast<GLint>(gpu.regs.vb_element_base);
- const GLsizei num_vertices = static_cast<GLsizei>(gpu.regs.index_array.count);
+ const GLint base_vertex = static_cast<GLint>(maxwell3d.regs.vb_element_base);
+ const GLsizei num_vertices = static_cast<GLsizei>(maxwell3d.regs.index_array.count);
const GLvoid* offset = reinterpret_cast<const GLvoid*>(index_buffer_offset);
- const GLenum format = MaxwellToGL::IndexFormat(gpu.regs.index_array.format);
+ const GLenum format = MaxwellToGL::IndexFormat(maxwell3d.regs.index_array.format);
if (num_instances == 1 && base_instance == 0 && base_vertex == 0) {
glDrawElements(primitive_mode, num_vertices, format, offset);
} else if (num_instances == 1 && base_instance == 0) {
@@ -727,8 +703,8 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
base_instance);
}
} else {
- const GLint base_vertex = static_cast<GLint>(gpu.regs.vertex_buffer.first);
- const GLsizei num_vertices = static_cast<GLsizei>(gpu.regs.vertex_buffer.count);
+ const GLint base_vertex = static_cast<GLint>(maxwell3d.regs.vertex_buffer.first);
+ const GLsizei num_vertices = static_cast<GLsizei>(maxwell3d.regs.vertex_buffer.count);
if (num_instances == 1 && base_instance == 0) {
glDrawArrays(primitive_mode, base_vertex, num_vertices);
} else if (base_instance == 0) {
@@ -743,7 +719,7 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
++num_queued_commands;
- system.GPU().TickWork();
+ gpu.TickWork();
}
void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) {
@@ -766,7 +742,8 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) {
buffer_cache.Unmap();
- const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
+ const auto& launch_desc = kepler_compute.launch_description;
+ program_manager.BindCompute(kernel->GetHandle());
glDispatchCompute(launch_desc.grid_dim_x, launch_desc.grid_dim_y, launch_desc.grid_dim_z);
++num_queued_commands;
}
@@ -828,17 +805,14 @@ void RasterizerOpenGL::SyncGuestHost() {
}
void RasterizerOpenGL::SignalSemaphore(GPUVAddr addr, u32 value) {
- auto& gpu{system.GPU()};
if (!gpu.IsAsync()) {
- auto& memory_manager{gpu.MemoryManager()};
- memory_manager.Write<u32>(addr, value);
+ gpu_memory.Write<u32>(addr, value);
return;
}
fence_manager.SignalSemaphore(addr, value);
}
void RasterizerOpenGL::SignalSyncPoint(u32 value) {
- auto& gpu{system.GPU()};
if (!gpu.IsAsync()) {
gpu.IncrementSyncPoint(value);
return;
@@ -847,7 +821,6 @@ void RasterizerOpenGL::SignalSyncPoint(u32 value) {
}
void RasterizerOpenGL::ReleaseFences() {
- auto& gpu{system.GPU()};
if (!gpu.IsAsync()) {
return;
}
@@ -933,7 +906,7 @@ void RasterizerOpenGL::SetupDrawConstBuffers(std::size_t stage_index, Shader* sh
GL_FRAGMENT_PROGRAM_PARAMETER_BUFFER_NV};
MICROPROFILE_SCOPE(OpenGL_UBO);
- const auto& stages = system.GPU().Maxwell3D().state.shader_stages;
+ const auto& stages = maxwell3d.state.shader_stages;
const auto& shader_stage = stages[stage_index];
const auto& entries = shader->GetEntries();
const bool use_unified = entries.use_unified_uniforms;
@@ -958,7 +931,7 @@ void RasterizerOpenGL::SetupDrawConstBuffers(std::size_t stage_index, Shader* sh
void RasterizerOpenGL::SetupComputeConstBuffers(Shader* kernel) {
MICROPROFILE_SCOPE(OpenGL_UBO);
- const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
+ const auto& launch_desc = kepler_compute.launch_description;
const auto& entries = kernel->GetEntries();
const bool use_unified = entries.use_unified_uniforms;
@@ -1031,9 +1004,7 @@ void RasterizerOpenGL::SetupDrawGlobalMemory(std::size_t stage_index, Shader* sh
GL_GEOMETRY_PROGRAM_NV, GL_FRAGMENT_PROGRAM_NV,
};
- auto& gpu{system.GPU()};
- auto& memory_manager{gpu.MemoryManager()};
- const auto& cbufs{gpu.Maxwell3D().state.shader_stages[stage_index]};
+ const auto& cbufs{maxwell3d.state.shader_stages[stage_index]};
const auto& entries{shader->GetEntries().global_memory_entries};
std::array<GLuint64EXT, 32> pointers;
@@ -1043,8 +1014,8 @@ void RasterizerOpenGL::SetupDrawGlobalMemory(std::size_t stage_index, Shader* sh
u32 binding = assembly_shaders ? 0 : device.GetBaseBindings(stage_index).shader_storage_buffer;
for (const auto& entry : entries) {
const GPUVAddr addr{cbufs.const_buffers[entry.cbuf_index].address + entry.cbuf_offset};
- const GPUVAddr gpu_addr{memory_manager.Read<u64>(addr)};
- const u32 size{memory_manager.Read<u32>(addr + 8)};
+ const GPUVAddr gpu_addr{gpu_memory.Read<u64>(addr)};
+ const u32 size{gpu_memory.Read<u32>(addr + 8)};
SetupGlobalMemory(binding, entry, gpu_addr, size, &pointers[binding]);
++binding;
}
@@ -1054,9 +1025,7 @@ void RasterizerOpenGL::SetupDrawGlobalMemory(std::size_t stage_index, Shader* sh
}
void RasterizerOpenGL::SetupComputeGlobalMemory(Shader* kernel) {
- auto& gpu{system.GPU()};
- auto& memory_manager{gpu.MemoryManager()};
- const auto& cbufs{gpu.KeplerCompute().launch_description.const_buffer_config};
+ const auto& cbufs{kepler_compute.launch_description.const_buffer_config};
const auto& entries{kernel->GetEntries().global_memory_entries};
std::array<GLuint64EXT, 32> pointers;
@@ -1065,8 +1034,8 @@ void RasterizerOpenGL::SetupComputeGlobalMemory(Shader* kernel) {
u32 binding = 0;
for (const auto& entry : entries) {
const GPUVAddr addr{cbufs[entry.cbuf_index].Address() + entry.cbuf_offset};
- const GPUVAddr gpu_addr{memory_manager.Read<u64>(addr)};
- const u32 size{memory_manager.Read<u32>(addr + 8)};
+ const GPUVAddr gpu_addr{gpu_memory.Read<u64>(addr)};
+ const u32 size{gpu_memory.Read<u32>(addr + 8)};
SetupGlobalMemory(binding, entry, gpu_addr, size, &pointers[binding]);
++binding;
}
@@ -1090,7 +1059,6 @@ void RasterizerOpenGL::SetupGlobalMemory(u32 binding, const GlobalMemoryEntry& e
void RasterizerOpenGL::SetupDrawTextures(std::size_t stage_index, Shader* shader) {
MICROPROFILE_SCOPE(OpenGL_Texture);
- const auto& maxwell3d = system.GPU().Maxwell3D();
u32 binding = device.GetBaseBindings(stage_index).sampler;
for (const auto& entry : shader->GetEntries().samplers) {
const auto shader_type = static_cast<ShaderType>(stage_index);
@@ -1103,11 +1071,10 @@ void RasterizerOpenGL::SetupDrawTextures(std::size_t stage_index, Shader* shader
void RasterizerOpenGL::SetupComputeTextures(Shader* kernel) {
MICROPROFILE_SCOPE(OpenGL_Texture);
- const auto& compute = system.GPU().KeplerCompute();
u32 binding = 0;
for (const auto& entry : kernel->GetEntries().samplers) {
for (std::size_t i = 0; i < entry.size; ++i) {
- const auto texture = GetTextureInfo(compute, entry, ShaderType::Compute, i);
+ const auto texture = GetTextureInfo(kepler_compute, entry, ShaderType::Compute, i);
SetupTexture(binding++, texture, entry);
}
}
@@ -1131,20 +1098,18 @@ void RasterizerOpenGL::SetupTexture(u32 binding, const Tegra::Texture::FullTextu
}
void RasterizerOpenGL::SetupDrawImages(std::size_t stage_index, Shader* shader) {
- const auto& maxwell3d = system.GPU().Maxwell3D();
u32 binding = device.GetBaseBindings(stage_index).image;
for (const auto& entry : shader->GetEntries().images) {
- const auto shader_type = static_cast<Tegra::Engines::ShaderType>(stage_index);
+ const auto shader_type = static_cast<ShaderType>(stage_index);
const auto tic = GetTextureInfo(maxwell3d, entry, shader_type).tic;
SetupImage(binding++, tic, entry);
}
}
void RasterizerOpenGL::SetupComputeImages(Shader* shader) {
- const auto& compute = system.GPU().KeplerCompute();
u32 binding = 0;
for (const auto& entry : shader->GetEntries().images) {
- const auto tic = GetTextureInfo(compute, entry, Tegra::Engines::ShaderType::Compute).tic;
+ const auto tic = GetTextureInfo(kepler_compute, entry, ShaderType::Compute).tic;
SetupImage(binding++, tic, entry);
}
}
@@ -1164,9 +1129,8 @@ void RasterizerOpenGL::SetupImage(u32 binding, const Tegra::Texture::TICEntry& t
}
void RasterizerOpenGL::SyncViewport() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
- const auto& regs = gpu.regs;
+ auto& flags = maxwell3d.dirty.flags;
+ const auto& regs = maxwell3d.regs;
const bool dirty_viewport = flags[Dirty::Viewports];
const bool dirty_clip_control = flags[Dirty::ClipControl];
@@ -1238,25 +1202,23 @@ void RasterizerOpenGL::SyncViewport() {
}
void RasterizerOpenGL::SyncDepthClamp() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::DepthClampEnabled]) {
return;
}
flags[Dirty::DepthClampEnabled] = false;
- oglEnable(GL_DEPTH_CLAMP, gpu.regs.view_volume_clip_control.depth_clamp_disabled == 0);
+ oglEnable(GL_DEPTH_CLAMP, maxwell3d.regs.view_volume_clip_control.depth_clamp_disabled == 0);
}
void RasterizerOpenGL::SyncClipEnabled(u32 clip_mask) {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::ClipDistances] && !flags[Dirty::Shaders]) {
return;
}
flags[Dirty::ClipDistances] = false;
- clip_mask &= gpu.regs.clip_distance_enabled;
+ clip_mask &= maxwell3d.regs.clip_distance_enabled;
if (clip_mask == last_clip_distance_mask) {
return;
}
@@ -1272,9 +1234,8 @@ void RasterizerOpenGL::SyncClipCoef() {
}
void RasterizerOpenGL::SyncCullMode() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
- const auto& regs = gpu.regs;
+ auto& flags = maxwell3d.dirty.flags;
+ const auto& regs = maxwell3d.regs;
if (flags[Dirty::CullTest]) {
flags[Dirty::CullTest] = false;
@@ -1289,26 +1250,24 @@ void RasterizerOpenGL::SyncCullMode() {
}
void RasterizerOpenGL::SyncPrimitiveRestart() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::PrimitiveRestart]) {
return;
}
flags[Dirty::PrimitiveRestart] = false;
- if (gpu.regs.primitive_restart.enabled) {
+ if (maxwell3d.regs.primitive_restart.enabled) {
glEnable(GL_PRIMITIVE_RESTART);
- glPrimitiveRestartIndex(gpu.regs.primitive_restart.index);
+ glPrimitiveRestartIndex(maxwell3d.regs.primitive_restart.index);
} else {
glDisable(GL_PRIMITIVE_RESTART);
}
}
void RasterizerOpenGL::SyncDepthTestState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
+ const auto& regs = maxwell3d.regs;
- const auto& regs = gpu.regs;
if (flags[Dirty::DepthMask]) {
flags[Dirty::DepthMask] = false;
glDepthMask(regs.depth_write_enabled ? GL_TRUE : GL_FALSE);
@@ -1326,14 +1285,13 @@ void RasterizerOpenGL::SyncDepthTestState() {
}
void RasterizerOpenGL::SyncStencilTestState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::StencilTest]) {
return;
}
flags[Dirty::StencilTest] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
oglEnable(GL_STENCIL_TEST, regs.stencil_enable);
glStencilFuncSeparate(GL_FRONT, MaxwellToGL::ComparisonOp(regs.stencil_front_func_func),
@@ -1358,25 +1316,24 @@ void RasterizerOpenGL::SyncStencilTestState() {
}
void RasterizerOpenGL::SyncRasterizeEnable() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::RasterizeEnable]) {
return;
}
flags[Dirty::RasterizeEnable] = false;
- oglEnable(GL_RASTERIZER_DISCARD, gpu.regs.rasterize_enable == 0);
+ oglEnable(GL_RASTERIZER_DISCARD, maxwell3d.regs.rasterize_enable == 0);
}
void RasterizerOpenGL::SyncPolygonModes() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::PolygonModes]) {
return;
}
flags[Dirty::PolygonModes] = false;
- if (gpu.regs.fill_rectangle) {
+ const auto& regs = maxwell3d.regs;
+ if (regs.fill_rectangle) {
if (!GLAD_GL_NV_fill_rectangle) {
LOG_ERROR(Render_OpenGL, "GL_NV_fill_rectangle used and not supported");
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
@@ -1389,27 +1346,26 @@ void RasterizerOpenGL::SyncPolygonModes() {
return;
}
- if (gpu.regs.polygon_mode_front == gpu.regs.polygon_mode_back) {
+ if (regs.polygon_mode_front == regs.polygon_mode_back) {
flags[Dirty::PolygonModeFront] = false;
flags[Dirty::PolygonModeBack] = false;
- glPolygonMode(GL_FRONT_AND_BACK, MaxwellToGL::PolygonMode(gpu.regs.polygon_mode_front));
+ glPolygonMode(GL_FRONT_AND_BACK, MaxwellToGL::PolygonMode(regs.polygon_mode_front));
return;
}
if (flags[Dirty::PolygonModeFront]) {
flags[Dirty::PolygonModeFront] = false;
- glPolygonMode(GL_FRONT, MaxwellToGL::PolygonMode(gpu.regs.polygon_mode_front));
+ glPolygonMode(GL_FRONT, MaxwellToGL::PolygonMode(regs.polygon_mode_front));
}
if (flags[Dirty::PolygonModeBack]) {
flags[Dirty::PolygonModeBack] = false;
- glPolygonMode(GL_BACK, MaxwellToGL::PolygonMode(gpu.regs.polygon_mode_back));
+ glPolygonMode(GL_BACK, MaxwellToGL::PolygonMode(regs.polygon_mode_back));
}
}
void RasterizerOpenGL::SyncColorMask() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::ColorMasks]) {
return;
}
@@ -1418,7 +1374,7 @@ void RasterizerOpenGL::SyncColorMask() {
const bool force = flags[Dirty::ColorMaskCommon];
flags[Dirty::ColorMaskCommon] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
if (regs.color_mask_common) {
if (!force && !flags[Dirty::ColorMask0]) {
return;
@@ -1443,33 +1399,30 @@ void RasterizerOpenGL::SyncColorMask() {
}
void RasterizerOpenGL::SyncMultiSampleState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::MultisampleControl]) {
return;
}
flags[Dirty::MultisampleControl] = false;
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
oglEnable(GL_SAMPLE_ALPHA_TO_COVERAGE, regs.multisample_control.alpha_to_coverage);
oglEnable(GL_SAMPLE_ALPHA_TO_ONE, regs.multisample_control.alpha_to_one);
}
void RasterizerOpenGL::SyncFragmentColorClampState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::FragmentClampColor]) {
return;
}
flags[Dirty::FragmentClampColor] = false;
- glClampColor(GL_CLAMP_FRAGMENT_COLOR, gpu.regs.frag_color_clamp ? GL_TRUE : GL_FALSE);
+ glClampColor(GL_CLAMP_FRAGMENT_COLOR, maxwell3d.regs.frag_color_clamp ? GL_TRUE : GL_FALSE);
}
void RasterizerOpenGL::SyncBlendState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
- const auto& regs = gpu.regs;
+ auto& flags = maxwell3d.dirty.flags;
+ const auto& regs = maxwell3d.regs;
if (flags[Dirty::BlendColor]) {
flags[Dirty::BlendColor] = false;
@@ -1526,14 +1479,13 @@ void RasterizerOpenGL::SyncBlendState() {
}
void RasterizerOpenGL::SyncLogicOpState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::LogicOp]) {
return;
}
flags[Dirty::LogicOp] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
if (regs.logic_op.enable) {
glEnable(GL_COLOR_LOGIC_OP);
glLogicOp(MaxwellToGL::LogicOp(regs.logic_op.operation));
@@ -1543,14 +1495,13 @@ void RasterizerOpenGL::SyncLogicOpState() {
}
void RasterizerOpenGL::SyncScissorTest() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::Scissors]) {
return;
}
flags[Dirty::Scissors] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
for (std::size_t index = 0; index < Maxwell::NumViewports; ++index) {
if (!flags[Dirty::Scissor0 + index]) {
continue;
@@ -1569,16 +1520,15 @@ void RasterizerOpenGL::SyncScissorTest() {
}
void RasterizerOpenGL::SyncPointState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::PointSize]) {
return;
}
flags[Dirty::PointSize] = false;
- oglEnable(GL_POINT_SPRITE, gpu.regs.point_sprite_enable);
+ oglEnable(GL_POINT_SPRITE, maxwell3d.regs.point_sprite_enable);
- if (gpu.regs.vp_point_size.enable) {
+ if (maxwell3d.regs.vp_point_size.enable) {
// By definition of GL_POINT_SIZE, it only matters if GL_PROGRAM_POINT_SIZE is disabled.
glEnable(GL_PROGRAM_POINT_SIZE);
return;
@@ -1586,32 +1536,30 @@ void RasterizerOpenGL::SyncPointState() {
// Limit the point size to 1 since nouveau sometimes sets a point size of 0 (and that's invalid
// in OpenGL).
- glPointSize(std::max(1.0f, gpu.regs.point_size));
+ glPointSize(std::max(1.0f, maxwell3d.regs.point_size));
glDisable(GL_PROGRAM_POINT_SIZE);
}
void RasterizerOpenGL::SyncLineState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::LineWidth]) {
return;
}
flags[Dirty::LineWidth] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
oglEnable(GL_LINE_SMOOTH, regs.line_smooth_enable);
glLineWidth(regs.line_smooth_enable ? regs.line_width_smooth : regs.line_width_aliased);
}
void RasterizerOpenGL::SyncPolygonOffset() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::PolygonOffset]) {
return;
}
flags[Dirty::PolygonOffset] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
oglEnable(GL_POLYGON_OFFSET_FILL, regs.polygon_offset_fill_enable);
oglEnable(GL_POLYGON_OFFSET_LINE, regs.polygon_offset_line_enable);
oglEnable(GL_POLYGON_OFFSET_POINT, regs.polygon_offset_point_enable);
@@ -1625,14 +1573,13 @@ void RasterizerOpenGL::SyncPolygonOffset() {
}
void RasterizerOpenGL::SyncAlphaTest() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::AlphaTest]) {
return;
}
flags[Dirty::AlphaTest] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
if (regs.alpha_test_enabled && regs.rt_control.count > 1) {
LOG_WARNING(Render_OpenGL, "Alpha testing with more than one render target is not tested");
}
@@ -1646,20 +1593,19 @@ void RasterizerOpenGL::SyncAlphaTest() {
}
void RasterizerOpenGL::SyncFramebufferSRGB() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::FramebufferSRGB]) {
return;
}
flags[Dirty::FramebufferSRGB] = false;
- oglEnable(GL_FRAMEBUFFER_SRGB, gpu.regs.framebuffer_srgb);
+ oglEnable(GL_FRAMEBUFFER_SRGB, maxwell3d.regs.framebuffer_srgb);
}
void RasterizerOpenGL::SyncTransformFeedback() {
// TODO(Rodrigo): Inject SKIP_COMPONENTS*_NV when required. An unimplemented message will signal
// when this is required.
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
static constexpr std::size_t STRIDE = 3;
std::array<GLint, 128 * STRIDE * Maxwell::NumTransformFeedbackBuffers> attribs;
@@ -1711,7 +1657,7 @@ void RasterizerOpenGL::SyncTransformFeedback() {
}
void RasterizerOpenGL::BeginTransformFeedback(GLenum primitive_mode) {
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
if (regs.tfb_enabled == 0) {
return;
}
@@ -1754,7 +1700,7 @@ void RasterizerOpenGL::BeginTransformFeedback(GLenum primitive_mode) {
}
void RasterizerOpenGL::EndTransformFeedback() {
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
if (regs.tfb_enabled == 0) {
return;
}
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index ccc6f50f6..f451404b2 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -36,8 +36,8 @@
#include "video_core/shader/async_shaders.h"
#include "video_core/textures/texture.h"
-namespace Core {
-class System;
+namespace Core::Memory {
+class Memory;
}
namespace Core::Frontend {
@@ -55,9 +55,10 @@ struct DrawParameters;
class RasterizerOpenGL : public VideoCore::RasterizerAccelerated {
public:
- explicit RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window,
- const Device& device, ScreenInfo& info,
- ProgramManager& program_manager, StateTracker& state_tracker);
+ explicit RasterizerOpenGL(Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu,
+ Core::Memory::Memory& cpu_memory, const Device& device,
+ ScreenInfo& screen_info, ProgramManager& program_manager,
+ StateTracker& state_tracker);
~RasterizerOpenGL() override;
void Draw(bool is_indexed, bool is_instanced) override;
@@ -83,9 +84,8 @@ public:
const Tegra::Engines::Fermi2D::Config& copy_config) override;
bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
u32 pixel_stride) override;
- void LoadDiskResources(const std::atomic_bool& stop_loading,
+ void LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) override;
- void SetupDirtyFlags() override;
/// Returns true when there are commands queued to the OpenGL server.
bool AnyCommandQueued() const {
@@ -237,7 +237,15 @@ private:
void SetupShaders(GLenum primitive_mode);
+ Tegra::GPU& gpu;
+ Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::Engines::KeplerCompute& kepler_compute;
+ Tegra::MemoryManager& gpu_memory;
+
const Device& device;
+ ScreenInfo& screen_info;
+ ProgramManager& program_manager;
+ StateTracker& state_tracker;
TextureCacheOpenGL texture_cache;
ShaderCacheOpenGL shader_cache;
@@ -247,10 +255,6 @@ private:
OGLBufferCache buffer_cache;
FenceManagerOpenGL fence_manager;
- Core::System& system;
- ScreenInfo& screen_info;
- ProgramManager& program_manager;
- StateTracker& state_tracker;
VideoCommon::Shader::AsyncShaders async_shaders;
static constexpr std::size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024;
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp
index a787e27d2..0ebcec427 100644
--- a/src/video_core/renderer_opengl/gl_resource_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <string_view>
#include <utility>
#include <glad/glad.h>
#include "common/common_types.h"
@@ -82,11 +83,13 @@ void OGLSampler::Release() {
handle = 0;
}
-void OGLShader::Create(const char* source, GLenum type) {
- if (handle != 0)
+void OGLShader::Create(std::string_view source, GLenum type) {
+ if (handle != 0) {
return;
- if (source == nullptr)
+ }
+ if (source.empty()) {
return;
+ }
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
handle = GLShader::LoadShader(source, type);
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h
index b05cb641c..f48398669 100644
--- a/src/video_core/renderer_opengl/gl_resource_manager.h
+++ b/src/video_core/renderer_opengl/gl_resource_manager.h
@@ -4,6 +4,7 @@
#pragma once
+#include <string_view>
#include <utility>
#include <glad/glad.h>
#include "common/common_types.h"
@@ -127,7 +128,7 @@ public:
return *this;
}
- void Create(const char* source, GLenum type);
+ void Create(std::string_view source, GLenum type);
void Release();
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index be71e1733..bd56bed0c 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -22,6 +22,7 @@
#include "video_core/memory_manager.h"
#include "video_core/renderer_opengl/gl_arb_decompiler.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"
+#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_shader_cache.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
#include "video_core/renderer_opengl/gl_shader_disk_cache.h"
@@ -238,12 +239,11 @@ std::unique_ptr<Shader> Shader::CreateStageFromMemory(
ProgramCode code_b, VideoCommon::Shader::AsyncShaders& async_shaders, VAddr cpu_addr) {
const auto shader_type = GetShaderType(program_type);
- auto& gpu = params.system.GPU();
+ auto& gpu = params.gpu;
gpu.ShaderNotify().MarkSharderBuilding();
auto registry = std::make_shared<Registry>(shader_type, gpu.Maxwell3D());
- if (!async_shaders.IsShaderAsync(params.system.GPU()) ||
- !params.device.UseAsynchronousShaders()) {
+ if (!async_shaders.IsShaderAsync(gpu) || !params.device.UseAsynchronousShaders()) {
const ShaderIR ir(code, STAGE_MAIN_OFFSET, COMPILER_SETTINGS, *registry);
// TODO(Rodrigo): Handle VertexA shaders
// std::optional<ShaderIR> ir_b;
@@ -286,11 +286,10 @@ std::unique_ptr<Shader> Shader::CreateStageFromMemory(
std::unique_ptr<Shader> Shader::CreateKernelFromMemory(const ShaderParameters& params,
ProgramCode code) {
- auto& gpu = params.system.GPU();
+ auto& gpu = params.gpu;
gpu.ShaderNotify().MarkSharderBuilding();
- auto& engine = gpu.KeplerCompute();
- auto registry = std::make_shared<Registry>(ShaderType::Compute, engine);
+ auto registry = std::make_shared<Registry>(ShaderType::Compute, params.engine);
const ShaderIR ir(code, KERNEL_MAIN_OFFSET, COMPILER_SETTINGS, *registry);
const u64 uid = params.unique_identifier;
auto program = BuildShader(params.device, ShaderType::Compute, uid, ir, *registry);
@@ -319,15 +318,20 @@ std::unique_ptr<Shader> Shader::CreateFromCache(const ShaderParameters& params,
precompiled_shader.registry, precompiled_shader.entries, precompiled_shader.program));
}
-ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system,
- Core::Frontend::EmuWindow& emu_window, const Device& device)
- : VideoCommon::ShaderCache<Shader>{rasterizer}, system{system},
- emu_window{emu_window}, device{device}, disk_cache{system} {}
+ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer,
+ Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_,
+ Tegra::Engines::Maxwell3D& maxwell3d_,
+ Tegra::Engines::KeplerCompute& kepler_compute_,
+ Tegra::MemoryManager& gpu_memory_, const Device& device_)
+ : VideoCommon::ShaderCache<Shader>{rasterizer}, emu_window{emu_window_}, gpu{gpu_},
+ gpu_memory{gpu_memory_}, maxwell3d{maxwell3d_},
+ kepler_compute{kepler_compute_}, device{device_} {}
ShaderCacheOpenGL::~ShaderCacheOpenGL() = default;
-void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
+void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
+ disk_cache.BindTitleID(title_id);
const std::optional transferable = disk_cache.LoadTransferable();
if (!transferable) {
return;
@@ -403,7 +407,7 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
}
};
- const auto num_workers{static_cast<std::size_t>(std::thread::hardware_concurrency() + 1ULL)};
+ const std::size_t num_workers{std::max(1U, std::thread::hardware_concurrency())};
const std::size_t bucket_size{transferable->size() / num_workers};
std::vector<std::unique_ptr<Core::Frontend::GraphicsContext>> contexts(num_workers);
std::vector<std::thread> threads(num_workers);
@@ -480,21 +484,19 @@ ProgramSharedPtr ShaderCacheOpenGL::GeneratePrecompiledProgram(
Shader* ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program,
VideoCommon::Shader::AsyncShaders& async_shaders) {
- if (!system.GPU().Maxwell3D().dirty.flags[Dirty::Shaders]) {
+ if (!maxwell3d.dirty.flags[Dirty::Shaders]) {
auto* last_shader = last_shaders[static_cast<std::size_t>(program)];
if (last_shader->IsBuilt()) {
return last_shader;
}
}
- auto& memory_manager{system.GPU().MemoryManager()};
- const GPUVAddr address{GetShaderAddress(system, program)};
+ const GPUVAddr address{GetShaderAddress(maxwell3d, program)};
if (device.UseAsynchronousShaders() && async_shaders.HasCompletedWork()) {
auto completed_work = async_shaders.GetCompletedWork();
for (auto& work : completed_work) {
Shader* shader = TryGet(work.cpu_address);
- auto& gpu = system.GPU();
gpu.ShaderNotify().MarkShaderComplete();
if (shader == nullptr) {
continue;
@@ -506,14 +508,13 @@ Shader* ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program,
shader->AsyncGLASMBuilt(std::move(work.program.glasm));
}
+ auto& registry = shader->GetRegistry();
+
ShaderDiskCacheEntry entry;
entry.type = work.shader_type;
entry.code = std::move(work.code);
entry.code_b = std::move(work.code_b);
entry.unique_identifier = work.uid;
-
- auto& registry = shader->GetRegistry();
-
entry.bound_buffer = registry.GetBoundBuffer();
entry.graphics_info = registry.GetGraphicsInfo();
entry.keys = registry.GetKeys();
@@ -524,28 +525,28 @@ Shader* ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program,
}
// Look up shader in the cache based on address
- const auto cpu_addr{memory_manager.GpuToCpuAddress(address)};
+ const std::optional<VAddr> cpu_addr{gpu_memory.GpuToCpuAddress(address)};
if (Shader* const shader{cpu_addr ? TryGet(*cpu_addr) : null_shader.get()}) {
return last_shaders[static_cast<std::size_t>(program)] = shader;
}
- const auto host_ptr{memory_manager.GetPointer(address)};
+ const u8* const host_ptr{gpu_memory.GetPointer(address)};
// No shader found - create a new one
- ProgramCode code{GetShaderCode(memory_manager, address, host_ptr, false)};
+ ProgramCode code{GetShaderCode(gpu_memory, address, host_ptr, false)};
ProgramCode code_b;
if (program == Maxwell::ShaderProgram::VertexA) {
- const GPUVAddr address_b{GetShaderAddress(system, Maxwell::ShaderProgram::VertexB)};
- const u8* host_ptr_b = memory_manager.GetPointer(address_b);
- code_b = GetShaderCode(memory_manager, address_b, host_ptr_b, false);
+ const GPUVAddr address_b{GetShaderAddress(maxwell3d, Maxwell::ShaderProgram::VertexB)};
+ const u8* host_ptr_b = gpu_memory.GetPointer(address_b);
+ code_b = GetShaderCode(gpu_memory, address_b, host_ptr_b, false);
}
const std::size_t code_size = code.size() * sizeof(u64);
const u64 unique_identifier = GetUniqueIdentifier(
GetShaderType(program), program == Maxwell::ShaderProgram::VertexA, code, code_b);
- const ShaderParameters params{system, disk_cache, device,
- *cpu_addr, host_ptr, unique_identifier};
+ const ShaderParameters params{gpu, maxwell3d, disk_cache, device,
+ *cpu_addr, host_ptr, unique_identifier};
std::unique_ptr<Shader> shader;
const auto found = runtime_cache.find(unique_identifier);
@@ -567,21 +568,20 @@ Shader* ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program,
}
Shader* ShaderCacheOpenGL::GetComputeKernel(GPUVAddr code_addr) {
- auto& memory_manager{system.GPU().MemoryManager()};
- const auto cpu_addr{memory_manager.GpuToCpuAddress(code_addr)};
+ const std::optional<VAddr> cpu_addr{gpu_memory.GpuToCpuAddress(code_addr)};
if (Shader* const kernel = cpu_addr ? TryGet(*cpu_addr) : null_kernel.get()) {
return kernel;
}
- const auto host_ptr{memory_manager.GetPointer(code_addr)};
// No kernel found, create a new one
- ProgramCode code{GetShaderCode(memory_manager, code_addr, host_ptr, true)};
+ const u8* host_ptr{gpu_memory.GetPointer(code_addr)};
+ ProgramCode code{GetShaderCode(gpu_memory, code_addr, host_ptr, true)};
const std::size_t code_size{code.size() * sizeof(u64)};
const u64 unique_identifier{GetUniqueIdentifier(ShaderType::Compute, false, code)};
- const ShaderParameters params{system, disk_cache, device,
- *cpu_addr, host_ptr, unique_identifier};
+ const ShaderParameters params{gpu, kepler_compute, disk_cache, device,
+ *cpu_addr, host_ptr, unique_identifier};
std::unique_ptr<Shader> kernel;
const auto found = runtime_cache.find(unique_identifier);
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h
index 7528ac686..1708af06a 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_cache.h
@@ -25,8 +25,8 @@
#include "video_core/shader/shader_ir.h"
#include "video_core/shader_cache.h"
-namespace Core {
-class System;
+namespace Tegra {
+class MemoryManager;
}
namespace Core::Frontend {
@@ -57,11 +57,12 @@ struct PrecompiledShader {
};
struct ShaderParameters {
- Core::System& system;
+ Tegra::GPU& gpu;
+ Tegra::Engines::ConstBufferEngineInterface& engine;
ShaderDiskCacheOpenGL& disk_cache;
const Device& device;
VAddr cpu_addr;
- u8* host_ptr;
+ const u8* host_ptr;
u64 unique_identifier;
};
@@ -118,12 +119,14 @@ private:
class ShaderCacheOpenGL final : public VideoCommon::ShaderCache<Shader> {
public:
- explicit ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system,
- Core::Frontend::EmuWindow& emu_window, const Device& device);
+ explicit ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::Frontend::EmuWindow& emu_window,
+ Tegra::GPU& gpu, Tegra::Engines::Maxwell3D& maxwell3d,
+ Tegra::Engines::KeplerCompute& kepler_compute,
+ Tegra::MemoryManager& gpu_memory, const Device& device);
~ShaderCacheOpenGL() override;
/// Loads disk cache for the current game
- void LoadDiskCache(const std::atomic_bool& stop_loading,
+ void LoadDiskCache(u64 title_id, const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback);
/// Gets the current specified shader stage program
@@ -138,9 +141,13 @@ private:
const ShaderDiskCacheEntry& entry, const ShaderDiskCachePrecompiled& precompiled_entry,
const std::unordered_set<GLenum>& supported_formats);
- Core::System& system;
Core::Frontend::EmuWindow& emu_window;
+ Tegra::GPU& gpu;
+ Tegra::MemoryManager& gpu_memory;
+ Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::Engines::KeplerCompute& kepler_compute;
const Device& device;
+
ShaderDiskCacheOpenGL disk_cache;
std::unordered_map<u64, PrecompiledShader> runtime_cache;
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
index 2dcc2b0eb..166ee34e1 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
@@ -73,7 +73,7 @@ ShaderDiskCacheEntry::ShaderDiskCacheEntry() = default;
ShaderDiskCacheEntry::~ShaderDiskCacheEntry() = default;
-bool ShaderDiskCacheEntry::Load(FileUtil::IOFile& file) {
+bool ShaderDiskCacheEntry::Load(Common::FS::IOFile& file) {
if (file.ReadBytes(&type, sizeof(u32)) != sizeof(u32)) {
return false;
}
@@ -144,7 +144,7 @@ bool ShaderDiskCacheEntry::Load(FileUtil::IOFile& file) {
return true;
}
-bool ShaderDiskCacheEntry::Save(FileUtil::IOFile& file) const {
+bool ShaderDiskCacheEntry::Save(Common::FS::IOFile& file) const {
if (file.WriteObject(static_cast<u32>(type)) != 1 ||
file.WriteObject(static_cast<u32>(code.size())) != 1 ||
file.WriteObject(static_cast<u32>(code_b.size())) != 1) {
@@ -206,28 +206,32 @@ bool ShaderDiskCacheEntry::Save(FileUtil::IOFile& file) const {
flat_bindless_samplers.size();
}
-ShaderDiskCacheOpenGL::ShaderDiskCacheOpenGL(Core::System& system) : system{system} {}
+ShaderDiskCacheOpenGL::ShaderDiskCacheOpenGL() = default;
ShaderDiskCacheOpenGL::~ShaderDiskCacheOpenGL() = default;
+void ShaderDiskCacheOpenGL::BindTitleID(u64 title_id_) {
+ title_id = title_id_;
+}
+
std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTransferable() {
// Skip games without title id
- const bool has_title_id = system.CurrentProcess()->GetTitleID() != 0;
+ const bool has_title_id = title_id != 0;
if (!Settings::values.use_disk_shader_cache.GetValue() || !has_title_id) {
- return {};
+ return std::nullopt;
}
- FileUtil::IOFile file(GetTransferablePath(), "rb");
+ Common::FS::IOFile file(GetTransferablePath(), "rb");
if (!file.IsOpen()) {
LOG_INFO(Render_OpenGL, "No transferable shader cache found");
is_usable = true;
- return {};
+ return std::nullopt;
}
u32 version{};
if (file.ReadBytes(&version, sizeof(version)) != sizeof(version)) {
LOG_ERROR(Render_OpenGL, "Failed to get transferable cache version, skipping it");
- return {};
+ return std::nullopt;
}
if (version < NativeVersion) {
@@ -235,12 +239,12 @@ std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTran
file.Close();
InvalidateTransferable();
is_usable = true;
- return {};
+ return std::nullopt;
}
if (version > NativeVersion) {
LOG_WARNING(Render_OpenGL, "Transferable shader cache was generated with a newer version "
"of the emulator, skipping");
- return {};
+ return std::nullopt;
}
// Version is valid, load the shaders
@@ -249,7 +253,7 @@ std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTran
ShaderDiskCacheEntry& entry = entries.emplace_back();
if (!entry.Load(file)) {
LOG_ERROR(Render_OpenGL, "Failed to load transferable raw entry, skipping");
- return {};
+ return std::nullopt;
}
}
@@ -262,7 +266,7 @@ std::vector<ShaderDiskCachePrecompiled> ShaderDiskCacheOpenGL::LoadPrecompiled()
return {};
}
- FileUtil::IOFile file(GetPrecompiledPath(), "rb");
+ Common::FS::IOFile file(GetPrecompiledPath(), "rb");
if (!file.IsOpen()) {
LOG_INFO(Render_OpenGL, "No precompiled shader cache found");
return {};
@@ -279,7 +283,7 @@ std::vector<ShaderDiskCachePrecompiled> ShaderDiskCacheOpenGL::LoadPrecompiled()
}
std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::LoadPrecompiledFile(
- FileUtil::IOFile& file) {
+ Common::FS::IOFile& file) {
// Read compressed file from disk and decompress to virtual precompiled cache file
std::vector<u8> compressed(file.GetSize());
file.ReadBytes(compressed.data(), compressed.size());
@@ -290,12 +294,12 @@ std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::Lo
ShaderCacheVersionHash file_hash{};
if (!LoadArrayFromPrecompiled(file_hash.data(), file_hash.size())) {
precompiled_cache_virtual_file_offset = 0;
- return {};
+ return std::nullopt;
}
if (GetShaderCacheVersionHash() != file_hash) {
LOG_INFO(Render_OpenGL, "Precompiled cache is from another version of the emulator");
precompiled_cache_virtual_file_offset = 0;
- return {};
+ return std::nullopt;
}
std::vector<ShaderDiskCachePrecompiled> entries;
@@ -305,19 +309,20 @@ std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::Lo
if (!LoadObjectFromPrecompiled(entry.unique_identifier) ||
!LoadObjectFromPrecompiled(entry.binary_format) ||
!LoadObjectFromPrecompiled(binary_size)) {
- return {};
+ return std::nullopt;
}
entry.binary.resize(binary_size);
if (!LoadArrayFromPrecompiled(entry.binary.data(), entry.binary.size())) {
- return {};
+ return std::nullopt;
}
}
- return entries;
+
+ return std::move(entries);
}
void ShaderDiskCacheOpenGL::InvalidateTransferable() {
- if (!FileUtil::Delete(GetTransferablePath())) {
+ if (!Common::FS::Delete(GetTransferablePath())) {
LOG_ERROR(Render_OpenGL, "Failed to invalidate transferable file={}",
GetTransferablePath());
}
@@ -328,7 +333,7 @@ void ShaderDiskCacheOpenGL::InvalidatePrecompiled() {
// Clear virtaul precompiled cache file
precompiled_cache_virtual_file.Resize(0);
- if (!FileUtil::Delete(GetPrecompiledPath())) {
+ if (!Common::FS::Delete(GetPrecompiledPath())) {
LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", GetPrecompiledPath());
}
}
@@ -344,7 +349,7 @@ void ShaderDiskCacheOpenGL::SaveEntry(const ShaderDiskCacheEntry& entry) {
return;
}
- FileUtil::IOFile file = AppendTransferableFile();
+ Common::FS::IOFile file = AppendTransferableFile();
if (!file.IsOpen()) {
return;
}
@@ -386,15 +391,15 @@ void ShaderDiskCacheOpenGL::SavePrecompiled(u64 unique_identifier, GLuint progra
}
}
-FileUtil::IOFile ShaderDiskCacheOpenGL::AppendTransferableFile() const {
+Common::FS::IOFile ShaderDiskCacheOpenGL::AppendTransferableFile() const {
if (!EnsureDirectories()) {
return {};
}
const auto transferable_path{GetTransferablePath()};
- const bool existed = FileUtil::Exists(transferable_path);
+ const bool existed = Common::FS::Exists(transferable_path);
- FileUtil::IOFile file(transferable_path, "ab");
+ Common::FS::IOFile file(transferable_path, "ab");
if (!file.IsOpen()) {
LOG_ERROR(Render_OpenGL, "Failed to open transferable cache in path={}", transferable_path);
return {};
@@ -426,7 +431,7 @@ void ShaderDiskCacheOpenGL::SaveVirtualPrecompiledFile() {
Common::Compression::CompressDataZSTDDefault(uncompressed.data(), uncompressed.size());
const auto precompiled_path{GetPrecompiledPath()};
- FileUtil::IOFile file(precompiled_path, "wb");
+ Common::FS::IOFile file(precompiled_path, "wb");
if (!file.IsOpen()) {
LOG_ERROR(Render_OpenGL, "Failed to open precompiled cache in path={}", precompiled_path);
@@ -440,24 +445,24 @@ void ShaderDiskCacheOpenGL::SaveVirtualPrecompiledFile() {
bool ShaderDiskCacheOpenGL::EnsureDirectories() const {
const auto CreateDir = [](const std::string& dir) {
- if (!FileUtil::CreateDir(dir)) {
+ if (!Common::FS::CreateDir(dir)) {
LOG_ERROR(Render_OpenGL, "Failed to create directory={}", dir);
return false;
}
return true;
};
- return CreateDir(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)) &&
+ return CreateDir(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir)) &&
CreateDir(GetBaseDir()) && CreateDir(GetTransferableDir()) &&
CreateDir(GetPrecompiledDir());
}
std::string ShaderDiskCacheOpenGL::GetTransferablePath() const {
- return FileUtil::SanitizePath(GetTransferableDir() + DIR_SEP_CHR + GetTitleID() + ".bin");
+ return Common::FS::SanitizePath(GetTransferableDir() + DIR_SEP_CHR + GetTitleID() + ".bin");
}
std::string ShaderDiskCacheOpenGL::GetPrecompiledPath() const {
- return FileUtil::SanitizePath(GetPrecompiledDir() + DIR_SEP_CHR + GetTitleID() + ".bin");
+ return Common::FS::SanitizePath(GetPrecompiledDir() + DIR_SEP_CHR + GetTitleID() + ".bin");
}
std::string ShaderDiskCacheOpenGL::GetTransferableDir() const {
@@ -469,11 +474,11 @@ std::string ShaderDiskCacheOpenGL::GetPrecompiledDir() const {
}
std::string ShaderDiskCacheOpenGL::GetBaseDir() const {
- return FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir) + DIR_SEP "opengl";
+ return Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir) + DIR_SEP "opengl";
}
std::string ShaderDiskCacheOpenGL::GetTitleID() const {
- return fmt::format("{:016X}", system.CurrentProcess()->GetTitleID());
+ return fmt::format("{:016X}", title_id);
}
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.h b/src/video_core/renderer_opengl/gl_shader_disk_cache.h
index a79cef0e9..aef841c1d 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.h
@@ -21,11 +21,7 @@
#include "video_core/engines/shader_type.h"
#include "video_core/shader/registry.h"
-namespace Core {
-class System;
-}
-
-namespace FileUtil {
+namespace Common::FS {
class IOFile;
}
@@ -38,9 +34,9 @@ struct ShaderDiskCacheEntry {
ShaderDiskCacheEntry();
~ShaderDiskCacheEntry();
- bool Load(FileUtil::IOFile& file);
+ bool Load(Common::FS::IOFile& file);
- bool Save(FileUtil::IOFile& file) const;
+ bool Save(Common::FS::IOFile& file) const;
bool HasProgramA() const {
return !code.empty() && !code_b.empty();
@@ -70,9 +66,12 @@ struct ShaderDiskCachePrecompiled {
class ShaderDiskCacheOpenGL {
public:
- explicit ShaderDiskCacheOpenGL(Core::System& system);
+ explicit ShaderDiskCacheOpenGL();
~ShaderDiskCacheOpenGL();
+ /// Binds a title ID for all future operations.
+ void BindTitleID(u64 title_id);
+
/// Loads transferable cache. If file has a old version or on failure, it deletes the file.
std::optional<std::vector<ShaderDiskCacheEntry>> LoadTransferable();
@@ -97,10 +96,10 @@ public:
private:
/// Loads the transferable cache. Returns empty on failure.
std::optional<std::vector<ShaderDiskCachePrecompiled>> LoadPrecompiledFile(
- FileUtil::IOFile& file);
+ Common::FS::IOFile& file);
/// Opens current game's transferable file and write it's header if it doesn't exist
- FileUtil::IOFile AppendTransferableFile() const;
+ Common::FS::IOFile AppendTransferableFile() const;
/// Save precompiled header to precompiled_cache_in_memory
void SavePrecompiledHeaderToVirtualPrecompiledCache();
@@ -157,8 +156,6 @@ private:
return LoadArrayFromPrecompiled(&object, 1);
}
- Core::System& system;
-
// Stores whole precompiled cache which will be read from or saved to the precompiled chache
// file
FileSys::VectorVfsFile precompiled_cache_virtual_file;
@@ -168,8 +165,11 @@ private:
// Stored transferable shaders
std::unordered_set<u64> stored_transferable;
+ /// Title ID to operate on
+ u64 title_id = 0;
+
// The cache has been loaded at boot
- bool is_usable{};
+ bool is_usable = false;
};
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp
index 9e74eda0d..4bf0d6090 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_util.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <string_view>
#include <vector>
#include <glad/glad.h>
#include "common/assert.h"
@@ -11,7 +12,8 @@
namespace OpenGL::GLShader {
namespace {
-const char* GetStageDebugName(GLenum type) {
+
+std::string_view StageDebugName(GLenum type) {
switch (type) {
case GL_VERTEX_SHADER:
return "vertex";
@@ -25,12 +27,17 @@ const char* GetStageDebugName(GLenum type) {
UNIMPLEMENTED();
return "unknown";
}
+
} // Anonymous namespace
-GLuint LoadShader(const char* source, GLenum type) {
- const char* debug_type = GetStageDebugName(type);
+GLuint LoadShader(std::string_view source, GLenum type) {
+ const std::string_view debug_type = StageDebugName(type);
const GLuint shader_id = glCreateShader(type);
- glShaderSource(shader_id, 1, &source, nullptr);
+
+ const GLchar* source_string = source.data();
+ const GLint source_length = static_cast<GLint>(source.size());
+
+ glShaderSource(shader_id, 1, &source_string, &source_length);
LOG_DEBUG(Render_OpenGL, "Compiling {} shader...", debug_type);
glCompileShader(shader_id);
diff --git a/src/video_core/renderer_opengl/gl_shader_util.h b/src/video_core/renderer_opengl/gl_shader_util.h
index 03b7548c2..1b770532e 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.h
+++ b/src/video_core/renderer_opengl/gl_shader_util.h
@@ -38,7 +38,7 @@ void LogShaderSource(T... shaders) {
* @param source String of the GLSL shader program
* @param type Type of the shader (GL_VERTEX_SHADER, GL_GEOMETRY_SHADER or GL_FRAGMENT_SHADER)
*/
-GLuint LoadShader(const char* source, GLenum type);
+GLuint LoadShader(std::string_view source, GLenum type);
/**
* Utility function to create and compile an OpenGL GLSL shader program (vertex + fragment shader)
diff --git a/src/video_core/renderer_opengl/gl_state_tracker.cpp b/src/video_core/renderer_opengl/gl_state_tracker.cpp
index d24fad3de..6bcf831f2 100644
--- a/src/video_core/renderer_opengl/gl_state_tracker.cpp
+++ b/src/video_core/renderer_opengl/gl_state_tracker.cpp
@@ -214,10 +214,8 @@ void SetupDirtyMisc(Tables& tables) {
} // Anonymous namespace
-StateTracker::StateTracker(Core::System& system) : system{system} {}
-
-void StateTracker::Initialize() {
- auto& dirty = system.GPU().Maxwell3D().dirty;
+StateTracker::StateTracker(Tegra::GPU& gpu) : flags{gpu.Maxwell3D().dirty.flags} {
+ auto& dirty = gpu.Maxwell3D().dirty;
auto& tables = dirty.tables;
SetupDirtyRenderTargets(tables);
SetupDirtyColorMasks(tables);
diff --git a/src/video_core/renderer_opengl/gl_state_tracker.h b/src/video_core/renderer_opengl/gl_state_tracker.h
index 0f823288e..9d127548f 100644
--- a/src/video_core/renderer_opengl/gl_state_tracker.h
+++ b/src/video_core/renderer_opengl/gl_state_tracker.h
@@ -13,8 +13,8 @@
#include "video_core/dirty_flags.h"
#include "video_core/engines/maxwell_3d.h"
-namespace Core {
-class System;
+namespace Tegra {
+class GPU;
}
namespace OpenGL {
@@ -90,9 +90,7 @@ static_assert(Last <= std::numeric_limits<u8>::max());
class StateTracker {
public:
- explicit StateTracker(Core::System& system);
-
- void Initialize();
+ explicit StateTracker(Tegra::GPU& gpu);
void BindIndexBuffer(GLuint new_index_buffer) {
if (index_buffer == new_index_buffer) {
@@ -103,7 +101,6 @@ public:
}
void NotifyScreenDrawVertexArray() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::VertexFormats] = true;
flags[OpenGL::Dirty::VertexFormat0 + 0] = true;
flags[OpenGL::Dirty::VertexFormat0 + 1] = true;
@@ -117,98 +114,81 @@ public:
}
void NotifyPolygonModes() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::PolygonModes] = true;
flags[OpenGL::Dirty::PolygonModeFront] = true;
flags[OpenGL::Dirty::PolygonModeBack] = true;
}
void NotifyViewport0() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::Viewports] = true;
flags[OpenGL::Dirty::Viewport0] = true;
}
void NotifyScissor0() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::Scissors] = true;
flags[OpenGL::Dirty::Scissor0] = true;
}
void NotifyColorMask0() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::ColorMasks] = true;
flags[OpenGL::Dirty::ColorMask0] = true;
}
void NotifyBlend0() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::BlendStates] = true;
flags[OpenGL::Dirty::BlendState0] = true;
}
void NotifyFramebuffer() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[VideoCommon::Dirty::RenderTargets] = true;
}
void NotifyFrontFace() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::FrontFace] = true;
}
void NotifyCullTest() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::CullTest] = true;
}
void NotifyDepthMask() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::DepthMask] = true;
}
void NotifyDepthTest() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::DepthTest] = true;
}
void NotifyStencilTest() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::StencilTest] = true;
}
void NotifyPolygonOffset() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::PolygonOffset] = true;
}
void NotifyRasterizeEnable() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::RasterizeEnable] = true;
}
void NotifyFramebufferSRGB() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::FramebufferSRGB] = true;
}
void NotifyLogicOp() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::LogicOp] = true;
}
void NotifyClipControl() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::ClipControl] = true;
}
void NotifyAlphaTest() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::AlphaTest] = true;
}
private:
- Core::System& system;
+ Tegra::Engines::Maxwell3D::DirtyState::Flags& flags;
GLuint index_buffer = 0;
};
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 0a7bc9e2b..a863ef218 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -403,7 +403,7 @@ void CachedSurface::DecorateSurfaceName() {
LabelGLObject(GL_TEXTURE, texture.handle, GetGpuAddr(), params.TargetName());
}
-void CachedSurfaceView::DecorateViewName(GPUVAddr gpu_addr, std::string prefix) {
+void CachedSurfaceView::DecorateViewName(GPUVAddr gpu_addr, const std::string& prefix) {
LabelGLObject(GL_TEXTURE, main_view.handle, gpu_addr, prefix);
}
@@ -532,10 +532,12 @@ OGLTextureView CachedSurfaceView::CreateTextureView() const {
return texture_view;
}
-TextureCacheOpenGL::TextureCacheOpenGL(Core::System& system,
- VideoCore::RasterizerInterface& rasterizer,
- const Device& device, StateTracker& state_tracker)
- : TextureCacheBase{system, rasterizer, device.HasASTC()}, state_tracker{state_tracker} {
+TextureCacheOpenGL::TextureCacheOpenGL(VideoCore::RasterizerInterface& rasterizer,
+ Tegra::Engines::Maxwell3D& maxwell3d,
+ Tegra::MemoryManager& gpu_memory, const Device& device,
+ StateTracker& state_tracker_)
+ : TextureCacheBase{rasterizer, maxwell3d, gpu_memory, device.HasASTC()}, state_tracker{
+ state_tracker_} {
src_framebuffer.Create();
dst_framebuffer.Create();
}
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index bfc4ddf5d..7787134fc 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -90,7 +90,7 @@ public:
Tegra::Texture::SwizzleSource z_source,
Tegra::Texture::SwizzleSource w_source);
- void DecorateViewName(GPUVAddr gpu_addr, std::string prefix);
+ void DecorateViewName(GPUVAddr gpu_addr, const std::string& prefix);
void MarkAsModified(u64 tick) {
surface.MarkAsModified(true, tick);
@@ -129,8 +129,10 @@ private:
class TextureCacheOpenGL final : public TextureCacheBase {
public:
- explicit TextureCacheOpenGL(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- const Device& device, StateTracker& state_tracker);
+ explicit TextureCacheOpenGL(VideoCore::RasterizerInterface& rasterizer,
+ Tegra::Engines::Maxwell3D& maxwell3d,
+ Tegra::MemoryManager& gpu_memory, const Device& device,
+ StateTracker& state_tracker);
~TextureCacheOpenGL();
protected:
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 52e9e8250..a4c5b8f74 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -21,6 +21,8 @@
#include "core/perf_stats.h"
#include "core/settings.h"
#include "core/telemetry_session.h"
+#include "video_core/host_shaders/opengl_present_frag.h"
+#include "video_core/host_shaders/opengl_present_vert.h"
#include "video_core/morton.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
@@ -44,46 +46,6 @@ struct Frame {
bool is_srgb{}; /// Framebuffer is sRGB or RGB
};
-constexpr char VERTEX_SHADER[] = R"(
-#version 430 core
-
-out gl_PerVertex {
- vec4 gl_Position;
-};
-
-layout (location = 0) in vec2 vert_position;
-layout (location = 1) in vec2 vert_tex_coord;
-layout (location = 0) out vec2 frag_tex_coord;
-
-// This is a truncated 3x3 matrix for 2D transformations:
-// The upper-left 2x2 submatrix performs scaling/rotation/mirroring.
-// The third column performs translation.
-// The third row could be used for projection, which we don't need in 2D. It hence is assumed to
-// implicitly be [0, 0, 1]
-layout (location = 0) uniform mat3x2 modelview_matrix;
-
-void main() {
- // Multiply input position by the rotscale part of the matrix and then manually translate by
- // the last column. This is equivalent to using a full 3x3 matrix and expanding the vector
- // to `vec3(vert_position.xy, 1.0)`
- gl_Position = vec4(mat2(modelview_matrix) * vert_position + modelview_matrix[2], 0.0, 1.0);
- frag_tex_coord = vert_tex_coord;
-}
-)";
-
-constexpr char FRAGMENT_SHADER[] = R"(
-#version 430 core
-
-layout (location = 0) in vec2 frag_tex_coord;
-layout (location = 0) out vec4 color;
-
-layout (binding = 0) uniform sampler2D color_texture;
-
-void main() {
- color = vec4(texture(color_texture, frag_tex_coord).rgb, 1.0f);
-}
-)";
-
constexpr GLint PositionLocation = 0;
constexpr GLint TexCoordLocation = 1;
constexpr GLint ModelViewMatrixLocation = 0;
@@ -313,10 +275,13 @@ public:
}
};
-RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system,
- Core::Frontend::GraphicsContext& context)
- : RendererBase{emu_window}, emu_window{emu_window}, system{system}, context{context},
- program_manager{device}, has_debug_tool{HasDebugTool()} {}
+RendererOpenGL::RendererOpenGL(Core::TelemetrySession& telemetry_session_,
+ Core::Frontend::EmuWindow& emu_window_,
+ Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_,
+ std::unique_ptr<Core::Frontend::GraphicsContext> context)
+ : RendererBase{emu_window_, std::move(context)}, telemetry_session{telemetry_session_},
+ emu_window{emu_window_}, cpu_memory{cpu_memory_}, gpu{gpu_}, program_manager{device},
+ has_debug_tool{HasDebugTool()} {}
RendererOpenGL::~RendererOpenGL() = default;
@@ -384,7 +349,7 @@ void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
if (has_debug_tool) {
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
Present(0);
- context.SwapBuffers();
+ context->SwapBuffers();
}
}
@@ -423,7 +388,7 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf
VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)};
const u32 bytes_per_pixel{VideoCore::Surface::GetBytesPerPixel(pixel_format)};
const u64 size_in_bytes{framebuffer.stride * framebuffer.height * bytes_per_pixel};
- u8* const host_ptr{system.Memory().GetPointer(framebuffer_addr)};
+ u8* const host_ptr{cpu_memory.GetPointer(framebuffer_addr)};
rasterizer->FlushRegion(ToCacheAddr(host_ptr), size_in_bytes);
// TODO(Rodrigo): Read this from HLE
@@ -460,10 +425,10 @@ void RendererOpenGL::InitOpenGLObjects() {
// Create shader programs
OGLShader vertex_shader;
- vertex_shader.Create(VERTEX_SHADER, GL_VERTEX_SHADER);
+ vertex_shader.Create(HostShaders::OPENGL_PRESENT_VERT, GL_VERTEX_SHADER);
OGLShader fragment_shader;
- fragment_shader.Create(FRAGMENT_SHADER, GL_FRAGMENT_SHADER);
+ fragment_shader.Create(HostShaders::OPENGL_PRESENT_FRAG, GL_FRAGMENT_SHADER);
vertex_program.Create(true, false, vertex_shader.handle);
fragment_program.Create(true, false, fragment_shader.handle);
@@ -508,18 +473,18 @@ void RendererOpenGL::AddTelemetryFields() {
LOG_INFO(Render_OpenGL, "GL_VENDOR: {}", gpu_vendor);
LOG_INFO(Render_OpenGL, "GL_RENDERER: {}", gpu_model);
- auto& telemetry_session = system.TelemetrySession();
- telemetry_session.AddField(Telemetry::FieldType::UserSystem, "GPU_Vendor", gpu_vendor);
- telemetry_session.AddField(Telemetry::FieldType::UserSystem, "GPU_Model", gpu_model);
- telemetry_session.AddField(Telemetry::FieldType::UserSystem, "GPU_OpenGL_Version", gl_version);
+ constexpr auto user_system = Common::Telemetry::FieldType::UserSystem;
+ telemetry_session.AddField(user_system, "GPU_Vendor", gpu_vendor);
+ telemetry_session.AddField(user_system, "GPU_Model", gpu_model);
+ telemetry_session.AddField(user_system, "GPU_OpenGL_Version", gl_version);
}
void RendererOpenGL::CreateRasterizer() {
if (rasterizer) {
return;
}
- rasterizer = std::make_unique<RasterizerOpenGL>(system, emu_window, device, screen_info,
- program_manager, state_tracker);
+ rasterizer = std::make_unique<RasterizerOpenGL>(emu_window, gpu, cpu_memory, device,
+ screen_info, program_manager, state_tracker);
}
void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index 8b18d32e6..5329577fb 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -16,16 +16,25 @@
namespace Core {
class System;
-}
+class TelemetrySession;
+} // namespace Core
namespace Core::Frontend {
class EmuWindow;
}
+namespace Core::Memory {
+class Memory;
+}
+
namespace Layout {
struct FramebufferLayout;
}
+namespace Tegra {
+class GPU;
+}
+
namespace OpenGL {
/// Structure used for storing information about the textures for the Switch screen
@@ -56,8 +65,10 @@ class FrameMailbox;
class RendererOpenGL final : public VideoCore::RendererBase {
public:
- explicit RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system,
- Core::Frontend::GraphicsContext& context);
+ explicit RendererOpenGL(Core::TelemetrySession& telemetry_session,
+ Core::Frontend::EmuWindow& emu_window, Core::Memory::Memory& cpu_memory,
+ Tegra::GPU& gpu,
+ std::unique_ptr<Core::Frontend::GraphicsContext> context);
~RendererOpenGL() override;
bool Init() override;
@@ -93,12 +104,13 @@ private:
bool Present(int timeout_ms);
+ Core::TelemetrySession& telemetry_session;
Core::Frontend::EmuWindow& emu_window;
- Core::System& system;
- Core::Frontend::GraphicsContext& context;
- const Device device;
+ Core::Memory::Memory& cpu_memory;
+ Tegra::GPU& gpu;
- StateTracker state_tracker{system};
+ const Device device;
+ StateTracker state_tracker{gpu};
// OpenGL object IDs
OGLBuffer vertex_buffer;
@@ -120,7 +132,7 @@ private:
std::vector<u8> gl_framebuffer_data;
/// Used for transforming the framebuffer orientation
- Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags;
+ Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags{};
Common::Rectangle<int> framebuffer_crop_rect;
/// Frame presentation mailbox
diff --git a/src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp b/src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp
index 435c8c1b8..5b01020ec 100644
--- a/src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp
+++ b/src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp
@@ -65,10 +65,10 @@ bool NsightAftermathTracker::Initialize() {
return false;
}
- dump_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir) + "gpucrash";
+ dump_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir) + "gpucrash";
- (void)FileUtil::DeleteDirRecursively(dump_dir);
- if (!FileUtil::CreateDir(dump_dir)) {
+ (void)Common::FS::DeleteDirRecursively(dump_dir);
+ if (!Common::FS::CreateDir(dump_dir)) {
LOG_ERROR(Render_Vulkan, "Failed to create Nsight Aftermath dump directory");
return false;
}
@@ -106,7 +106,7 @@ void NsightAftermathTracker::SaveShader(const std::vector<u32>& spirv) const {
return;
}
- FileUtil::IOFile file(fmt::format("{}/source_{:016x}.spv", dump_dir, hash.hash), "wb");
+ Common::FS::IOFile file(fmt::format("{}/source_{:016x}.spv", dump_dir, hash.hash), "wb");
if (!file.IsOpen()) {
LOG_ERROR(Render_Vulkan, "Failed to dump SPIR-V module with hash={:016x}", hash.hash);
return;
@@ -156,12 +156,12 @@ void NsightAftermathTracker::OnGpuCrashDumpCallback(const void* gpu_crash_dump,
}();
std::string_view dump_view(static_cast<const char*>(gpu_crash_dump), gpu_crash_dump_size);
- if (FileUtil::WriteStringToFile(false, base_name, dump_view) != gpu_crash_dump_size) {
+ if (Common::FS::WriteStringToFile(false, base_name, dump_view) != gpu_crash_dump_size) {
LOG_ERROR(Render_Vulkan, "Failed to write dump file");
return;
}
const std::string_view json_view(json.data(), json.size());
- if (FileUtil::WriteStringToFile(true, base_name + ".json", json_view) != json.size()) {
+ if (Common::FS::WriteStringToFile(true, base_name + ".json", json_view) != json.size()) {
LOG_ERROR(Render_Vulkan, "Failed to write JSON");
return;
}
@@ -180,7 +180,7 @@ void NsightAftermathTracker::OnShaderDebugInfoCallback(const void* shader_debug_
const std::string path =
fmt::format("{}/shader_{:016x}{:016x}.nvdbg", dump_dir, identifier.id[0], identifier.id[1]);
- FileUtil::IOFile file(path, "wb");
+ Common::FS::IOFile file(path, "wb");
if (!file.IsOpen()) {
LOG_ERROR(Render_Vulkan, "Failed to create file {}", path);
return;
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 2258479f5..0e4583986 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -78,7 +78,7 @@ Common::DynamicLibrary OpenVulkanLibrary() {
if (!libvulkan_env || !library.Open(libvulkan_env)) {
// Use the libvulkan.dylib from the application bundle.
const std::string filename =
- FileUtil::GetBundleDirectory() + "/Contents/Frameworks/libvulkan.dylib";
+ Common::FS::GetBundleDirectory() + "/Contents/Frameworks/libvulkan.dylib";
library.Open(filename.c_str());
}
#else
@@ -86,7 +86,7 @@ Common::DynamicLibrary OpenVulkanLibrary() {
if (!library.Open(filename.c_str())) {
// Android devices may not have libvulkan.so.1, only libvulkan.so.
filename = Common::DynamicLibrary::GetVersionedFilename("vulkan");
- library.Open(filename.c_str());
+ (void)library.Open(filename.c_str());
}
#endif
return library;
@@ -237,8 +237,12 @@ std::string BuildCommaSeparatedExtensions(std::vector<std::string> available_ext
} // Anonymous namespace
-RendererVulkan::RendererVulkan(Core::Frontend::EmuWindow& window, Core::System& system)
- : RendererBase(window), system{system} {}
+RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
+ Core::Frontend::EmuWindow& emu_window,
+ Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_,
+ std::unique_ptr<Core::Frontend::GraphicsContext> context)
+ : RendererBase{emu_window, std::move(context)}, telemetry_session{telemetry_session_},
+ cpu_memory{cpu_memory_}, gpu{gpu_} {}
RendererVulkan::~RendererVulkan() {
ShutDown();
@@ -302,15 +306,15 @@ bool RendererVulkan::Init() {
swapchain = std::make_unique<VKSwapchain>(*surface, *device);
swapchain->Create(framebuffer.width, framebuffer.height, false);
- state_tracker = std::make_unique<StateTracker>(system);
+ state_tracker = std::make_unique<StateTracker>(gpu);
scheduler = std::make_unique<VKScheduler>(*device, *resource_manager, *state_tracker);
- rasterizer = std::make_unique<RasterizerVulkan>(system, render_window, screen_info, *device,
- *resource_manager, *memory_manager,
- *state_tracker, *scheduler);
+ rasterizer = std::make_unique<RasterizerVulkan>(
+ render_window, gpu, gpu.MemoryManager(), cpu_memory, screen_info, *device,
+ *resource_manager, *memory_manager, *state_tracker, *scheduler);
- blit_screen = std::make_unique<VKBlitScreen>(system, render_window, *rasterizer, *device,
+ blit_screen = std::make_unique<VKBlitScreen>(cpu_memory, render_window, *rasterizer, *device,
*resource_manager, *memory_manager, *swapchain,
*scheduler, screen_info);
@@ -438,8 +442,7 @@ void RendererVulkan::Report() const {
LOG_INFO(Render_Vulkan, "Device: {}", model_name);
LOG_INFO(Render_Vulkan, "Vulkan: {}", api_version);
- auto& telemetry_session = system.TelemetrySession();
- constexpr auto field = Telemetry::FieldType::UserSystem;
+ static constexpr auto field = Common::Telemetry::FieldType::UserSystem;
telemetry_session.AddField(field, "GPU_Vendor", vendor_name);
telemetry_session.AddField(field, "GPU_Model", model_name);
telemetry_session.AddField(field, "GPU_Vulkan_Driver", driver_name);
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index 522b5bff8..ddff77942 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -14,7 +14,15 @@
#include "video_core/renderer_vulkan/wrapper.h"
namespace Core {
-class System;
+class TelemetrySession;
+}
+
+namespace Core::Memory {
+class Memory;
+}
+
+namespace Tegra {
+class GPU;
}
namespace Vulkan {
@@ -38,7 +46,10 @@ struct VKScreenInfo {
class RendererVulkan final : public VideoCore::RendererBase {
public:
- explicit RendererVulkan(Core::Frontend::EmuWindow& window, Core::System& system);
+ explicit RendererVulkan(Core::TelemetrySession& telemtry_session,
+ Core::Frontend::EmuWindow& emu_window, Core::Memory::Memory& cpu_memory,
+ Tegra::GPU& gpu,
+ std::unique_ptr<Core::Frontend::GraphicsContext> context);
~RendererVulkan() override;
bool Init() override;
@@ -57,7 +68,9 @@ private:
void Report() const;
- Core::System& system;
+ Core::TelemetrySession& telemetry_session;
+ Core::Memory::Memory& cpu_memory;
+ Tegra::GPU& gpu;
Common::DynamicLibrary library;
vk::InstanceDispatch dld;
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index ce53e5a6b..2bea7b24d 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -210,14 +210,16 @@ struct VKBlitScreen::BufferData {
// Unaligned image data goes here
};
-VKBlitScreen::VKBlitScreen(Core::System& system, Core::Frontend::EmuWindow& render_window,
- VideoCore::RasterizerInterface& rasterizer, const VKDevice& device,
- VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
- VKSwapchain& swapchain, VKScheduler& scheduler,
- const VKScreenInfo& screen_info)
- : system{system}, render_window{render_window}, rasterizer{rasterizer}, device{device},
- resource_manager{resource_manager}, memory_manager{memory_manager}, swapchain{swapchain},
- scheduler{scheduler}, image_count{swapchain.GetImageCount()}, screen_info{screen_info} {
+VKBlitScreen::VKBlitScreen(Core::Memory::Memory& cpu_memory_,
+ Core::Frontend::EmuWindow& render_window_,
+ VideoCore::RasterizerInterface& rasterizer_, const VKDevice& device_,
+ VKResourceManager& resource_manager_, VKMemoryManager& memory_manager_,
+ VKSwapchain& swapchain_, VKScheduler& scheduler_,
+ const VKScreenInfo& screen_info_)
+ : cpu_memory{cpu_memory_}, render_window{render_window_},
+ rasterizer{rasterizer_}, device{device_}, resource_manager{resource_manager_},
+ memory_manager{memory_manager_}, swapchain{swapchain_}, scheduler{scheduler_},
+ image_count{swapchain.GetImageCount()}, screen_info{screen_info_} {
watches.resize(image_count);
std::generate(watches.begin(), watches.end(),
[]() { return std::make_unique<VKFenceWatch>(); });
@@ -259,7 +261,7 @@ std::tuple<VKFence&, VkSemaphore> VKBlitScreen::Draw(const Tegra::FramebufferCon
const auto pixel_format =
VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format);
const VAddr framebuffer_addr = framebuffer.address + framebuffer.offset;
- const auto host_ptr = system.Memory().GetPointer(framebuffer_addr);
+ const auto host_ptr = cpu_memory.GetPointer(framebuffer_addr);
rasterizer.FlushRegion(ToCacheAddr(host_ptr), GetSizeInBytes(framebuffer));
// TODO(Rodrigo): Read this from HLE
@@ -696,6 +698,7 @@ void VKBlitScreen::CreateFramebuffers() {
.flags = 0,
.renderPass = *renderpass,
.attachmentCount = 1,
+ .pAttachments = nullptr,
.width = size.width,
.height = size.height,
.layers = 1,
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h
index 243640fab..838d38f69 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.h
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.h
@@ -15,6 +15,10 @@ namespace Core {
class System;
}
+namespace Core::Memory {
+class Memory;
+}
+
namespace Core::Frontend {
class EmuWindow;
}
@@ -39,7 +43,8 @@ class VKSwapchain;
class VKBlitScreen final {
public:
- explicit VKBlitScreen(Core::System& system, Core::Frontend::EmuWindow& render_window,
+ explicit VKBlitScreen(Core::Memory::Memory& cpu_memory,
+ Core::Frontend::EmuWindow& render_window,
VideoCore::RasterizerInterface& rasterizer, const VKDevice& device,
VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
VKSwapchain& swapchain, VKScheduler& scheduler,
@@ -81,7 +86,7 @@ private:
u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer,
std::size_t image_index) const;
- Core::System& system;
+ Core::Memory::Memory& cpu_memory;
Core::Frontend::EmuWindow& render_window;
VideoCore::RasterizerInterface& rasterizer;
const VKDevice& device;
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index 1d2f8b557..d9d3da9ea 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -145,14 +145,15 @@ void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst
});
}
-VKBufferCache::VKBufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system,
- const VKDevice& device, VKMemoryManager& memory_manager,
- VKScheduler& scheduler, VKStagingBufferPool& staging_pool)
- : VideoCommon::BufferCache<Buffer, VkBuffer, VKStreamBuffer>{rasterizer, system,
- CreateStreamBuffer(device,
- scheduler)},
- device{device}, memory_manager{memory_manager}, scheduler{scheduler}, staging_pool{
- staging_pool} {}
+VKBufferCache::VKBufferCache(VideoCore::RasterizerInterface& rasterizer,
+ Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory,
+ const VKDevice& device_, VKMemoryManager& memory_manager_,
+ VKScheduler& scheduler_, VKStagingBufferPool& staging_pool_)
+ : VideoCommon::BufferCache<Buffer, VkBuffer, VKStreamBuffer>{rasterizer, gpu_memory, cpu_memory,
+ CreateStreamBuffer(device_,
+ scheduler_)},
+ device{device_}, memory_manager{memory_manager_}, scheduler{scheduler_}, staging_pool{
+ staging_pool_} {}
VKBufferCache::~VKBufferCache() = default;
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index 991ee451c..7fb5ceedf 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -13,10 +13,6 @@
#include "video_core/renderer_vulkan/vk_stream_buffer.h"
#include "video_core/renderer_vulkan/wrapper.h"
-namespace Core {
-class System;
-}
-
namespace Vulkan {
class VKDevice;
@@ -53,7 +49,8 @@ private:
class VKBufferCache final : public VideoCommon::BufferCache<Buffer, VkBuffer, VKStreamBuffer> {
public:
- explicit VKBufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system,
+ explicit VKBufferCache(VideoCore::RasterizerInterface& rasterizer,
+ Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory,
const VKDevice& device, VKMemoryManager& memory_manager,
VKScheduler& scheduler, VKStagingBufferPool& staging_pool);
~VKBufferCache();
diff --git a/src/video_core/renderer_vulkan/vk_device.cpp b/src/video_core/renderer_vulkan/vk_device.cpp
index 6245e0d78..4205bd573 100644
--- a/src/video_core/renderer_vulkan/vk_device.cpp
+++ b/src/video_core/renderer_vulkan/vk_device.cpp
@@ -380,8 +380,18 @@ bool VKDevice::Create() {
CollectTelemetryParameters();
+ if (ext_extended_dynamic_state && driver_id == VK_DRIVER_ID_AMD_PROPRIETARY_KHR) {
+ // AMD's proprietary driver supports VK_EXT_extended_dynamic_state but the <stride> field
+ // seems to be bugged. Blacklisting it for now.
+ LOG_WARNING(Render_Vulkan,
+ "Blacklisting AMD proprietary from VK_EXT_extended_dynamic_state");
+ ext_extended_dynamic_state = false;
+ }
+
graphics_queue = logical.GetQueue(graphics_family);
present_queue = logical.GetQueue(present_family);
+
+ use_asynchronous_shaders = Settings::values.use_asynchronous_shaders.GetValue();
return true;
}
@@ -771,8 +781,9 @@ std::vector<VkDeviceQueueCreateInfo> VKDevice::GetDeviceQueueCreateInfos() const
.pNext = nullptr,
.flags = 0,
.queueFamilyIndex = queue_family,
+ .queueCount = 1,
+ .pQueuePriorities = nullptr,
});
- ci.queueCount = 1;
ci.pQueuePriorities = &QUEUE_PRIORITY;
}
diff --git a/src/video_core/renderer_vulkan/vk_device.h b/src/video_core/renderer_vulkan/vk_device.h
index 529744f2d..26a233db1 100644
--- a/src/video_core/renderer_vulkan/vk_device.h
+++ b/src/video_core/renderer_vulkan/vk_device.h
@@ -202,6 +202,11 @@ public:
return reported_extensions;
}
+ /// Returns true if the setting for async shader compilation is enabled.
+ bool UseAsynchronousShaders() const {
+ return use_asynchronous_shaders;
+ }
+
/// Checks if the physical device is suitable.
static bool IsSuitable(vk::PhysicalDevice physical, VkSurfaceKHR surface);
@@ -252,6 +257,9 @@ private:
bool ext_extended_dynamic_state{}; ///< Support for VK_EXT_extended_dynamic_state.
bool nv_device_diagnostics_config{}; ///< Support for VK_NV_device_diagnostics_config.
+ // Asynchronous Graphics Pipeline setting
+ bool use_asynchronous_shaders{}; ///< Setting to use asynchronous shaders/graphics pipeline
+
// Telemetry parameters
std::string vendor_name; ///< Device's driver name.
std::vector<std::string> reported_extensions; ///< Reported Vulkan extensions.
diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.cpp b/src/video_core/renderer_vulkan/vk_fence_manager.cpp
index a02be5487..55a8348fc 100644
--- a/src/video_core/renderer_vulkan/vk_fence_manager.cpp
+++ b/src/video_core/renderer_vulkan/vk_fence_manager.cpp
@@ -29,7 +29,7 @@ void InnerFence::Queue() {
}
ASSERT(!event);
- event = device.GetLogical().CreateEvent();
+ event = device.GetLogical().CreateNewEvent();
ticks = scheduler.Ticks();
scheduler.RequestOutsideRenderPassOperationContext();
@@ -71,12 +71,12 @@ bool InnerFence::IsEventSignalled() const {
}
}
-VKFenceManager::VKFenceManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- const VKDevice& device, VKScheduler& scheduler,
- VKTextureCache& texture_cache, VKBufferCache& buffer_cache,
- VKQueryCache& query_cache)
- : GenericFenceManager(system, rasterizer, texture_cache, buffer_cache, query_cache),
- device{device}, scheduler{scheduler} {}
+VKFenceManager::VKFenceManager(VideoCore::RasterizerInterface& rasterizer, Tegra::GPU& gpu,
+ Tegra::MemoryManager& memory_manager, VKTextureCache& texture_cache,
+ VKBufferCache& buffer_cache, VKQueryCache& query_cache,
+ const VKDevice& device_, VKScheduler& scheduler_)
+ : GenericFenceManager(rasterizer, gpu, texture_cache, buffer_cache, query_cache),
+ device{device_}, scheduler{scheduler_} {}
Fence VKFenceManager::CreateFence(u32 value, bool is_stubbed) {
return std::make_shared<InnerFence>(device, scheduler, value, is_stubbed);
diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.h b/src/video_core/renderer_vulkan/vk_fence_manager.h
index 043fe7947..1547d6d30 100644
--- a/src/video_core/renderer_vulkan/vk_fence_manager.h
+++ b/src/video_core/renderer_vulkan/vk_fence_manager.h
@@ -55,10 +55,10 @@ using GenericFenceManager =
class VKFenceManager final : public GenericFenceManager {
public:
- explicit VKFenceManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- const VKDevice& device, VKScheduler& scheduler,
- VKTextureCache& texture_cache, VKBufferCache& buffer_cache,
- VKQueryCache& query_cache);
+ explicit VKFenceManager(VideoCore::RasterizerInterface& rasterizer, Tegra::GPU& gpu,
+ Tegra::MemoryManager& memory_manager, VKTextureCache& texture_cache,
+ VKBufferCache& buffer_cache, VKQueryCache& query_cache,
+ const VKDevice& device, VKScheduler& scheduler);
protected:
Fence CreateFence(u32 value, bool is_stubbed) override;
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index aaf930b90..2e46c6278 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -78,15 +78,14 @@ VKGraphicsPipeline::VKGraphicsPipeline(const VKDevice& device, VKScheduler& sche
const GraphicsPipelineCacheKey& key,
vk::Span<VkDescriptorSetLayoutBinding> bindings,
const SPIRVProgram& program)
- : device{device}, scheduler{scheduler}, fixed_state{key.fixed_state}, hash{key.Hash()},
+ : device{device}, scheduler{scheduler}, cache_key{key}, hash{cache_key.Hash()},
descriptor_set_layout{CreateDescriptorSetLayout(bindings)},
descriptor_allocator{descriptor_pool, *descriptor_set_layout},
update_descriptor_queue{update_descriptor_queue}, layout{CreatePipelineLayout()},
descriptor_template{CreateDescriptorUpdateTemplate(program)}, modules{CreateShaderModules(
program)},
- renderpass{renderpass_cache.GetRenderPass(key.renderpass_params)}, pipeline{CreatePipeline(
- key.renderpass_params,
- program)} {}
+ renderpass{renderpass_cache.GetRenderPass(cache_key.renderpass_params)},
+ pipeline{CreatePipeline(cache_key.renderpass_params, program)} {}
VKGraphicsPipeline::~VKGraphicsPipeline() = default;
@@ -181,7 +180,7 @@ std::vector<vk::ShaderModule> VKGraphicsPipeline::CreateShaderModules(
vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpass_params,
const SPIRVProgram& program) const {
- const auto& state = fixed_state;
+ const auto& state = cache_key.fixed_state;
const auto& viewport_swizzles = state.viewport_swizzles;
FixedPipelineState::DynamicState dynamic;
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
index a1d699a6c..58aa35efd 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
@@ -19,7 +19,27 @@ namespace Vulkan {
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
-struct GraphicsPipelineCacheKey;
+struct GraphicsPipelineCacheKey {
+ RenderPassParams renderpass_params;
+ u32 padding;
+ std::array<GPUVAddr, Maxwell::MaxShaderProgram> shaders;
+ FixedPipelineState fixed_state;
+
+ std::size_t Hash() const noexcept;
+
+ bool operator==(const GraphicsPipelineCacheKey& rhs) const noexcept;
+
+ bool operator!=(const GraphicsPipelineCacheKey& rhs) const noexcept {
+ return !operator==(rhs);
+ }
+
+ std::size_t Size() const noexcept {
+ return sizeof(renderpass_params) + sizeof(padding) + sizeof(shaders) + fixed_state.Size();
+ }
+};
+static_assert(std::has_unique_object_representations_v<GraphicsPipelineCacheKey>);
+static_assert(std::is_trivially_copyable_v<GraphicsPipelineCacheKey>);
+static_assert(std::is_trivially_constructible_v<GraphicsPipelineCacheKey>);
class VKDescriptorPool;
class VKDevice;
@@ -54,6 +74,10 @@ public:
return renderpass;
}
+ GraphicsPipelineCacheKey GetCacheKey() const {
+ return cache_key;
+ }
+
private:
vk::DescriptorSetLayout CreateDescriptorSetLayout(
vk::Span<VkDescriptorSetLayoutBinding> bindings) const;
@@ -70,7 +94,7 @@ private:
const VKDevice& device;
VKScheduler& scheduler;
- const FixedPipelineState fixed_state;
+ const GraphicsPipelineCacheKey cache_key;
const u64 hash;
vk::DescriptorSetLayout descriptor_set_layout;
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 42b3a744c..5c038f4bc 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -28,6 +28,7 @@
#include "video_core/shader/compiler_settings.h"
#include "video_core/shader/memory_util.h"
#include "video_core/shader_cache.h"
+#include "video_core/shader_notify.h"
namespace Vulkan {
@@ -134,64 +135,56 @@ bool ComputePipelineCacheKey::operator==(const ComputePipelineCacheKey& rhs) con
return std::memcmp(&rhs, this, sizeof *this) == 0;
}
-Shader::Shader(Core::System& system, Tegra::Engines::ShaderType stage, GPUVAddr gpu_addr,
- VideoCommon::Shader::ProgramCode program_code, u32 main_offset)
- : gpu_addr{gpu_addr}, program_code{std::move(program_code)},
- registry{stage, GetEngine(system, stage)}, shader_ir{this->program_code, main_offset,
- compiler_settings, registry},
- entries{GenerateShaderEntries(shader_ir)} {}
+Shader::Shader(Tegra::Engines::ConstBufferEngineInterface& engine, Tegra::Engines::ShaderType stage,
+ GPUVAddr gpu_addr_, VAddr cpu_addr, VideoCommon::Shader::ProgramCode program_code_,
+ u32 main_offset)
+ : gpu_addr(gpu_addr_), program_code(std::move(program_code_)), registry(stage, engine),
+ shader_ir(program_code, main_offset, compiler_settings, registry),
+ entries(GenerateShaderEntries(shader_ir)) {}
Shader::~Shader() = default;
-Tegra::Engines::ConstBufferEngineInterface& Shader::GetEngine(Core::System& system,
- Tegra::Engines::ShaderType stage) {
- if (stage == ShaderType::Compute) {
- return system.GPU().KeplerCompute();
- } else {
- return system.GPU().Maxwell3D();
- }
-}
-
-VKPipelineCache::VKPipelineCache(Core::System& system, RasterizerVulkan& rasterizer,
- const VKDevice& device, VKScheduler& scheduler,
- VKDescriptorPool& descriptor_pool,
- VKUpdateDescriptorQueue& update_descriptor_queue,
- VKRenderPassCache& renderpass_cache)
- : VideoCommon::ShaderCache<Shader>{rasterizer}, system{system}, device{device},
- scheduler{scheduler}, descriptor_pool{descriptor_pool},
- update_descriptor_queue{update_descriptor_queue}, renderpass_cache{renderpass_cache} {}
+VKPipelineCache::VKPipelineCache(RasterizerVulkan& rasterizer, Tegra::GPU& gpu_,
+ Tegra::Engines::Maxwell3D& maxwell3d_,
+ Tegra::Engines::KeplerCompute& kepler_compute_,
+ Tegra::MemoryManager& gpu_memory_, const VKDevice& device_,
+ VKScheduler& scheduler_, VKDescriptorPool& descriptor_pool_,
+ VKUpdateDescriptorQueue& update_descriptor_queue_,
+ VKRenderPassCache& renderpass_cache_)
+ : VideoCommon::ShaderCache<Shader>{rasterizer}, gpu{gpu_}, maxwell3d{maxwell3d_},
+ kepler_compute{kepler_compute_}, gpu_memory{gpu_memory_}, device{device_},
+ scheduler{scheduler_}, descriptor_pool{descriptor_pool_},
+ update_descriptor_queue{update_descriptor_queue_}, renderpass_cache{renderpass_cache_} {}
VKPipelineCache::~VKPipelineCache() = default;
std::array<Shader*, Maxwell::MaxShaderProgram> VKPipelineCache::GetShaders() {
- const auto& gpu = system.GPU().Maxwell3D();
-
std::array<Shader*, Maxwell::MaxShaderProgram> shaders{};
+
for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
const auto program{static_cast<Maxwell::ShaderProgram>(index)};
// Skip stages that are not enabled
- if (!gpu.regs.IsShaderConfigEnabled(index)) {
+ if (!maxwell3d.regs.IsShaderConfigEnabled(index)) {
continue;
}
- auto& memory_manager{system.GPU().MemoryManager()};
- const GPUVAddr program_addr{GetShaderAddress(system, program)};
- const std::optional cpu_addr = memory_manager.GpuToCpuAddress(program_addr);
+ const GPUVAddr gpu_addr{GetShaderAddress(maxwell3d, program)};
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
ASSERT(cpu_addr);
Shader* result = cpu_addr ? TryGet(*cpu_addr) : null_shader.get();
if (!result) {
- const auto host_ptr{memory_manager.GetPointer(program_addr)};
+ const u8* const host_ptr{gpu_memory.GetPointer(gpu_addr)};
// No shader found - create a new one
- constexpr u32 stage_offset = STAGE_MAIN_OFFSET;
+ static constexpr u32 stage_offset = STAGE_MAIN_OFFSET;
const auto stage = static_cast<ShaderType>(index == 0 ? 0 : index - 1);
- ProgramCode code = GetShaderCode(memory_manager, program_addr, host_ptr, false);
+ ProgramCode code = GetShaderCode(gpu_memory, gpu_addr, host_ptr, false);
const std::size_t size_in_bytes = code.size() * sizeof(u64);
- auto shader = std::make_unique<Shader>(system, stage, program_addr, std::move(code),
- stage_offset);
+ auto shader = std::make_unique<Shader>(maxwell3d, stage, gpu_addr, *cpu_addr,
+ std::move(code), stage_offset);
result = shader.get();
if (cpu_addr) {
@@ -205,24 +198,43 @@ std::array<Shader*, Maxwell::MaxShaderProgram> VKPipelineCache::GetShaders() {
return last_shaders = shaders;
}
-VKGraphicsPipeline& VKPipelineCache::GetGraphicsPipeline(const GraphicsPipelineCacheKey& key) {
+VKGraphicsPipeline* VKPipelineCache::GetGraphicsPipeline(
+ const GraphicsPipelineCacheKey& key, VideoCommon::Shader::AsyncShaders& async_shaders) {
MICROPROFILE_SCOPE(Vulkan_PipelineCache);
if (last_graphics_pipeline && last_graphics_key == key) {
- return *last_graphics_pipeline;
+ return last_graphics_pipeline;
}
last_graphics_key = key;
+ if (device.UseAsynchronousShaders() && async_shaders.IsShaderAsync(gpu)) {
+ std::unique_lock lock{pipeline_cache};
+ const auto [pair, is_cache_miss] = graphics_cache.try_emplace(key);
+ if (is_cache_miss) {
+ gpu.ShaderNotify().MarkSharderBuilding();
+ LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash());
+ const auto [program, bindings] = DecompileShaders(key.fixed_state);
+ async_shaders.QueueVulkanShader(this, device, scheduler, descriptor_pool,
+ update_descriptor_queue, renderpass_cache, bindings,
+ program, key);
+ }
+ last_graphics_pipeline = pair->second.get();
+ return last_graphics_pipeline;
+ }
+
const auto [pair, is_cache_miss] = graphics_cache.try_emplace(key);
auto& entry = pair->second;
if (is_cache_miss) {
+ gpu.ShaderNotify().MarkSharderBuilding();
LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash());
- const auto [program, bindings] = DecompileShaders(key);
+ const auto [program, bindings] = DecompileShaders(key.fixed_state);
entry = std::make_unique<VKGraphicsPipeline>(device, scheduler, descriptor_pool,
update_descriptor_queue, renderpass_cache, key,
bindings, program);
+ gpu.ShaderNotify().MarkShaderComplete();
}
- return *(last_graphics_pipeline = entry.get());
+ last_graphics_pipeline = entry.get();
+ return last_graphics_pipeline;
}
VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCacheKey& key) {
@@ -235,22 +247,21 @@ VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCach
}
LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash());
- auto& memory_manager = system.GPU().MemoryManager();
- const auto program_addr = key.shader;
+ const GPUVAddr gpu_addr = key.shader;
- const auto cpu_addr = memory_manager.GpuToCpuAddress(program_addr);
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
ASSERT(cpu_addr);
Shader* shader = cpu_addr ? TryGet(*cpu_addr) : null_kernel.get();
if (!shader) {
// No shader found - create a new one
- const auto host_ptr = memory_manager.GetPointer(program_addr);
+ const auto host_ptr = gpu_memory.GetPointer(gpu_addr);
- ProgramCode code = GetShaderCode(memory_manager, program_addr, host_ptr, true);
+ ProgramCode code = GetShaderCode(gpu_memory, gpu_addr, host_ptr, true);
const std::size_t size_in_bytes = code.size() * sizeof(u64);
- auto shader_info = std::make_unique<Shader>(system, ShaderType::Compute, program_addr,
- std::move(code), KERNEL_MAIN_OFFSET);
+ auto shader_info = std::make_unique<Shader>(kepler_compute, ShaderType::Compute, gpu_addr,
+ *cpu_addr, std::move(code), KERNEL_MAIN_OFFSET);
shader = shader_info.get();
if (cpu_addr) {
@@ -261,8 +272,13 @@ VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCach
}
const Specialization specialization{
+ .base_binding = 0,
.workgroup_size = key.workgroup_size,
.shared_memory_size = key.shared_memory_size,
+ .point_size = std::nullopt,
+ .enabled_attributes = {},
+ .attribute_types = {},
+ .ndc_minus_one_to_one = false,
};
const SPIRVShader spirv_shader{Decompile(device, shader->GetIR(), ShaderType::Compute,
shader->GetRegistry(), specialization),
@@ -272,6 +288,12 @@ VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCach
return *entry;
}
+void VKPipelineCache::EmplacePipeline(std::unique_ptr<VKGraphicsPipeline> pipeline) {
+ gpu.ShaderNotify().MarkShaderComplete();
+ std::unique_lock lock{pipeline_cache};
+ graphics_cache.at(pipeline->GetCacheKey()) = std::move(pipeline);
+}
+
void VKPipelineCache::OnShaderRemoval(Shader* shader) {
bool finished = false;
const auto Finish = [&] {
@@ -307,11 +329,7 @@ void VKPipelineCache::OnShaderRemoval(Shader* shader) {
}
std::pair<SPIRVProgram, std::vector<VkDescriptorSetLayoutBinding>>
-VKPipelineCache::DecompileShaders(const GraphicsPipelineCacheKey& key) {
- const auto& fixed_state = key.fixed_state;
- auto& memory_manager = system.GPU().MemoryManager();
- const auto& gpu = system.GPU().Maxwell3D();
-
+VKPipelineCache::DecompileShaders(const FixedPipelineState& fixed_state) {
Specialization specialization;
if (fixed_state.dynamic_state.Topology() == Maxwell::PrimitiveTopology::Points ||
device.IsExtExtendedDynamicStateSupported()) {
@@ -334,12 +352,12 @@ VKPipelineCache::DecompileShaders(const GraphicsPipelineCacheKey& key) {
const auto program_enum = static_cast<Maxwell::ShaderProgram>(index);
// Skip stages that are not enabled
- if (!gpu.regs.IsShaderConfigEnabled(index)) {
+ if (!maxwell3d.regs.IsShaderConfigEnabled(index)) {
continue;
}
- const GPUVAddr gpu_addr = GetShaderAddress(system, program_enum);
- const std::optional<VAddr> cpu_addr = memory_manager.GpuToCpuAddress(gpu_addr);
+ const GPUVAddr gpu_addr = GetShaderAddress(maxwell3d, program_enum);
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
Shader* const shader = cpu_addr ? TryGet(*cpu_addr) : null_shader.get();
const std::size_t stage = index == 0 ? 0 : index - 1; // Stage indices are 0 - 5
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h
index 0a3fe65fb..1a31fd9f6 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h
@@ -22,6 +22,7 @@
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
#include "video_core/renderer_vulkan/vk_shader_decompiler.h"
#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/shader/async_shaders.h"
#include "video_core/shader/memory_util.h"
#include "video_core/shader/registry.h"
#include "video_core/shader/shader_ir.h"
@@ -43,28 +44,6 @@ class VKUpdateDescriptorQueue;
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
-struct GraphicsPipelineCacheKey {
- RenderPassParams renderpass_params;
- u32 padding;
- std::array<GPUVAddr, Maxwell::MaxShaderProgram> shaders;
- FixedPipelineState fixed_state;
-
- std::size_t Hash() const noexcept;
-
- bool operator==(const GraphicsPipelineCacheKey& rhs) const noexcept;
-
- bool operator!=(const GraphicsPipelineCacheKey& rhs) const noexcept {
- return !operator==(rhs);
- }
-
- std::size_t Size() const noexcept {
- return sizeof(renderpass_params) + sizeof(padding) + sizeof(shaders) + fixed_state.Size();
- }
-};
-static_assert(std::has_unique_object_representations_v<GraphicsPipelineCacheKey>);
-static_assert(std::is_trivially_copyable_v<GraphicsPipelineCacheKey>);
-static_assert(std::is_trivially_constructible_v<GraphicsPipelineCacheKey>);
-
struct ComputePipelineCacheKey {
GPUVAddr shader;
u32 shared_memory_size;
@@ -106,7 +85,8 @@ namespace Vulkan {
class Shader {
public:
- explicit Shader(Core::System& system, Tegra::Engines::ShaderType stage, GPUVAddr gpu_addr,
+ explicit Shader(Tegra::Engines::ConstBufferEngineInterface& engine,
+ Tegra::Engines::ShaderType stage, GPUVAddr gpu_addr, VAddr cpu_addr,
VideoCommon::Shader::ProgramCode program_code, u32 main_offset);
~Shader();
@@ -118,22 +98,19 @@ public:
return shader_ir;
}
- const VideoCommon::Shader::Registry& GetRegistry() const {
- return registry;
- }
-
const VideoCommon::Shader::ShaderIR& GetIR() const {
return shader_ir;
}
+ const VideoCommon::Shader::Registry& GetRegistry() const {
+ return registry;
+ }
+
const ShaderEntries& GetEntries() const {
return entries;
}
private:
- static Tegra::Engines::ConstBufferEngineInterface& GetEngine(Core::System& system,
- Tegra::Engines::ShaderType stage);
-
GPUVAddr gpu_addr{};
VideoCommon::Shader::ProgramCode program_code;
VideoCommon::Shader::Registry registry;
@@ -143,27 +120,36 @@ private:
class VKPipelineCache final : public VideoCommon::ShaderCache<Shader> {
public:
- explicit VKPipelineCache(Core::System& system, RasterizerVulkan& rasterizer,
- const VKDevice& device, VKScheduler& scheduler,
- VKDescriptorPool& descriptor_pool,
+ explicit VKPipelineCache(RasterizerVulkan& rasterizer, Tegra::GPU& gpu,
+ Tegra::Engines::Maxwell3D& maxwell3d,
+ Tegra::Engines::KeplerCompute& kepler_compute,
+ Tegra::MemoryManager& gpu_memory, const VKDevice& device,
+ VKScheduler& scheduler, VKDescriptorPool& descriptor_pool,
VKUpdateDescriptorQueue& update_descriptor_queue,
VKRenderPassCache& renderpass_cache);
~VKPipelineCache() override;
std::array<Shader*, Maxwell::MaxShaderProgram> GetShaders();
- VKGraphicsPipeline& GetGraphicsPipeline(const GraphicsPipelineCacheKey& key);
+ VKGraphicsPipeline* GetGraphicsPipeline(const GraphicsPipelineCacheKey& key,
+ VideoCommon::Shader::AsyncShaders& async_shaders);
VKComputePipeline& GetComputePipeline(const ComputePipelineCacheKey& key);
+ void EmplacePipeline(std::unique_ptr<VKGraphicsPipeline> pipeline);
+
protected:
void OnShaderRemoval(Shader* shader) final;
private:
std::pair<SPIRVProgram, std::vector<VkDescriptorSetLayoutBinding>> DecompileShaders(
- const GraphicsPipelineCacheKey& key);
+ const FixedPipelineState& fixed_state);
+
+ Tegra::GPU& gpu;
+ Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::Engines::KeplerCompute& kepler_compute;
+ Tegra::MemoryManager& gpu_memory;
- Core::System& system;
const VKDevice& device;
VKScheduler& scheduler;
VKDescriptorPool& descriptor_pool;
@@ -178,6 +164,7 @@ private:
GraphicsPipelineCacheKey last_graphics_key;
VKGraphicsPipeline* last_graphics_pipeline = nullptr;
+ std::mutex pipeline_cache;
std::unordered_map<GraphicsPipelineCacheKey, std::unique_ptr<VKGraphicsPipeline>>
graphics_cache;
std::unordered_map<ComputePipelineCacheKey, std::unique_ptr<VKComputePipeline>> compute_cache;
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp
index 6cd63d090..5a97c959d 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp
@@ -68,10 +68,11 @@ void QueryPool::Reserve(std::pair<VkQueryPool, u32> query) {
usage[pool_index * GROW_STEP + static_cast<std::ptrdiff_t>(query.second)] = false;
}
-VKQueryCache::VKQueryCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
+VKQueryCache::VKQueryCache(VideoCore::RasterizerInterface& rasterizer,
+ Tegra::Engines::Maxwell3D& maxwell3d, Tegra::MemoryManager& gpu_memory,
const VKDevice& device, VKScheduler& scheduler)
: VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter,
- QueryPool>{system, rasterizer},
+ QueryPool>{rasterizer, maxwell3d, gpu_memory},
device{device}, scheduler{scheduler} {
for (std::size_t i = 0; i < static_cast<std::size_t>(VideoCore::NumQueryTypes); ++i) {
query_pools[i].Initialize(device, static_cast<VideoCore::QueryType>(i));
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.h b/src/video_core/renderer_vulkan/vk_query_cache.h
index 40119e6d3..9be996e55 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.h
+++ b/src/video_core/renderer_vulkan/vk_query_cache.h
@@ -56,7 +56,8 @@ class VKQueryCache final
: public VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter,
QueryPool> {
public:
- explicit VKQueryCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
+ explicit VKQueryCache(VideoCore::RasterizerInterface& rasterizer,
+ Tegra::Engines::Maxwell3D& maxwell3d, Tegra::MemoryManager& gpu_memory,
const VKDevice& device, VKScheduler& scheduler);
~VKQueryCache();
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 31e44aa2b..bafebe294 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -14,6 +14,7 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
+#include "common/scope_exit.h"
#include "core/core.h"
#include "core/settings.h"
#include "video_core/engines/kepler_compute.h"
@@ -380,28 +381,34 @@ void RasterizerVulkan::DrawParameters::Draw(vk::CommandBuffer cmdbuf) const {
}
}
-RasterizerVulkan::RasterizerVulkan(Core::System& system, Core::Frontend::EmuWindow& renderer,
- VKScreenInfo& screen_info, const VKDevice& device,
- VKResourceManager& resource_manager,
- VKMemoryManager& memory_manager, StateTracker& state_tracker,
- VKScheduler& scheduler)
- : RasterizerAccelerated{system.Memory()}, system{system}, render_window{renderer},
- screen_info{screen_info}, device{device}, resource_manager{resource_manager},
- memory_manager{memory_manager}, state_tracker{state_tracker}, scheduler{scheduler},
+RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu_,
+ Tegra::MemoryManager& gpu_memory_,
+ Core::Memory::Memory& cpu_memory, VKScreenInfo& screen_info_,
+ const VKDevice& device_, VKResourceManager& resource_manager_,
+ VKMemoryManager& memory_manager_, StateTracker& state_tracker_,
+ VKScheduler& scheduler_)
+ : RasterizerAccelerated(cpu_memory), gpu(gpu_), gpu_memory(gpu_memory_),
+ maxwell3d(gpu.Maxwell3D()), kepler_compute(gpu.KeplerCompute()), screen_info(screen_info_),
+ device(device_), resource_manager(resource_manager_), memory_manager(memory_manager_),
+ state_tracker(state_tracker_), scheduler(scheduler_),
staging_pool(device, memory_manager, scheduler), descriptor_pool(device),
update_descriptor_queue(device, scheduler), renderpass_cache(device),
quad_array_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue),
quad_indexed_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue),
uint8_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue),
- texture_cache(system, *this, device, resource_manager, memory_manager, scheduler,
- staging_pool),
- pipeline_cache(system, *this, device, scheduler, descriptor_pool, update_descriptor_queue,
- renderpass_cache),
- buffer_cache(*this, system, device, memory_manager, scheduler, staging_pool),
- sampler_cache(device),
- fence_manager(system, *this, device, scheduler, texture_cache, buffer_cache, query_cache),
- query_cache(system, *this, device, scheduler), wfi_event{device.GetLogical().CreateEvent()} {
+ texture_cache(*this, maxwell3d, gpu_memory, device, resource_manager, memory_manager,
+ scheduler, staging_pool),
+ pipeline_cache(*this, gpu, maxwell3d, kepler_compute, gpu_memory, device, scheduler,
+ descriptor_pool, update_descriptor_queue, renderpass_cache),
+ buffer_cache(*this, gpu_memory, cpu_memory, device, memory_manager, scheduler, staging_pool),
+ sampler_cache(device), query_cache(*this, maxwell3d, gpu_memory, device, scheduler),
+ fence_manager(*this, gpu, gpu_memory, texture_cache, buffer_cache, query_cache, device,
+ scheduler),
+ wfi_event(device.GetLogical().CreateNewEvent()), async_shaders(emu_window) {
scheduler.SetQueryCache(query_cache);
+ if (device.UseAsynchronousShaders()) {
+ async_shaders.AllocateWorkers();
+ }
}
RasterizerVulkan::~RasterizerVulkan() = default;
@@ -409,13 +416,13 @@ RasterizerVulkan::~RasterizerVulkan() = default;
void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
MICROPROFILE_SCOPE(Vulkan_Drawing);
+ SCOPE_EXIT({ gpu.TickWork(); });
FlushWork();
query_cache.UpdateCounters();
- const auto& gpu = system.GPU().Maxwell3D();
GraphicsPipelineCacheKey key;
- key.fixed_state.Fill(gpu.regs, device.IsExtExtendedDynamicStateSupported());
+ key.fixed_state.Fill(maxwell3d.regs, device.IsExtExtendedDynamicStateSupported());
buffer_cache.Map(CalculateGraphicsStreamBufferSize(is_indexed));
@@ -439,10 +446,15 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
key.renderpass_params = GetRenderPassParams(texceptions);
key.padding = 0;
- auto& pipeline = pipeline_cache.GetGraphicsPipeline(key);
- scheduler.BindGraphicsPipeline(pipeline.GetHandle());
+ auto* pipeline = pipeline_cache.GetGraphicsPipeline(key, async_shaders);
+ if (pipeline == nullptr || pipeline->GetHandle() == VK_NULL_HANDLE) {
+ // Async graphics pipeline was not ready.
+ return;
+ }
+
+ scheduler.BindGraphicsPipeline(pipeline->GetHandle());
- const auto renderpass = pipeline.GetRenderPass();
+ const auto renderpass = pipeline->GetRenderPass();
const auto [framebuffer, render_area] = ConfigureFramebuffers(renderpass);
scheduler.RequestRenderpass(renderpass, framebuffer, render_area);
@@ -452,8 +464,8 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
BeginTransformFeedback();
- const auto pipeline_layout = pipeline.GetLayout();
- const auto descriptor_set = pipeline.CommitDescriptorSet();
+ const auto pipeline_layout = pipeline->GetLayout();
+ const auto descriptor_set = pipeline->CommitDescriptorSet();
scheduler.Record([pipeline_layout, descriptor_set, draw_params](vk::CommandBuffer cmdbuf) {
if (descriptor_set) {
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout,
@@ -463,15 +475,12 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
});
EndTransformFeedback();
-
- system.GPU().TickWork();
}
void RasterizerVulkan::Clear() {
MICROPROFILE_SCOPE(Vulkan_Clearing);
- const auto& gpu = system.GPU().Maxwell3D();
- if (!system.GPU().Maxwell3D().ShouldExecute()) {
+ if (!maxwell3d.ShouldExecute()) {
return;
}
@@ -480,7 +489,7 @@ void RasterizerVulkan::Clear() {
query_cache.UpdateCounters();
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
const bool use_color = regs.clear_buffers.R || regs.clear_buffers.G || regs.clear_buffers.B ||
regs.clear_buffers.A;
const bool use_depth = regs.clear_buffers.Z;
@@ -532,10 +541,6 @@ void RasterizerVulkan::Clear() {
scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil,
clear_rect, aspect_flags](vk::CommandBuffer cmdbuf) {
- VkClearValue clear_value;
- clear_value.depthStencil.depth = clear_depth;
- clear_value.depthStencil.stencil = clear_stencil;
-
VkClearAttachment attachment;
attachment.aspectMask = aspect_flags;
attachment.colorAttachment = 0;
@@ -553,7 +558,7 @@ void RasterizerVulkan::DispatchCompute(GPUVAddr code_addr) {
query_cache.UpdateCounters();
- const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
+ const auto& launch_desc = kepler_compute.launch_description;
auto& pipeline = pipeline_cache.GetComputePipeline({
.shader = code_addr,
.shared_memory_size = launch_desc.shared_alloc,
@@ -649,16 +654,14 @@ void RasterizerVulkan::SyncGuestHost() {
}
void RasterizerVulkan::SignalSemaphore(GPUVAddr addr, u32 value) {
- auto& gpu{system.GPU()};
if (!gpu.IsAsync()) {
- gpu.MemoryManager().Write<u32>(addr, value);
+ gpu_memory.Write<u32>(addr, value);
return;
}
fence_manager.SignalSemaphore(addr, value);
}
void RasterizerVulkan::SignalSyncPoint(u32 value) {
- auto& gpu{system.GPU()};
if (!gpu.IsAsync()) {
gpu.IncrementSyncPoint(value);
return;
@@ -667,7 +670,6 @@ void RasterizerVulkan::SignalSyncPoint(u32 value) {
}
void RasterizerVulkan::ReleaseFences() {
- auto& gpu{system.GPU()};
if (!gpu.IsAsync()) {
return;
}
@@ -745,10 +747,6 @@ bool RasterizerVulkan::AccelerateDisplay(const Tegra::FramebufferConfig& config,
return true;
}
-void RasterizerVulkan::SetupDirtyFlags() {
- state_tracker.Initialize();
-}
-
void RasterizerVulkan::FlushWork() {
static constexpr u32 DRAWS_TO_DISPATCH = 4096;
@@ -772,10 +770,9 @@ void RasterizerVulkan::FlushWork() {
RasterizerVulkan::Texceptions RasterizerVulkan::UpdateAttachments(bool is_clear) {
MICROPROFILE_SCOPE(Vulkan_RenderTargets);
- auto& maxwell3d = system.GPU().Maxwell3D();
- auto& dirty = maxwell3d.dirty.flags;
- auto& regs = maxwell3d.regs;
+ const auto& regs = maxwell3d.regs;
+ auto& dirty = maxwell3d.dirty.flags;
const bool update_rendertargets = dirty[VideoCommon::Dirty::RenderTargets];
dirty[VideoCommon::Dirty::RenderTargets] = false;
@@ -819,8 +816,13 @@ bool RasterizerVulkan::WalkAttachmentOverlaps(const CachedSurfaceView& attachmen
std::tuple<VkFramebuffer, VkExtent2D> RasterizerVulkan::ConfigureFramebuffers(
VkRenderPass renderpass) {
- FramebufferCacheKey key{renderpass, std::numeric_limits<u32>::max(),
- std::numeric_limits<u32>::max(), std::numeric_limits<u32>::max()};
+ FramebufferCacheKey key{
+ .renderpass = renderpass,
+ .width = std::numeric_limits<u32>::max(),
+ .height = std::numeric_limits<u32>::max(),
+ .layers = std::numeric_limits<u32>::max(),
+ .views = {},
+ };
const auto try_push = [&key](const View& view) {
if (!view) {
@@ -833,7 +835,7 @@ std::tuple<VkFramebuffer, VkExtent2D> RasterizerVulkan::ConfigureFramebuffers(
return true;
};
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
const std::size_t num_attachments = static_cast<std::size_t>(regs.rt_control.count);
for (std::size_t index = 0; index < num_attachments; ++index) {
if (try_push(color_attachments[index])) {
@@ -869,13 +871,12 @@ RasterizerVulkan::DrawParameters RasterizerVulkan::SetupGeometry(FixedPipelineSt
bool is_instanced) {
MICROPROFILE_SCOPE(Vulkan_Geometry);
- const auto& gpu = system.GPU().Maxwell3D();
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
SetupVertexArrays(buffer_bindings);
const u32 base_instance = regs.vb_base_instance;
- const u32 num_instances = is_instanced ? gpu.mme_draw.instance_count : 1;
+ const u32 num_instances = is_instanced ? maxwell3d.mme_draw.instance_count : 1;
const u32 base_vertex = is_indexed ? regs.vb_element_base : regs.vertex_buffer.first;
const u32 num_vertices = is_indexed ? regs.index_array.count : regs.vertex_buffer.count;
@@ -936,7 +937,7 @@ void RasterizerVulkan::SetupImageTransitions(
}
void RasterizerVulkan::UpdateDynamicStates() {
- auto& regs = system.GPU().Maxwell3D().regs;
+ auto& regs = maxwell3d.regs;
UpdateViewportsState(regs);
UpdateScissorsState(regs);
UpdateDepthBias(regs);
@@ -957,7 +958,7 @@ void RasterizerVulkan::UpdateDynamicStates() {
}
void RasterizerVulkan::BeginTransformFeedback() {
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
if (regs.tfb_enabled == 0) {
return;
}
@@ -989,7 +990,7 @@ void RasterizerVulkan::BeginTransformFeedback() {
}
void RasterizerVulkan::EndTransformFeedback() {
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
if (regs.tfb_enabled == 0) {
return;
}
@@ -1002,7 +1003,7 @@ void RasterizerVulkan::EndTransformFeedback() {
}
void RasterizerVulkan::SetupVertexArrays(BufferBindings& buffer_bindings) {
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
const auto& vertex_array = regs.vertex_array[index];
@@ -1028,7 +1029,7 @@ void RasterizerVulkan::SetupIndexBuffer(BufferBindings& buffer_bindings, DrawPar
if (params.num_vertices == 0) {
return;
}
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
switch (regs.draw.topology) {
case Maxwell::PrimitiveTopology::Quads: {
if (!params.is_indexed) {
@@ -1076,8 +1077,7 @@ void RasterizerVulkan::SetupIndexBuffer(BufferBindings& buffer_bindings, DrawPar
void RasterizerVulkan::SetupGraphicsConstBuffers(const ShaderEntries& entries, std::size_t stage) {
MICROPROFILE_SCOPE(Vulkan_ConstBuffers);
- const auto& gpu = system.GPU().Maxwell3D();
- const auto& shader_stage = gpu.state.shader_stages[stage];
+ const auto& shader_stage = maxwell3d.state.shader_stages[stage];
for (const auto& entry : entries.const_buffers) {
SetupConstBuffer(entry, shader_stage.const_buffers[entry.GetIndex()]);
}
@@ -1085,8 +1085,7 @@ void RasterizerVulkan::SetupGraphicsConstBuffers(const ShaderEntries& entries, s
void RasterizerVulkan::SetupGraphicsGlobalBuffers(const ShaderEntries& entries, std::size_t stage) {
MICROPROFILE_SCOPE(Vulkan_GlobalBuffers);
- auto& gpu{system.GPU()};
- const auto cbufs{gpu.Maxwell3D().state.shader_stages[stage]};
+ const auto& cbufs{maxwell3d.state.shader_stages[stage]};
for (const auto& entry : entries.global_buffers) {
const auto addr = cbufs.const_buffers[entry.GetCbufIndex()].address + entry.GetCbufOffset();
@@ -1096,19 +1095,17 @@ void RasterizerVulkan::SetupGraphicsGlobalBuffers(const ShaderEntries& entries,
void RasterizerVulkan::SetupGraphicsUniformTexels(const ShaderEntries& entries, std::size_t stage) {
MICROPROFILE_SCOPE(Vulkan_Textures);
- const auto& gpu = system.GPU().Maxwell3D();
for (const auto& entry : entries.uniform_texels) {
- const auto image = GetTextureInfo(gpu, entry, stage).tic;
+ const auto image = GetTextureInfo(maxwell3d, entry, stage).tic;
SetupUniformTexels(image, entry);
}
}
void RasterizerVulkan::SetupGraphicsTextures(const ShaderEntries& entries, std::size_t stage) {
MICROPROFILE_SCOPE(Vulkan_Textures);
- const auto& gpu = system.GPU().Maxwell3D();
for (const auto& entry : entries.samplers) {
for (std::size_t i = 0; i < entry.size; ++i) {
- const auto texture = GetTextureInfo(gpu, entry, stage, i);
+ const auto texture = GetTextureInfo(maxwell3d, entry, stage, i);
SetupTexture(texture, entry);
}
}
@@ -1116,25 +1113,23 @@ void RasterizerVulkan::SetupGraphicsTextures(const ShaderEntries& entries, std::
void RasterizerVulkan::SetupGraphicsStorageTexels(const ShaderEntries& entries, std::size_t stage) {
MICROPROFILE_SCOPE(Vulkan_Textures);
- const auto& gpu = system.GPU().Maxwell3D();
for (const auto& entry : entries.storage_texels) {
- const auto image = GetTextureInfo(gpu, entry, stage).tic;
+ const auto image = GetTextureInfo(maxwell3d, entry, stage).tic;
SetupStorageTexel(image, entry);
}
}
void RasterizerVulkan::SetupGraphicsImages(const ShaderEntries& entries, std::size_t stage) {
MICROPROFILE_SCOPE(Vulkan_Images);
- const auto& gpu = system.GPU().Maxwell3D();
for (const auto& entry : entries.images) {
- const auto tic = GetTextureInfo(gpu, entry, stage).tic;
+ const auto tic = GetTextureInfo(maxwell3d, entry, stage).tic;
SetupImage(tic, entry);
}
}
void RasterizerVulkan::SetupComputeConstBuffers(const ShaderEntries& entries) {
MICROPROFILE_SCOPE(Vulkan_ConstBuffers);
- const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
+ const auto& launch_desc = kepler_compute.launch_description;
for (const auto& entry : entries.const_buffers) {
const auto& config = launch_desc.const_buffer_config[entry.GetIndex()];
const std::bitset<8> mask = launch_desc.const_buffer_enable_mask.Value();
@@ -1148,7 +1143,7 @@ void RasterizerVulkan::SetupComputeConstBuffers(const ShaderEntries& entries) {
void RasterizerVulkan::SetupComputeGlobalBuffers(const ShaderEntries& entries) {
MICROPROFILE_SCOPE(Vulkan_GlobalBuffers);
- const auto cbufs{system.GPU().KeplerCompute().launch_description.const_buffer_config};
+ const auto& cbufs{kepler_compute.launch_description.const_buffer_config};
for (const auto& entry : entries.global_buffers) {
const auto addr{cbufs[entry.GetCbufIndex()].Address() + entry.GetCbufOffset()};
SetupGlobalBuffer(entry, addr);
@@ -1157,19 +1152,17 @@ void RasterizerVulkan::SetupComputeGlobalBuffers(const ShaderEntries& entries) {
void RasterizerVulkan::SetupComputeUniformTexels(const ShaderEntries& entries) {
MICROPROFILE_SCOPE(Vulkan_Textures);
- const auto& gpu = system.GPU().KeplerCompute();
for (const auto& entry : entries.uniform_texels) {
- const auto image = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic;
+ const auto image = GetTextureInfo(kepler_compute, entry, ComputeShaderIndex).tic;
SetupUniformTexels(image, entry);
}
}
void RasterizerVulkan::SetupComputeTextures(const ShaderEntries& entries) {
MICROPROFILE_SCOPE(Vulkan_Textures);
- const auto& gpu = system.GPU().KeplerCompute();
for (const auto& entry : entries.samplers) {
for (std::size_t i = 0; i < entry.size; ++i) {
- const auto texture = GetTextureInfo(gpu, entry, ComputeShaderIndex, i);
+ const auto texture = GetTextureInfo(kepler_compute, entry, ComputeShaderIndex, i);
SetupTexture(texture, entry);
}
}
@@ -1177,18 +1170,16 @@ void RasterizerVulkan::SetupComputeTextures(const ShaderEntries& entries) {
void RasterizerVulkan::SetupComputeStorageTexels(const ShaderEntries& entries) {
MICROPROFILE_SCOPE(Vulkan_Textures);
- const auto& gpu = system.GPU().KeplerCompute();
for (const auto& entry : entries.storage_texels) {
- const auto image = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic;
+ const auto image = GetTextureInfo(kepler_compute, entry, ComputeShaderIndex).tic;
SetupStorageTexel(image, entry);
}
}
void RasterizerVulkan::SetupComputeImages(const ShaderEntries& entries) {
MICROPROFILE_SCOPE(Vulkan_Images);
- const auto& gpu = system.GPU().KeplerCompute();
for (const auto& entry : entries.images) {
- const auto tic = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic;
+ const auto tic = GetTextureInfo(kepler_compute, entry, ComputeShaderIndex).tic;
SetupImage(tic, entry);
}
}
@@ -1212,9 +1203,8 @@ void RasterizerVulkan::SetupConstBuffer(const ConstBufferEntry& entry,
}
void RasterizerVulkan::SetupGlobalBuffer(const GlobalBufferEntry& entry, GPUVAddr address) {
- auto& memory_manager{system.GPU().MemoryManager()};
- const auto actual_addr = memory_manager.Read<u64>(address);
- const auto size = memory_manager.Read<u32>(address + 8);
+ const u64 actual_addr = gpu_memory.Read<u64>(address);
+ const u32 size = gpu_memory.Read<u32>(address + 8);
if (size == 0) {
// Sometimes global memory pointers don't have a proper size. Upload a dummy entry
@@ -1432,10 +1422,10 @@ void RasterizerVulkan::UpdateFrontFace(Tegra::Engines::Maxwell3D::Regs& regs) {
}
void RasterizerVulkan::UpdatePrimitiveTopology(Tegra::Engines::Maxwell3D::Regs& regs) {
- if (!state_tracker.TouchPrimitiveTopology()) {
+ const Maxwell::PrimitiveTopology primitive_topology = regs.draw.topology.Value();
+ if (!state_tracker.ChangePrimitiveTopology(primitive_topology)) {
return;
}
- const Maxwell::PrimitiveTopology primitive_topology = regs.draw.topology.Value();
scheduler.Record([this, primitive_topology](vk::CommandBuffer cmdbuf) {
cmdbuf.SetPrimitiveTopologyEXT(MaxwellToVK::PrimitiveTopology(device, primitive_topology));
});
@@ -1497,7 +1487,7 @@ std::size_t RasterizerVulkan::CalculateComputeStreamBufferSize() const {
}
std::size_t RasterizerVulkan::CalculateVertexArraysSize() const {
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
std::size_t size = 0;
for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) {
@@ -1512,9 +1502,8 @@ std::size_t RasterizerVulkan::CalculateVertexArraysSize() const {
}
std::size_t RasterizerVulkan::CalculateIndexBufferSize() const {
- const auto& regs = system.GPU().Maxwell3D().regs;
- return static_cast<std::size_t>(regs.index_array.count) *
- static_cast<std::size_t>(regs.index_array.FormatSizeInBytes());
+ return static_cast<std::size_t>(maxwell3d.regs.index_array.count) *
+ static_cast<std::size_t>(maxwell3d.regs.index_array.FormatSizeInBytes());
}
std::size_t RasterizerVulkan::CalculateConstBufferSize(
@@ -1529,7 +1518,7 @@ std::size_t RasterizerVulkan::CalculateConstBufferSize(
}
RenderPassParams RasterizerVulkan::GetRenderPassParams(Texceptions texceptions) const {
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
const std::size_t num_attachments = static_cast<std::size_t>(regs.rt_control.count);
RenderPassParams params;
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index 923178b0b..16251d0f6 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -32,6 +32,7 @@
#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/shader/async_shaders.h"
namespace Core {
class System;
@@ -105,7 +106,8 @@ struct ImageView {
class RasterizerVulkan final : public VideoCore::RasterizerAccelerated {
public:
- explicit RasterizerVulkan(Core::System& system, Core::Frontend::EmuWindow& render_window,
+ explicit RasterizerVulkan(Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu,
+ Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory,
VKScreenInfo& screen_info, const VKDevice& device,
VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
StateTracker& state_tracker, VKScheduler& scheduler);
@@ -134,7 +136,14 @@ public:
const Tegra::Engines::Fermi2D::Config& copy_config) override;
bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
u32 pixel_stride) override;
- void SetupDirtyFlags() override;
+
+ VideoCommon::Shader::AsyncShaders& GetAsyncShaders() {
+ return async_shaders;
+ }
+
+ const VideoCommon::Shader::AsyncShaders& GetAsyncShaders() const {
+ return async_shaders;
+ }
/// Maximum supported size that a constbuffer can have in bytes.
static constexpr std::size_t MaxConstbufferSize = 0x10000;
@@ -270,8 +279,11 @@ private:
VkBuffer DefaultBuffer();
- Core::System& system;
- Core::Frontend::EmuWindow& render_window;
+ Tegra::GPU& gpu;
+ Tegra::MemoryManager& gpu_memory;
+ Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::Engines::KeplerCompute& kepler_compute;
+
VKScreenInfo& screen_info;
const VKDevice& device;
VKResourceManager& resource_manager;
@@ -291,12 +303,13 @@ private:
VKPipelineCache pipeline_cache;
VKBufferCache buffer_cache;
VKSamplerCache sampler_cache;
- VKFenceManager fence_manager;
VKQueryCache query_cache;
+ VKFenceManager fence_manager;
vk::Buffer default_buffer;
VKMemoryCommit default_buffer_commit;
vk::Event wfi_event;
+ VideoCommon::Shader::AsyncShaders async_shaders;
std::array<View, Maxwell::NumRenderTargets> color_attachments;
View zeta_attachment;
diff --git a/src/video_core/renderer_vulkan/vk_sampler_cache.cpp b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
index 2d5460776..b068888f9 100644
--- a/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
@@ -47,6 +47,7 @@ vk::Sampler VKSamplerCache::CreateSampler(const Tegra::Texture::TSCEntry& tsc) c
VkSamplerCustomBorderColorCreateInfoEXT border{
.sType = VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT,
.pNext = nullptr,
+ .customBorderColor = {},
.format = VK_FORMAT_UNDEFINED,
};
std::memcpy(&border.customBorderColor, color.data(), sizeof(color));
diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.cpp b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
index 9151d9fb1..5d2c4a796 100644
--- a/src/video_core/renderer_vulkan/vk_state_tracker.cpp
+++ b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
@@ -42,7 +42,6 @@ Flags MakeInvalidationFlags() {
flags[DepthWriteEnable] = true;
flags[DepthCompareOp] = true;
flags[FrontFace] = true;
- flags[PrimitiveTopology] = true;
flags[StencilOp] = true;
flags[StencilTestEnable] = true;
return flags;
@@ -112,10 +111,6 @@ void SetupDirtyFrontFace(Tables& tables) {
table[OFF(screen_y_control)] = FrontFace;
}
-void SetupDirtyPrimitiveTopology(Tables& tables) {
- tables[0][OFF(draw.topology)] = PrimitiveTopology;
-}
-
void SetupDirtyStencilOp(Tables& tables) {
auto& table = tables[0];
table[OFF(stencil_front_op_fail)] = StencilOp;
@@ -137,12 +132,9 @@ void SetupDirtyStencilTestEnable(Tables& tables) {
} // Anonymous namespace
-StateTracker::StateTracker(Core::System& system)
- : system{system}, invalidation_flags{MakeInvalidationFlags()} {}
-
-void StateTracker::Initialize() {
- auto& dirty = system.GPU().Maxwell3D().dirty;
- auto& tables = dirty.tables;
+StateTracker::StateTracker(Tegra::GPU& gpu)
+ : flags{gpu.Maxwell3D().dirty.flags}, invalidation_flags{MakeInvalidationFlags()} {
+ auto& tables = gpu.Maxwell3D().dirty.tables;
SetupDirtyRenderTargets(tables);
SetupDirtyViewports(tables);
SetupDirtyScissors(tables);
@@ -156,13 +148,8 @@ void StateTracker::Initialize() {
SetupDirtyDepthWriteEnable(tables);
SetupDirtyDepthCompareOp(tables);
SetupDirtyFrontFace(tables);
- SetupDirtyPrimitiveTopology(tables);
SetupDirtyStencilOp(tables);
SetupDirtyStencilTestEnable(tables);
}
-void StateTracker::InvalidateCommandBufferState() {
- system.GPU().Maxwell3D().dirty.flags |= invalidation_flags;
-}
-
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.h b/src/video_core/renderer_vulkan/vk_state_tracker.h
index 54ca0d6c6..1de789e57 100644
--- a/src/video_core/renderer_vulkan/vk_state_tracker.h
+++ b/src/video_core/renderer_vulkan/vk_state_tracker.h
@@ -32,7 +32,6 @@ enum : u8 {
DepthWriteEnable,
DepthCompareOp,
FrontFace,
- PrimitiveTopology,
StencilOp,
StencilTestEnable,
@@ -43,12 +42,15 @@ static_assert(Last <= std::numeric_limits<u8>::max());
} // namespace Dirty
class StateTracker {
-public:
- explicit StateTracker(Core::System& system);
+ using Maxwell = Tegra::Engines::Maxwell3D::Regs;
- void Initialize();
+public:
+ explicit StateTracker(Tegra::GPU& gpu);
- void InvalidateCommandBufferState();
+ void InvalidateCommandBufferState() {
+ flags |= invalidation_flags;
+ current_topology = INVALID_TOPOLOGY;
+ }
bool TouchViewports() {
return Exchange(Dirty::Viewports, false);
@@ -102,10 +104,6 @@ public:
return Exchange(Dirty::FrontFace, false);
}
- bool TouchPrimitiveTopology() {
- return Exchange(Dirty::PrimitiveTopology, false);
- }
-
bool TouchStencilOp() {
return Exchange(Dirty::StencilOp, false);
}
@@ -114,16 +112,24 @@ public:
return Exchange(Dirty::StencilTestEnable, false);
}
+ bool ChangePrimitiveTopology(Maxwell::PrimitiveTopology new_topology) {
+ const bool has_changed = current_topology != new_topology;
+ current_topology = new_topology;
+ return has_changed;
+ }
+
private:
+ static constexpr auto INVALID_TOPOLOGY = static_cast<Maxwell::PrimitiveTopology>(~0u);
+
bool Exchange(std::size_t id, bool new_value) const noexcept {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
const bool is_dirty = flags[id];
flags[id] = new_value;
return is_dirty;
}
- Core::System& system;
+ Tegra::Engines::Maxwell3D::DirtyState::Flags& flags;
Tegra::Engines::Maxwell3D::DirtyState::Flags invalidation_flags;
+ Maxwell::PrimitiveTopology current_topology = INVALID_TOPOLOGY;
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp
index a5526a3f5..3c9171a5e 100644
--- a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp
+++ b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp
@@ -57,9 +57,9 @@ u32 GetMemoryType(const VkPhysicalDeviceMemoryProperties& properties,
} // Anonymous namespace
-VKStreamBuffer::VKStreamBuffer(const VKDevice& device, VKScheduler& scheduler,
+VKStreamBuffer::VKStreamBuffer(const VKDevice& device_, VKScheduler& scheduler_,
VkBufferUsageFlags usage)
- : device{device}, scheduler{scheduler} {
+ : device{device_}, scheduler{scheduler_} {
CreateBuffers(usage);
ReserveWatches(current_watches, WATCHES_INITIAL_RESERVE);
ReserveWatches(previous_watches, WATCHES_INITIAL_RESERVE);
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp
index c25e312b6..6bfd2abae 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.cpp
+++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp
@@ -156,6 +156,7 @@ void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities,
.minImageCount = requested_image_count,
.imageFormat = surface_format.format,
.imageColorSpace = surface_format.colorSpace,
+ .imageExtent = {},
.imageArrayLayers = 1,
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
@@ -204,6 +205,7 @@ void VKSwapchain::CreateImageViews() {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
+ .image = {},
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = image_format,
.components =
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index d102e6d27..06182d909 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -138,6 +138,7 @@ VkImageCreateInfo GenerateImageCreateInfo(const VKDevice& device, const SurfaceP
.flags = 0,
.imageType = SurfaceTargetToImage(params.target),
.format = format,
+ .extent = {},
.mipLevels = params.num_levels,
.arrayLayers = static_cast<u32>(params.GetNumLayers()),
.samples = VK_SAMPLE_COUNT_1_BIT,
@@ -187,13 +188,13 @@ u32 EncodeSwizzle(Tegra::Texture::SwizzleSource x_source, Tegra::Texture::Swizzl
} // Anonymous namespace
-CachedSurface::CachedSurface(Core::System& system, const VKDevice& device,
- VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
- VKScheduler& scheduler, VKStagingBufferPool& staging_pool,
- GPUVAddr gpu_addr, const SurfaceParams& params)
- : SurfaceBase<View>{gpu_addr, params, device.IsOptimalAstcSupported()}, system{system},
- device{device}, resource_manager{resource_manager},
- memory_manager{memory_manager}, scheduler{scheduler}, staging_pool{staging_pool} {
+CachedSurface::CachedSurface(const VKDevice& device, VKResourceManager& resource_manager,
+ VKMemoryManager& memory_manager, VKScheduler& scheduler,
+ VKStagingBufferPool& staging_pool, GPUVAddr gpu_addr,
+ const SurfaceParams& params)
+ : SurfaceBase<View>{gpu_addr, params, device.IsOptimalAstcSupported()}, device{device},
+ resource_manager{resource_manager}, memory_manager{memory_manager}, scheduler{scheduler},
+ staging_pool{staging_pool} {
if (params.IsBuffer()) {
buffer = CreateBuffer(device, params, host_memory_size);
commit = memory_manager.Commit(buffer, false);
@@ -458,6 +459,7 @@ VkImageView CachedSurfaceView::GetAttachment() {
.pNext = nullptr,
.flags = 0,
.image = surface.GetImageHandle(),
+ .viewType = VK_IMAGE_VIEW_TYPE_1D,
.format = surface.GetImage().GetFormat(),
.components =
{
@@ -471,6 +473,8 @@ VkImageView CachedSurfaceView::GetAttachment() {
.aspectMask = aspect_mask,
.baseMipLevel = base_level,
.levelCount = num_levels,
+ .baseArrayLayer = 0,
+ .layerCount = 0,
},
};
if (image_view_type == VK_IMAGE_VIEW_TYPE_3D) {
@@ -486,19 +490,21 @@ VkImageView CachedSurfaceView::GetAttachment() {
return *render_target;
}
-VKTextureCache::VKTextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- const VKDevice& device, VKResourceManager& resource_manager,
- VKMemoryManager& memory_manager, VKScheduler& scheduler,
- VKStagingBufferPool& staging_pool)
- : TextureCache(system, rasterizer, device.IsOptimalAstcSupported()), device{device},
- resource_manager{resource_manager}, memory_manager{memory_manager}, scheduler{scheduler},
- staging_pool{staging_pool} {}
+VKTextureCache::VKTextureCache(VideoCore::RasterizerInterface& rasterizer,
+ Tegra::Engines::Maxwell3D& maxwell3d,
+ Tegra::MemoryManager& gpu_memory, const VKDevice& device_,
+ VKResourceManager& resource_manager_,
+ VKMemoryManager& memory_manager_, VKScheduler& scheduler_,
+ VKStagingBufferPool& staging_pool_)
+ : TextureCache(rasterizer, maxwell3d, gpu_memory, device_.IsOptimalAstcSupported()),
+ device{device_}, resource_manager{resource_manager_},
+ memory_manager{memory_manager_}, scheduler{scheduler_}, staging_pool{staging_pool_} {}
VKTextureCache::~VKTextureCache() = default;
Surface VKTextureCache::CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) {
- return std::make_shared<CachedSurface>(system, device, resource_manager, memory_manager,
- scheduler, staging_pool, gpu_addr, params);
+ return std::make_shared<CachedSurface>(device, resource_manager, memory_manager, scheduler,
+ staging_pool, gpu_addr, params);
}
void VKTextureCache::ImageCopy(Surface& src_surface, Surface& dst_surface,
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index 807e26c8a..e47d02c41 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -15,10 +15,6 @@
#include "video_core/texture_cache/surface_base.h"
#include "video_core/texture_cache/texture_cache.h"
-namespace Core {
-class System;
-}
-
namespace VideoCore {
class RasterizerInterface;
}
@@ -45,10 +41,10 @@ class CachedSurface final : public VideoCommon::SurfaceBase<View> {
friend CachedSurfaceView;
public:
- explicit CachedSurface(Core::System& system, const VKDevice& device,
- VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
- VKScheduler& scheduler, VKStagingBufferPool& staging_pool,
- GPUVAddr gpu_addr, const SurfaceParams& params);
+ explicit CachedSurface(const VKDevice& device, VKResourceManager& resource_manager,
+ VKMemoryManager& memory_manager, VKScheduler& scheduler,
+ VKStagingBufferPool& staging_pool, GPUVAddr gpu_addr,
+ const SurfaceParams& params);
~CachedSurface();
void UploadTexture(const std::vector<u8>& staging_buffer) override;
@@ -101,7 +97,6 @@ private:
VkImageSubresourceRange GetImageSubresourceRange() const;
- Core::System& system;
const VKDevice& device;
VKResourceManager& resource_manager;
VKMemoryManager& memory_manager;
@@ -201,7 +196,8 @@ private:
class VKTextureCache final : public TextureCacheBase {
public:
- explicit VKTextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
+ explicit VKTextureCache(VideoCore::RasterizerInterface& rasterizer,
+ Tegra::Engines::Maxwell3D& maxwell3d, Tegra::MemoryManager& gpu_memory,
const VKDevice& device, VKResourceManager& resource_manager,
VKMemoryManager& memory_manager, VKScheduler& scheduler,
VKStagingBufferPool& staging_pool);
diff --git a/src/video_core/renderer_vulkan/wrapper.cpp b/src/video_core/renderer_vulkan/wrapper.cpp
index 14cac38ea..013865aa4 100644
--- a/src/video_core/renderer_vulkan/wrapper.cpp
+++ b/src/video_core/renderer_vulkan/wrapper.cpp
@@ -644,7 +644,7 @@ ShaderModule Device::CreateShaderModule(const VkShaderModuleCreateInfo& ci) cons
return ShaderModule(object, handle, *dld);
}
-Event Device::CreateEvent() const {
+Event Device::CreateNewEvent() const {
static constexpr VkEventCreateInfo ci{
.sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO,
.pNext = nullptr,
@@ -786,7 +786,7 @@ std::optional<std::vector<VkExtensionProperties>> EnumerateInstanceExtensionProp
VK_SUCCESS) {
return std::nullopt;
}
- return properties;
+ return std::move(properties);
}
std::optional<std::vector<VkLayerProperties>> EnumerateInstanceLayerProperties(
diff --git a/src/video_core/renderer_vulkan/wrapper.h b/src/video_core/renderer_vulkan/wrapper.h
index 71daac9d7..b9d3fedc1 100644
--- a/src/video_core/renderer_vulkan/wrapper.h
+++ b/src/video_core/renderer_vulkan/wrapper.h
@@ -721,7 +721,7 @@ public:
ShaderModule CreateShaderModule(const VkShaderModuleCreateInfo& ci) const;
- Event CreateEvent() const;
+ Event CreateNewEvent() const;
SwapchainKHR CreateSwapchainKHR(const VkSwapchainCreateInfoKHR& ci) const;
@@ -756,8 +756,8 @@ public:
}
VkResult GetQueryResults(VkQueryPool query_pool, u32 first, u32 count, std::size_t data_size,
- void* data, VkDeviceSize stride, VkQueryResultFlags flags) const
- noexcept {
+ void* data, VkDeviceSize stride,
+ VkQueryResultFlags flags) const noexcept {
return dld->vkGetQueryPoolResults(handle, query_pool, first, count, data_size, data, stride,
flags);
}
@@ -849,8 +849,8 @@ public:
dld->vkCmdBindPipeline(handle, bind_point, pipeline);
}
- void BindIndexBuffer(VkBuffer buffer, VkDeviceSize offset, VkIndexType index_type) const
- noexcept {
+ void BindIndexBuffer(VkBuffer buffer, VkDeviceSize offset,
+ VkIndexType index_type) const noexcept {
dld->vkCmdBindIndexBuffer(handle, buffer, offset, index_type);
}
@@ -863,8 +863,8 @@ public:
BindVertexBuffers(binding, 1, &buffer, &offset);
}
- void Draw(u32 vertex_count, u32 instance_count, u32 first_vertex, u32 first_instance) const
- noexcept {
+ void Draw(u32 vertex_count, u32 instance_count, u32 first_vertex,
+ u32 first_instance) const noexcept {
dld->vkCmdDraw(handle, vertex_count, instance_count, first_vertex, first_instance);
}
@@ -874,15 +874,15 @@ public:
first_instance);
}
- void ClearAttachments(Span<VkClearAttachment> attachments, Span<VkClearRect> rects) const
- noexcept {
+ void ClearAttachments(Span<VkClearAttachment> attachments,
+ Span<VkClearRect> rects) const noexcept {
dld->vkCmdClearAttachments(handle, attachments.size(), attachments.data(), rects.size(),
rects.data());
}
void BlitImage(VkImage src_image, VkImageLayout src_layout, VkImage dst_image,
- VkImageLayout dst_layout, Span<VkImageBlit> regions, VkFilter filter) const
- noexcept {
+ VkImageLayout dst_layout, Span<VkImageBlit> regions,
+ VkFilter filter) const noexcept {
dld->vkCmdBlitImage(handle, src_image, src_layout, dst_image, dst_layout, regions.size(),
regions.data(), filter);
}
@@ -907,8 +907,8 @@ public:
regions.data());
}
- void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer, Span<VkBufferCopy> regions) const
- noexcept {
+ void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer,
+ Span<VkBufferCopy> regions) const noexcept {
dld->vkCmdCopyBuffer(handle, src_buffer, dst_buffer, regions.size(), regions.data());
}
@@ -924,8 +924,8 @@ public:
regions.data());
}
- void FillBuffer(VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize size, u32 data) const
- noexcept {
+ void FillBuffer(VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize size,
+ u32 data) const noexcept {
dld->vkCmdFillBuffer(handle, dst_buffer, dst_offset, size, data);
}
diff --git a/src/video_core/shader/async_shaders.cpp b/src/video_core/shader/async_shaders.cpp
index b7f66d7ee..aabd62c5c 100644
--- a/src/video_core/shader/async_shaders.cpp
+++ b/src/video_core/shader/async_shaders.cpp
@@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <chrono>
#include <condition_variable>
#include <mutex>
#include <thread>
@@ -20,9 +19,18 @@ AsyncShaders::~AsyncShaders() {
KillWorkers();
}
-void AsyncShaders::AllocateWorkers(std::size_t num_workers) {
- // If we're already have workers queued or don't want to queue workers, ignore
- if (num_workers == worker_threads.size() || num_workers == 0) {
+void AsyncShaders::AllocateWorkers() {
+ // Max worker threads we should allow
+ constexpr u32 MAX_THREADS = 4;
+ // Deduce how many threads we can use
+ const u32 threads_used = std::thread::hardware_concurrency() / 4;
+ // Always allow at least 1 thread regardless of our settings
+ const auto max_worker_count = std::max(1U, threads_used);
+ // Don't use more than MAX_THREADS
+ const auto num_workers = std::min(max_worker_count, MAX_THREADS);
+
+ // If we already have workers queued, ignore
+ if (num_workers == worker_threads.size()) {
return;
}
@@ -34,8 +42,8 @@ void AsyncShaders::AllocateWorkers(std::size_t num_workers) {
// Create workers
for (std::size_t i = 0; i < num_workers; i++) {
context_list.push_back(emu_window.CreateSharedContext());
- worker_threads.push_back(std::move(
- std::thread(&AsyncShaders::ShaderCompilerThread, this, context_list[i].get())));
+ worker_threads.push_back(
+ std::thread(&AsyncShaders::ShaderCompilerThread, this, context_list[i].get()));
}
}
@@ -65,11 +73,11 @@ void AsyncShaders::KillWorkers() {
worker_threads.clear();
}
-bool AsyncShaders::HasWorkQueued() {
+bool AsyncShaders::HasWorkQueued() const {
return !pending_queue.empty();
}
-bool AsyncShaders::HasCompletedWork() {
+bool AsyncShaders::HasCompletedWork() const {
std::shared_lock lock{completed_mutex};
return !finished_work.empty();
}
@@ -94,7 +102,7 @@ bool AsyncShaders::IsShaderAsync(const Tegra::GPU& gpu) const {
}
std::vector<AsyncShaders::Result> AsyncShaders::GetCompletedWork() {
- std::vector<AsyncShaders::Result> results;
+ std::vector<Result> results;
{
std::unique_lock lock{completed_mutex};
results.assign(std::make_move_iterator(finished_work.begin()),
@@ -111,24 +119,50 @@ void AsyncShaders::QueueOpenGLShader(const OpenGL::Device& device,
VideoCommon::Shader::CompilerSettings compiler_settings,
const VideoCommon::Shader::Registry& registry,
VAddr cpu_addr) {
- WorkerParams params{device.UseAssemblyShaders() ? AsyncShaders::Backend::GLASM
- : AsyncShaders::Backend::OpenGL,
- device,
- shader_type,
- uid,
- std::move(code),
- std::move(code_b),
- main_offset,
- compiler_settings,
- registry,
- cpu_addr};
+ WorkerParams params{
+ .backend = device.UseAssemblyShaders() ? Backend::GLASM : Backend::OpenGL,
+ .device = &device,
+ .shader_type = shader_type,
+ .uid = uid,
+ .code = std::move(code),
+ .code_b = std::move(code_b),
+ .main_offset = main_offset,
+ .compiler_settings = compiler_settings,
+ .registry = registry,
+ .cpu_address = cpu_addr,
+ };
std::unique_lock lock(queue_mutex);
- pending_queue.push_back(std::move(params));
+ pending_queue.push(std::move(params));
+ cv.notify_one();
+}
+
+void AsyncShaders::QueueVulkanShader(Vulkan::VKPipelineCache* pp_cache,
+ const Vulkan::VKDevice& device, Vulkan::VKScheduler& scheduler,
+ Vulkan::VKDescriptorPool& descriptor_pool,
+ Vulkan::VKUpdateDescriptorQueue& update_descriptor_queue,
+ Vulkan::VKRenderPassCache& renderpass_cache,
+ std::vector<VkDescriptorSetLayoutBinding> bindings,
+ Vulkan::SPIRVProgram program,
+ Vulkan::GraphicsPipelineCacheKey key) {
+ WorkerParams params{
+ .backend = Backend::Vulkan,
+ .pp_cache = pp_cache,
+ .vk_device = &device,
+ .scheduler = &scheduler,
+ .descriptor_pool = &descriptor_pool,
+ .update_descriptor_queue = &update_descriptor_queue,
+ .renderpass_cache = &renderpass_cache,
+ .bindings = bindings,
+ .program = program,
+ .key = key,
+ };
+
+ std::unique_lock lock(queue_mutex);
+ pending_queue.push(std::move(params));
cv.notify_one();
}
void AsyncShaders::ShaderCompilerThread(Core::Frontend::GraphicsContext* context) {
- using namespace std::chrono_literals;
while (!is_thread_exiting.load(std::memory_order_relaxed)) {
std::unique_lock lock{queue_mutex};
cv.wait(lock, [this] { return HasWorkQueued() || is_thread_exiting; });
@@ -144,18 +178,17 @@ void AsyncShaders::ShaderCompilerThread(Core::Frontend::GraphicsContext* context
if (pending_queue.empty()) {
continue;
}
+
// Pull work from queue
WorkerParams work = std::move(pending_queue.front());
- pending_queue.pop_front();
-
+ pending_queue.pop();
lock.unlock();
- if (work.backend == AsyncShaders::Backend::OpenGL ||
- work.backend == AsyncShaders::Backend::GLASM) {
- const ShaderIR ir(work.code, work.main_offset, work.compiler_settings, work.registry);
+ if (work.backend == Backend::OpenGL || work.backend == Backend::GLASM) {
+ const ShaderIR ir(work.code, work.main_offset, work.compiler_settings, *work.registry);
const auto scope = context->Acquire();
auto program =
- OpenGL::BuildShader(work.device, work.shader_type, work.uid, ir, work.registry);
+ OpenGL::BuildShader(*work.device, work.shader_type, work.uid, ir, *work.registry);
Result result{};
result.backend = work.backend;
result.cpu_address = work.cpu_address;
@@ -164,9 +197,9 @@ void AsyncShaders::ShaderCompilerThread(Core::Frontend::GraphicsContext* context
result.code_b = std::move(work.code_b);
result.shader_type = work.shader_type;
- if (work.backend == AsyncShaders::Backend::OpenGL) {
+ if (work.backend == Backend::OpenGL) {
result.program.opengl = std::move(program->source_program);
- } else if (work.backend == AsyncShaders::Backend::GLASM) {
+ } else if (work.backend == Backend::GLASM) {
result.program.glasm = std::move(program->assembly_program);
}
@@ -174,6 +207,13 @@ void AsyncShaders::ShaderCompilerThread(Core::Frontend::GraphicsContext* context
std::unique_lock complete_lock(completed_mutex);
finished_work.push_back(std::move(result));
}
+ } else if (work.backend == Backend::Vulkan) {
+ auto pipeline = std::make_unique<Vulkan::VKGraphicsPipeline>(
+ *work.vk_device, *work.scheduler, *work.descriptor_pool,
+ *work.update_descriptor_queue, *work.renderpass_cache, work.key, work.bindings,
+ work.program);
+
+ work.pp_cache->EmplacePipeline(std::move(pipeline));
}
}
}
diff --git a/src/video_core/shader/async_shaders.h b/src/video_core/shader/async_shaders.h
index 2f5ee94ad..7cf8d994c 100644
--- a/src/video_core/shader/async_shaders.h
+++ b/src/video_core/shader/async_shaders.h
@@ -5,15 +5,17 @@
#pragma once
#include <condition_variable>
-#include <deque>
#include <memory>
#include <shared_mutex>
#include <thread>
-#include "common/bit_field.h"
+
#include "common/common_types.h"
#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
+#include "video_core/renderer_vulkan/vk_device.h"
+#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
+#include "video_core/renderer_vulkan/vk_scheduler.h"
namespace Core::Frontend {
class EmuWindow;
@@ -24,6 +26,10 @@ namespace Tegra {
class GPU;
}
+namespace Vulkan {
+class VKPipelineCache;
+}
+
namespace VideoCommon::Shader {
class AsyncShaders {
@@ -31,6 +37,7 @@ public:
enum class Backend {
OpenGL,
GLASM,
+ Vulkan,
};
struct ResultPrograms {
@@ -52,7 +59,7 @@ public:
~AsyncShaders();
/// Start up shader worker threads
- void AllocateWorkers(std::size_t num_workers);
+ void AllocateWorkers();
/// Clear the shader queue and kill all worker threads
void FreeWorkers();
@@ -61,48 +68,68 @@ public:
void KillWorkers();
/// Check to see if any shaders have actually been compiled
- bool HasCompletedWork();
+ [[nodiscard]] bool HasCompletedWork() const;
/// Deduce if a shader can be build on another thread of MUST be built in sync. We cannot build
/// every shader async as some shaders are only built and executed once. We try to "guess" which
/// shader would be used only once
- bool IsShaderAsync(const Tegra::GPU& gpu) const;
+ [[nodiscard]] bool IsShaderAsync(const Tegra::GPU& gpu) const;
/// Pulls completed compiled shaders
- std::vector<Result> GetCompletedWork();
+ [[nodiscard]] std::vector<Result> GetCompletedWork();
void QueueOpenGLShader(const OpenGL::Device& device, Tegra::Engines::ShaderType shader_type,
u64 uid, std::vector<u64> code, std::vector<u64> code_b, u32 main_offset,
- VideoCommon::Shader::CompilerSettings compiler_settings,
- const VideoCommon::Shader::Registry& registry, VAddr cpu_addr);
+ CompilerSettings compiler_settings, const Registry& registry,
+ VAddr cpu_addr);
+
+ void QueueVulkanShader(Vulkan::VKPipelineCache* pp_cache, const Vulkan::VKDevice& device,
+ Vulkan::VKScheduler& scheduler,
+ Vulkan::VKDescriptorPool& descriptor_pool,
+ Vulkan::VKUpdateDescriptorQueue& update_descriptor_queue,
+ Vulkan::VKRenderPassCache& renderpass_cache,
+ std::vector<VkDescriptorSetLayoutBinding> bindings,
+ Vulkan::SPIRVProgram program, Vulkan::GraphicsPipelineCacheKey key);
private:
void ShaderCompilerThread(Core::Frontend::GraphicsContext* context);
/// Check our worker queue to see if we have any work queued already
- bool HasWorkQueued();
+ [[nodiscard]] bool HasWorkQueued() const;
struct WorkerParams {
- AsyncShaders::Backend backend;
- OpenGL::Device device;
+ Backend backend;
+ // For OGL
+ const OpenGL::Device* device;
Tegra::Engines::ShaderType shader_type;
u64 uid;
std::vector<u64> code;
std::vector<u64> code_b;
u32 main_offset;
- VideoCommon::Shader::CompilerSettings compiler_settings;
- VideoCommon::Shader::Registry registry;
+ CompilerSettings compiler_settings;
+ std::optional<Registry> registry;
VAddr cpu_address;
+
+ // For Vulkan
+ Vulkan::VKPipelineCache* pp_cache;
+ const Vulkan::VKDevice* vk_device;
+ Vulkan::VKScheduler* scheduler;
+ Vulkan::VKDescriptorPool* descriptor_pool;
+ Vulkan::VKUpdateDescriptorQueue* update_descriptor_queue;
+ Vulkan::VKRenderPassCache* renderpass_cache;
+ std::vector<VkDescriptorSetLayoutBinding> bindings;
+ Vulkan::SPIRVProgram program;
+ Vulkan::GraphicsPipelineCacheKey key;
};
std::condition_variable cv;
- std::mutex queue_mutex;
- std::shared_mutex completed_mutex;
+ mutable std::mutex queue_mutex;
+ mutable std::shared_mutex completed_mutex;
std::atomic<bool> is_thread_exiting{};
std::vector<std::unique_ptr<Core::Frontend::GraphicsContext>> context_list;
std::vector<std::thread> worker_threads;
- std::deque<WorkerParams> pending_queue;
- std::vector<AsyncShaders::Result> finished_work;
+ std::queue<WorkerParams> pending_queue;
+ std::vector<Result> finished_work;
Core::Frontend::EmuWindow& emu_window;
};
diff --git a/src/video_core/shader/control_flow.cpp b/src/video_core/shader/control_flow.cpp
index 8d86020f6..336397cdb 100644
--- a/src/video_core/shader/control_flow.cpp
+++ b/src/video_core/shader/control_flow.cpp
@@ -187,24 +187,26 @@ std::optional<std::pair<BufferInfo, u64>> TrackLDC(const CFGRebuildState& state,
std::optional<u64> TrackSHLRegister(const CFGRebuildState& state, u32& pos,
u64 ldc_tracked_register) {
- return TrackInstruction<u64>(state, pos,
- [ldc_tracked_register](auto instr, const auto& opcode) {
- return opcode.GetId() == OpCode::Id::SHL_IMM &&
- instr.gpr0.Value() == ldc_tracked_register;
- },
- [](auto instr, const auto&) { return instr.gpr8.Value(); });
+ return TrackInstruction<u64>(
+ state, pos,
+ [ldc_tracked_register](auto instr, const auto& opcode) {
+ return opcode.GetId() == OpCode::Id::SHL_IMM &&
+ instr.gpr0.Value() == ldc_tracked_register;
+ },
+ [](auto instr, const auto&) { return instr.gpr8.Value(); });
}
std::optional<u32> TrackIMNMXValue(const CFGRebuildState& state, u32& pos,
u64 shl_tracked_register) {
- return TrackInstruction<u32>(state, pos,
- [shl_tracked_register](auto instr, const auto& opcode) {
- return opcode.GetId() == OpCode::Id::IMNMX_IMM &&
- instr.gpr0.Value() == shl_tracked_register;
- },
- [](auto instr, const auto&) {
- return static_cast<u32>(instr.alu.GetSignedImm20_20() + 1);
- });
+ return TrackInstruction<u32>(
+ state, pos,
+ [shl_tracked_register](auto instr, const auto& opcode) {
+ return opcode.GetId() == OpCode::Id::IMNMX_IMM &&
+ instr.gpr0.Value() == shl_tracked_register;
+ },
+ [](auto instr, const auto&) {
+ return static_cast<u32>(instr.alu.GetSignedImm20_20() + 1);
+ });
}
std::optional<BranchIndirectInfo> TrackBranchIndirectInfo(const CFGRebuildState& state, u32 pos) {
diff --git a/src/video_core/shader/decode/memory.cpp b/src/video_core/shader/decode/memory.cpp
index 63adbc4a3..e2bba88dd 100644
--- a/src/video_core/shader/decode/memory.cpp
+++ b/src/video_core/shader/decode/memory.cpp
@@ -386,7 +386,8 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
break;
}
case OpCode::Id::RED: {
- UNIMPLEMENTED_IF_MSG(instr.red.type != GlobalAtomicType::U32);
+ UNIMPLEMENTED_IF_MSG(instr.red.type != GlobalAtomicType::U32, "type={}",
+ static_cast<int>(instr.red.type.Value()));
const auto [real_address, base_address, descriptor] =
TrackGlobalMemory(bb, instr, true, true);
if (!real_address || !base_address) {
@@ -471,9 +472,9 @@ std::tuple<Node, Node, GlobalMemoryBase> ShaderIR::TrackGlobalMemory(NodeBlock&
const auto [base_address, index, offset] =
TrackCbuf(addr_register, global_code, static_cast<s64>(global_code.size()));
- ASSERT_OR_EXECUTE_MSG(base_address != nullptr,
- { return std::make_tuple(nullptr, nullptr, GlobalMemoryBase{}); },
- "Global memory tracking failed");
+ ASSERT_OR_EXECUTE_MSG(
+ base_address != nullptr, { return std::make_tuple(nullptr, nullptr, GlobalMemoryBase{}); },
+ "Global memory tracking failed");
bb.push_back(Comment(fmt::format("Base address is c[0x{:x}][0x{:x}]", index, offset)));
diff --git a/src/video_core/shader/memory_util.cpp b/src/video_core/shader/memory_util.cpp
index 5071c83ca..e18ccba8e 100644
--- a/src/video_core/shader/memory_util.cpp
+++ b/src/video_core/shader/memory_util.cpp
@@ -16,11 +16,10 @@
namespace VideoCommon::Shader {
-GPUVAddr GetShaderAddress(Core::System& system,
+GPUVAddr GetShaderAddress(Tegra::Engines::Maxwell3D& maxwell3d,
Tegra::Engines::Maxwell3D::Regs::ShaderProgram program) {
- const auto& gpu{system.GPU().Maxwell3D()};
- const auto& shader_config{gpu.regs.shader_config[static_cast<std::size_t>(program)]};
- return gpu.regs.code_address.CodeAddress() + shader_config.offset;
+ const auto& shader_config{maxwell3d.regs.shader_config[static_cast<std::size_t>(program)]};
+ return maxwell3d.regs.code_address.CodeAddress() + shader_config.offset;
}
bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) {
diff --git a/src/video_core/shader/memory_util.h b/src/video_core/shader/memory_util.h
index be90d24fd..4624d38e6 100644
--- a/src/video_core/shader/memory_util.h
+++ b/src/video_core/shader/memory_util.h
@@ -11,10 +11,6 @@
#include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/shader_type.h"
-namespace Core {
-class System;
-}
-
namespace Tegra {
class MemoryManager;
}
@@ -27,7 +23,7 @@ constexpr u32 STAGE_MAIN_OFFSET = 10;
constexpr u32 KERNEL_MAIN_OFFSET = 0;
/// Gets the address for the specified shader stage program
-GPUVAddr GetShaderAddress(Core::System& system,
+GPUVAddr GetShaderAddress(Tegra::Engines::Maxwell3D& maxwell3d,
Tegra::Engines::Maxwell3D::Regs::ShaderProgram program);
/// Gets if the current instruction offset is a scheduler instruction
diff --git a/src/video_core/texture_cache/surface_params.cpp b/src/video_core/texture_cache/surface_params.cpp
index 9e5fe2374..e8515321b 100644
--- a/src/video_core/texture_cache/surface_params.cpp
+++ b/src/video_core/texture_cache/surface_params.cpp
@@ -74,9 +74,9 @@ SurfaceParams SurfaceParams::CreateForTexture(const FormatLookupTable& lookup_ta
SurfaceParams params;
params.is_tiled = tic.IsTiled();
params.srgb_conversion = tic.IsSrgbConversionEnabled();
- params.block_width = params.is_tiled ? tic.BlockWidth() : 0,
- params.block_height = params.is_tiled ? tic.BlockHeight() : 0,
- params.block_depth = params.is_tiled ? tic.BlockDepth() : 0,
+ params.block_width = params.is_tiled ? tic.BlockWidth() : 0;
+ params.block_height = params.is_tiled ? tic.BlockHeight() : 0;
+ params.block_depth = params.is_tiled ? tic.BlockDepth() : 0;
params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1;
params.pixel_format = lookup_table.GetPixelFormat(
tic.format, params.srgb_conversion, tic.r_type, tic.g_type, tic.b_type, tic.a_type);
@@ -96,7 +96,6 @@ SurfaceParams SurfaceParams::CreateForTexture(const FormatLookupTable& lookup_ta
}
params.type = GetFormatType(params.pixel_format);
}
- params.type = GetFormatType(params.pixel_format);
// TODO: on 1DBuffer we should use the tic info.
if (tic.IsBuffer()) {
params.target = SurfaceTarget::TextureBuffer;
@@ -130,14 +129,13 @@ SurfaceParams SurfaceParams::CreateForImage(const FormatLookupTable& lookup_tabl
SurfaceParams params;
params.is_tiled = tic.IsTiled();
params.srgb_conversion = tic.IsSrgbConversionEnabled();
- params.block_width = params.is_tiled ? tic.BlockWidth() : 0,
- params.block_height = params.is_tiled ? tic.BlockHeight() : 0,
- params.block_depth = params.is_tiled ? tic.BlockDepth() : 0,
+ params.block_width = params.is_tiled ? tic.BlockWidth() : 0;
+ params.block_height = params.is_tiled ? tic.BlockHeight() : 0;
+ params.block_depth = params.is_tiled ? tic.BlockDepth() : 0;
params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1;
params.pixel_format = lookup_table.GetPixelFormat(
tic.format, params.srgb_conversion, tic.r_type, tic.g_type, tic.b_type, tic.a_type);
params.type = GetFormatType(params.pixel_format);
- params.type = GetFormatType(params.pixel_format);
params.target = ImageTypeToSurfaceTarget(entry.type);
// TODO: on 1DBuffer we should use the tic info.
if (tic.IsBuffer()) {
@@ -165,33 +163,35 @@ SurfaceParams SurfaceParams::CreateForImage(const FormatLookupTable& lookup_tabl
return params;
}
-SurfaceParams SurfaceParams::CreateForDepthBuffer(Core::System& system) {
- const auto& regs = system.GPU().Maxwell3D().regs;
- SurfaceParams params;
- params.is_tiled = regs.zeta.memory_layout.type ==
- Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
- params.srgb_conversion = false;
- params.block_width = std::min(regs.zeta.memory_layout.block_width.Value(), 5U);
- params.block_height = std::min(regs.zeta.memory_layout.block_height.Value(), 5U);
- params.block_depth = std::min(regs.zeta.memory_layout.block_depth.Value(), 5U);
- params.tile_width_spacing = 1;
- params.pixel_format = PixelFormatFromDepthFormat(regs.zeta.format);
- params.type = GetFormatType(params.pixel_format);
- params.width = regs.zeta_width;
- params.height = regs.zeta_height;
- params.pitch = 0;
- params.num_levels = 1;
- params.emulated_levels = 1;
-
- const bool is_layered = regs.zeta_layers > 1 && params.block_depth == 0;
- params.is_layered = is_layered;
- params.target = is_layered ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D;
- params.depth = is_layered ? regs.zeta_layers.Value() : 1U;
- return params;
+SurfaceParams SurfaceParams::CreateForDepthBuffer(Tegra::Engines::Maxwell3D& maxwell3d) {
+ const auto& regs = maxwell3d.regs;
+ const auto block_depth = std::min(regs.zeta.memory_layout.block_depth.Value(), 5U);
+ const bool is_layered = regs.zeta_layers > 1 && block_depth == 0;
+ const auto pixel_format = PixelFormatFromDepthFormat(regs.zeta.format);
+ return {
+ .is_tiled = regs.zeta.memory_layout.type ==
+ Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear,
+ .srgb_conversion = false,
+ .is_layered = is_layered,
+ .block_width = std::min(regs.zeta.memory_layout.block_width.Value(), 5U),
+ .block_height = std::min(regs.zeta.memory_layout.block_height.Value(), 5U),
+ .block_depth = block_depth,
+ .tile_width_spacing = 1,
+ .width = regs.zeta_width,
+ .height = regs.zeta_height,
+ .depth = is_layered ? regs.zeta_layers.Value() : 1U,
+ .pitch = 0,
+ .num_levels = 1,
+ .emulated_levels = 1,
+ .pixel_format = pixel_format,
+ .type = GetFormatType(pixel_format),
+ .target = is_layered ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D,
+ };
}
-SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::size_t index) {
- const auto& config{system.GPU().Maxwell3D().regs.rt[index]};
+SurfaceParams SurfaceParams::CreateForFramebuffer(Tegra::Engines::Maxwell3D& maxwell3d,
+ std::size_t index) {
+ const auto& config{maxwell3d.regs.rt[index]};
SurfaceParams params;
params.is_tiled =
config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
@@ -233,24 +233,29 @@ SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::siz
SurfaceParams SurfaceParams::CreateForFermiCopySurface(
const Tegra::Engines::Fermi2D::Regs::Surface& config) {
- SurfaceParams params{};
- params.is_tiled = !config.linear;
- params.srgb_conversion = config.format == Tegra::RenderTargetFormat::B8G8R8A8_SRGB ||
- config.format == Tegra::RenderTargetFormat::A8B8G8R8_SRGB;
- params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 5U) : 0,
- params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 5U) : 0,
- params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 5U) : 0,
- params.tile_width_spacing = 1;
- params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
- params.type = GetFormatType(params.pixel_format);
- params.width = config.width;
- params.height = config.height;
- params.pitch = config.pitch;
- // TODO(Rodrigo): Try to guess texture arrays from parameters
- params.target = SurfaceTarget::Texture2D;
- params.depth = 1;
- params.num_levels = 1;
- params.emulated_levels = 1;
+ const bool is_tiled = !config.linear;
+ const auto pixel_format = PixelFormatFromRenderTargetFormat(config.format);
+
+ SurfaceParams params{
+ .is_tiled = is_tiled,
+ .srgb_conversion = config.format == Tegra::RenderTargetFormat::B8G8R8A8_SRGB ||
+ config.format == Tegra::RenderTargetFormat::A8B8G8R8_SRGB,
+ .block_width = is_tiled ? std::min(config.BlockWidth(), 5U) : 0U,
+ .block_height = is_tiled ? std::min(config.BlockHeight(), 5U) : 0U,
+ .block_depth = is_tiled ? std::min(config.BlockDepth(), 5U) : 0U,
+ .tile_width_spacing = 1,
+ .width = config.width,
+ .height = config.height,
+ .depth = 1,
+ .pitch = config.pitch,
+ .num_levels = 1,
+ .emulated_levels = 1,
+ .pixel_format = pixel_format,
+ .type = GetFormatType(pixel_format),
+ // TODO(Rodrigo): Try to guess texture arrays from parameters
+ .target = SurfaceTarget::Texture2D,
+ };
+
params.is_layered = params.IsLayered();
return params;
}
diff --git a/src/video_core/texture_cache/surface_params.h b/src/video_core/texture_cache/surface_params.h
index 118aa689e..4466c3c34 100644
--- a/src/video_core/texture_cache/surface_params.h
+++ b/src/video_core/texture_cache/surface_params.h
@@ -33,10 +33,11 @@ public:
const VideoCommon::Shader::Image& entry);
/// Creates SurfaceCachedParams for a depth buffer configuration.
- static SurfaceParams CreateForDepthBuffer(Core::System& system);
+ static SurfaceParams CreateForDepthBuffer(Tegra::Engines::Maxwell3D& maxwell3d);
/// Creates SurfaceCachedParams from a framebuffer configuration.
- static SurfaceParams CreateForFramebuffer(Core::System& system, std::size_t index);
+ static SurfaceParams CreateForFramebuffer(Tegra::Engines::Maxwell3D& maxwell3d,
+ std::size_t index);
/// Creates SurfaceCachedParams from a Fermi2D surface configuration.
static SurfaceParams CreateForFermiCopySurface(
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 96c4e4cc2..ea835c59f 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -135,8 +135,7 @@ public:
return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
}
- const std::optional<VAddr> cpu_addr =
- system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
if (!cpu_addr) {
return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
}
@@ -160,8 +159,7 @@ public:
if (!gpu_addr) {
return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
}
- const std::optional<VAddr> cpu_addr =
- system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
if (!cpu_addr) {
return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
}
@@ -183,11 +181,11 @@ public:
TView GetDepthBufferSurface(bool preserve_contents) {
std::lock_guard lock{mutex};
- auto& maxwell3d = system.GPU().Maxwell3D();
- if (!maxwell3d.dirty.flags[VideoCommon::Dirty::ZetaBuffer]) {
+ auto& dirty = maxwell3d.dirty;
+ if (!dirty.flags[VideoCommon::Dirty::ZetaBuffer]) {
return depth_buffer.view;
}
- maxwell3d.dirty.flags[VideoCommon::Dirty::ZetaBuffer] = false;
+ dirty.flags[VideoCommon::Dirty::ZetaBuffer] = false;
const auto& regs{maxwell3d.regs};
const auto gpu_addr{regs.zeta.Address()};
@@ -195,13 +193,12 @@ public:
SetEmptyDepthBuffer();
return {};
}
- const std::optional<VAddr> cpu_addr =
- system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
if (!cpu_addr) {
SetEmptyDepthBuffer();
return {};
}
- const auto depth_params{SurfaceParams::CreateForDepthBuffer(system)};
+ const auto depth_params{SurfaceParams::CreateForDepthBuffer(maxwell3d)};
auto surface_view = GetSurface(gpu_addr, *cpu_addr, depth_params, preserve_contents, true);
if (depth_buffer.target)
depth_buffer.target->MarkAsRenderTarget(false, NO_RT);
@@ -215,7 +212,6 @@ public:
TView GetColorBufferSurface(std::size_t index, bool preserve_contents) {
std::lock_guard lock{mutex};
ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets);
- auto& maxwell3d = system.GPU().Maxwell3D();
if (!maxwell3d.dirty.flags[VideoCommon::Dirty::ColorBuffer0 + index]) {
return render_targets[index].view;
}
@@ -235,15 +231,14 @@ public:
return {};
}
- const std::optional<VAddr> cpu_addr =
- system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
if (!cpu_addr) {
SetEmptyColorBuffer(index);
return {};
}
auto surface_view =
- GetSurface(gpu_addr, *cpu_addr, SurfaceParams::CreateForFramebuffer(system, index),
+ GetSurface(gpu_addr, *cpu_addr, SurfaceParams::CreateForFramebuffer(maxwell3d, index),
preserve_contents, true);
if (render_targets[index].target) {
auto& surface = render_targets[index].target;
@@ -300,9 +295,8 @@ public:
const GPUVAddr dst_gpu_addr = dst_config.Address();
DeduceBestBlit(src_params, dst_params, src_gpu_addr, dst_gpu_addr);
- const auto& memory_manager = system.GPU().MemoryManager();
- const std::optional<VAddr> dst_cpu_addr = memory_manager.GpuToCpuAddress(dst_gpu_addr);
- const std::optional<VAddr> src_cpu_addr = memory_manager.GpuToCpuAddress(src_gpu_addr);
+ const std::optional<VAddr> dst_cpu_addr = gpu_memory.GpuToCpuAddress(dst_gpu_addr);
+ const std::optional<VAddr> src_cpu_addr = gpu_memory.GpuToCpuAddress(src_gpu_addr);
std::pair dst_surface = GetSurface(dst_gpu_addr, *dst_cpu_addr, dst_params, true, false);
TView src_surface = GetSurface(src_gpu_addr, *src_cpu_addr, src_params, true, false).second;
ImageBlit(src_surface, dst_surface.second, copy_config);
@@ -358,9 +352,11 @@ public:
}
protected:
- explicit TextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- bool is_astc_supported)
- : system{system}, is_astc_supported{is_astc_supported}, rasterizer{rasterizer} {
+ explicit TextureCache(VideoCore::RasterizerInterface& rasterizer_,
+ Tegra::Engines::Maxwell3D& maxwell3d_, Tegra::MemoryManager& gpu_memory_,
+ bool is_astc_supported_)
+ : is_astc_supported{is_astc_supported_}, rasterizer{rasterizer_}, maxwell3d{maxwell3d_},
+ gpu_memory{gpu_memory_} {
for (std::size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) {
SetEmptyColorBuffer(i);
}
@@ -395,7 +391,7 @@ protected:
virtual void BufferCopy(TSurface& src_surface, TSurface& dst_surface) = 0;
void ManageRenderTargetUnregister(TSurface& surface) {
- auto& dirty = system.GPU().Maxwell3D().dirty;
+ auto& dirty = maxwell3d.dirty;
const u32 index = surface->GetRenderTarget();
if (index == DEPTH_RT) {
dirty.flags[VideoCommon::Dirty::ZetaBuffer] = true;
@@ -408,8 +404,7 @@ protected:
void Register(TSurface surface) {
const GPUVAddr gpu_addr = surface->GetGpuAddr();
const std::size_t size = surface->GetSizeInBytes();
- const std::optional<VAddr> cpu_addr =
- system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
if (!cpu_addr) {
LOG_CRITICAL(HW_GPU, "Failed to register surface with unmapped gpu_address 0x{:016x}",
gpu_addr);
@@ -459,7 +454,6 @@ protected:
return new_surface;
}
- Core::System& system;
const bool is_astc_supported;
private:
@@ -954,8 +948,7 @@ private:
* @param params The parameters on the candidate surface.
**/
Deduction DeduceSurface(const GPUVAddr gpu_addr, const SurfaceParams& params) {
- const std::optional<VAddr> cpu_addr =
- system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
if (!cpu_addr) {
Deduction result{};
@@ -1112,7 +1105,7 @@ private:
void LoadSurface(const TSurface& surface) {
staging_cache.GetBuffer(0).resize(surface->GetHostSizeInBytes());
- surface->LoadBuffer(system.GPU().MemoryManager(), staging_cache);
+ surface->LoadBuffer(gpu_memory, staging_cache);
surface->UploadTexture(staging_cache.GetBuffer(0));
surface->MarkAsModified(false, Tick());
}
@@ -1123,7 +1116,7 @@ private:
}
staging_cache.GetBuffer(0).resize(surface->GetHostSizeInBytes());
surface->DownloadTexture(staging_cache.GetBuffer(0));
- surface->FlushBuffer(system.GPU().MemoryManager(), staging_cache);
+ surface->FlushBuffer(gpu_memory, staging_cache);
surface->MarkAsModified(false, Tick());
}
@@ -1253,6 +1246,8 @@ private:
}
VideoCore::RasterizerInterface& rasterizer;
+ Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::MemoryManager& gpu_memory;
FormatLookupTable format_lookup_table;
FormatCompatibility format_compatibility;
diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp
index 474ae620a..16d46a018 100644
--- a/src/video_core/textures/decoders.cpp
+++ b/src/video_core/textures/decoders.cpp
@@ -228,24 +228,30 @@ void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32
}
}
-void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width,
- u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data,
- u32 block_height_bit, u32 offset_x, u32 offset_y) {
- const u32 block_height = 1U << block_height_bit;
- for (u32 line = 0; line < subrect_height; ++line) {
- const u32 y2 = line + offset_y;
- const u32 gob_address_y = (y2 / (GOB_SIZE_Y * block_height)) * GOB_SIZE * block_height +
- ((y2 % (GOB_SIZE_Y * block_height)) / GOB_SIZE_Y) * GOB_SIZE;
- const auto& table = LEGACY_SWIZZLE_TABLE[y2 % GOB_SIZE_Y];
- for (u32 x = 0; x < subrect_width; ++x) {
- const u32 x2 = (x + offset_x) * bytes_per_pixel;
- const u32 gob_address = gob_address_y + (x2 / GOB_SIZE_X) * GOB_SIZE * block_height;
- const u32 swizzled_offset = gob_address + table[x2 % GOB_SIZE_X];
- const u32 unswizzled_offset = line * dest_pitch + x * bytes_per_pixel;
- u8* dest_line = unswizzled_data + unswizzled_offset;
- u8* source_addr = swizzled_data + swizzled_offset;
+void UnswizzleSubrect(u32 line_length_in, u32 line_count, u32 pitch, u32 width, u32 bytes_per_pixel,
+ u32 block_height, u32 origin_x, u32 origin_y, u8* output, const u8* input) {
+ const u32 stride = width * bytes_per_pixel;
+ const u32 gobs_in_x = (stride + GOB_SIZE_X - 1) / GOB_SIZE_X;
+ const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height);
+
+ const u32 block_height_mask = (1U << block_height) - 1;
+ const u32 x_shift = static_cast<u32>(GOB_SIZE_SHIFT) + block_height;
+
+ for (u32 line = 0; line < line_count; ++line) {
+ const u32 src_y = line + origin_y;
+ const auto& table = LEGACY_SWIZZLE_TABLE[src_y % GOB_SIZE_Y];
+
+ const u32 block_y = src_y >> GOB_SIZE_Y_SHIFT;
+ const u32 src_offset_y = (block_y >> block_height) * block_size +
+ ((block_y & block_height_mask) << GOB_SIZE_SHIFT);
+ for (u32 column = 0; column < line_length_in; ++column) {
+ const u32 src_x = (column + origin_x) * bytes_per_pixel;
+ const u32 src_offset_x = (src_x >> GOB_SIZE_X_SHIFT) << x_shift;
+
+ const u32 swizzled_offset = src_offset_y + src_offset_x + table[src_x % GOB_SIZE_X];
+ const u32 unswizzled_offset = line * pitch + column * bytes_per_pixel;
- std::memcpy(dest_line, source_addr, bytes_per_pixel);
+ std::memcpy(output + unswizzled_offset, input + swizzled_offset, bytes_per_pixel);
}
}
}
@@ -261,7 +267,7 @@ void SwizzleSliceToVoxel(u32 line_length_in, u32 line_count, u32 pitch, u32 widt
const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height + block_depth);
const u32 block_height_mask = (1U << block_height) - 1;
- const u32 x_shift = Common::CountTrailingZeroes32(GOB_SIZE << (block_height + block_depth));
+ const u32 x_shift = static_cast<u32>(GOB_SIZE_SHIFT) + block_height + block_depth;
for (u32 line = 0; line < line_count; ++line) {
const auto& table = LEGACY_SWIZZLE_TABLE[line % GOB_SIZE_Y];
diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h
index d6fe35d37..01e156bc8 100644
--- a/src/video_core/textures/decoders.h
+++ b/src/video_core/textures/decoders.h
@@ -48,9 +48,8 @@ void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32
u32 block_height_bit, u32 offset_x, u32 offset_y);
/// Copies a tiled subrectangle into a linear surface.
-void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width,
- u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height,
- u32 offset_x, u32 offset_y);
+void UnswizzleSubrect(u32 line_length_in, u32 line_count, u32 pitch, u32 width, u32 bytes_per_pixel,
+ u32 block_height, u32 origin_x, u32 origin_y, u8* output, const u8* input);
/// @brief Swizzles a 2D array of pixels into a 3D texture
/// @param line_length_in Number of pixels per line
diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp
index 45f360bdd..a14df06a3 100644
--- a/src/video_core/video_core.cpp
+++ b/src/video_core/video_core.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <memory>
+
#include "common/logging/log.h"
#include "core/core.h"
#include "core/settings.h"
@@ -16,37 +17,49 @@
#include "video_core/video_core.h"
namespace {
-std::unique_ptr<VideoCore::RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_window,
- Core::System& system,
- Core::Frontend::GraphicsContext& context) {
+
+std::unique_ptr<VideoCore::RendererBase> CreateRenderer(
+ Core::System& system, Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu,
+ std::unique_ptr<Core::Frontend::GraphicsContext> context) {
+ auto& telemetry_session = system.TelemetrySession();
+ auto& cpu_memory = system.Memory();
+
switch (Settings::values.renderer_backend.GetValue()) {
case Settings::RendererBackend::OpenGL:
- return std::make_unique<OpenGL::RendererOpenGL>(emu_window, system, context);
+ return std::make_unique<OpenGL::RendererOpenGL>(telemetry_session, emu_window, cpu_memory,
+ gpu, std::move(context));
#ifdef HAS_VULKAN
case Settings::RendererBackend::Vulkan:
- return std::make_unique<Vulkan::RendererVulkan>(emu_window, system);
+ return std::make_unique<Vulkan::RendererVulkan>(telemetry_session, emu_window, cpu_memory,
+ gpu, std::move(context));
#endif
default:
return nullptr;
}
}
+
} // Anonymous namespace
namespace VideoCore {
std::unique_ptr<Tegra::GPU> CreateGPU(Core::Frontend::EmuWindow& emu_window, Core::System& system) {
+ std::unique_ptr<Tegra::GPU> gpu;
+ if (Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
+ gpu = std::make_unique<VideoCommon::GPUAsynch>(system);
+ } else {
+ gpu = std::make_unique<VideoCommon::GPUSynch>(system);
+ }
+
auto context = emu_window.CreateSharedContext();
const auto scope = context->Acquire();
- auto renderer = CreateRenderer(emu_window, system, *context);
+
+ auto renderer = CreateRenderer(system, emu_window, *gpu, std::move(context));
if (!renderer->Init()) {
return nullptr;
}
- if (Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
- return std::make_unique<VideoCommon::GPUAsynch>(system, std::move(renderer),
- std::move(context));
- }
- return std::make_unique<VideoCommon::GPUSynch>(system, std::move(renderer), std::move(context));
+ gpu->BindRenderer(std::move(renderer));
+ return gpu;
}
u16 GetResolutionScaleFactor(const RendererBase& renderer) {
diff --git a/src/web_service/CMakeLists.txt b/src/web_service/CMakeLists.txt
index 06ab7c59d..7e484b906 100644
--- a/src/web_service/CMakeLists.txt
+++ b/src/web_service/CMakeLists.txt
@@ -5,6 +5,7 @@ add_library(web_service STATIC
verify_login.h
web_backend.cpp
web_backend.h
+ web_result.h
)
create_target_directory_groups(web_service)
diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp
index 7a480e33c..6215c914f 100644
--- a/src/web_service/telemetry_json.cpp
+++ b/src/web_service/telemetry_json.cpp
@@ -4,12 +4,14 @@
#include <nlohmann/json.hpp>
#include "common/detached_tasks.h"
-#include "common/web_result.h"
#include "web_service/telemetry_json.h"
#include "web_service/web_backend.h"
+#include "web_service/web_result.h"
namespace WebService {
+namespace Telemetry = Common::Telemetry;
+
struct TelemetryJson::Impl {
Impl(std::string host, std::string username, std::string token)
: host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {}
@@ -123,7 +125,7 @@ bool TelemetryJson::SubmitTestcase() {
Client client(impl->host, impl->username, impl->token);
auto value = client.PostJson("/gamedb/testcase", content, false);
- return value.result_code == Common::WebResult::Code::Success;
+ return value.result_code == WebResult::Code::Success;
}
} // namespace WebService
diff --git a/src/web_service/telemetry_json.h b/src/web_service/telemetry_json.h
index dfd202829..df51e00f8 100644
--- a/src/web_service/telemetry_json.h
+++ b/src/web_service/telemetry_json.h
@@ -14,25 +14,25 @@ namespace WebService {
* Implementation of VisitorInterface that serialized telemetry into JSON, and submits it to the
* yuzu web service
*/
-class TelemetryJson : public Telemetry::VisitorInterface {
+class TelemetryJson : public Common::Telemetry::VisitorInterface {
public:
TelemetryJson(std::string host, std::string username, std::string token);
~TelemetryJson() override;
- void Visit(const Telemetry::Field<bool>& field) override;
- void Visit(const Telemetry::Field<double>& field) override;
- void Visit(const Telemetry::Field<float>& field) override;
- void Visit(const Telemetry::Field<u8>& field) override;
- void Visit(const Telemetry::Field<u16>& field) override;
- void Visit(const Telemetry::Field<u32>& field) override;
- void Visit(const Telemetry::Field<u64>& field) override;
- void Visit(const Telemetry::Field<s8>& field) override;
- void Visit(const Telemetry::Field<s16>& field) override;
- void Visit(const Telemetry::Field<s32>& field) override;
- void Visit(const Telemetry::Field<s64>& field) override;
- void Visit(const Telemetry::Field<std::string>& field) override;
- void Visit(const Telemetry::Field<const char*>& field) override;
- void Visit(const Telemetry::Field<std::chrono::microseconds>& field) override;
+ void Visit(const Common::Telemetry::Field<bool>& field) override;
+ void Visit(const Common::Telemetry::Field<double>& field) override;
+ void Visit(const Common::Telemetry::Field<float>& field) override;
+ void Visit(const Common::Telemetry::Field<u8>& field) override;
+ void Visit(const Common::Telemetry::Field<u16>& field) override;
+ void Visit(const Common::Telemetry::Field<u32>& field) override;
+ void Visit(const Common::Telemetry::Field<u64>& field) override;
+ void Visit(const Common::Telemetry::Field<s8>& field) override;
+ void Visit(const Common::Telemetry::Field<s16>& field) override;
+ void Visit(const Common::Telemetry::Field<s32>& field) override;
+ void Visit(const Common::Telemetry::Field<s64>& field) override;
+ void Visit(const Common::Telemetry::Field<std::string>& field) override;
+ void Visit(const Common::Telemetry::Field<const char*>& field) override;
+ void Visit(const Common::Telemetry::Field<std::chrono::microseconds>& field) override;
void Complete() override;
bool SubmitTestcase() override;
diff --git a/src/web_service/verify_login.cpp b/src/web_service/verify_login.cpp
index bfaa5b70a..ceb55ca6b 100644
--- a/src/web_service/verify_login.cpp
+++ b/src/web_service/verify_login.cpp
@@ -3,9 +3,9 @@
// Refer to the license.txt file included.
#include <nlohmann/json.hpp>
-#include "common/web_result.h"
#include "web_service/verify_login.h"
#include "web_service/web_backend.h"
+#include "web_service/web_result.h"
namespace WebService {
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
index 09d1651ac..74e287045 100644
--- a/src/web_service/web_backend.cpp
+++ b/src/web_service/web_backend.cpp
@@ -6,13 +6,14 @@
#include <cstdlib>
#include <mutex>
#include <string>
+
#include <LUrlParser.h>
#include <fmt/format.h>
#include <httplib.h>
-#include "common/common_types.h"
+
#include "common/logging/log.h"
-#include "common/web_result.h"
#include "web_service/web_backend.h"
+#include "web_service/web_result.h"
namespace WebService {
@@ -33,17 +34,16 @@ struct Client::Impl {
}
/// A generic function handles POST, GET and DELETE request together
- Common::WebResult GenericRequest(const std::string& method, const std::string& path,
- const std::string& data, bool allow_anonymous,
- const std::string& accept) {
+ WebResult GenericRequest(const std::string& method, const std::string& path,
+ const std::string& data, bool allow_anonymous,
+ const std::string& accept) {
if (jwt.empty()) {
UpdateJWT();
}
if (jwt.empty() && !allow_anonymous) {
LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
- return Common::WebResult{Common::WebResult::Code::CredentialsMissing,
- "Credentials needed", ""};
+ return WebResult{WebResult::Code::CredentialsMissing, "Credentials needed", ""};
}
auto result = GenericRequest(method, path, data, accept, jwt);
@@ -62,10 +62,10 @@ struct Client::Impl {
* username + token is used if jwt is empty but username and token are
* not empty anonymous if all of jwt, username and token are empty
*/
- Common::WebResult GenericRequest(const std::string& method, const std::string& path,
- const std::string& data, const std::string& accept,
- const std::string& jwt = "", const std::string& username = "",
- const std::string& token = "") {
+ WebResult GenericRequest(const std::string& method, const std::string& path,
+ const std::string& data, const std::string& accept,
+ const std::string& jwt = "", const std::string& username = "",
+ const std::string& token = "") {
if (cli == nullptr) {
auto parsedUrl = LUrlParser::clParseURL::ParseURL(host);
int port;
@@ -81,12 +81,12 @@ struct Client::Impl {
cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port);
} else {
LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme);
- return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme", ""};
+ return WebResult{WebResult::Code::InvalidURL, "Bad URL scheme", ""};
}
}
if (cli == nullptr) {
LOG_ERROR(WebService, "Invalid URL {}", host + path);
- return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL", ""};
+ return WebResult{WebResult::Code::InvalidURL, "Invalid URL", ""};
}
cli->set_timeout_sec(TIMEOUT_SECONDS);
@@ -106,7 +106,7 @@ struct Client::Impl {
std::string(API_VERSION.begin(), API_VERSION.end()));
if (method != "GET") {
params.emplace(std::string("Content-Type"), std::string("application/json"));
- };
+ }
httplib::Request request;
request.method = method;
@@ -118,29 +118,28 @@ struct Client::Impl {
if (!cli->send(request, response)) {
LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
- return Common::WebResult{Common::WebResult::Code::LibError, "Null response", ""};
+ return WebResult{WebResult::Code::LibError, "Null response", ""};
}
if (response.status >= 400) {
LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path,
response.status);
- return Common::WebResult{Common::WebResult::Code::HttpError,
- std::to_string(response.status), ""};
+ return WebResult{WebResult::Code::HttpError, std::to_string(response.status), ""};
}
auto content_type = response.headers.find("content-type");
if (content_type == response.headers.end()) {
LOG_ERROR(WebService, "{} to {} returned no content", method, host + path);
- return Common::WebResult{Common::WebResult::Code::WrongContent, "", ""};
+ return WebResult{WebResult::Code::WrongContent, "", ""};
}
if (content_type->second.find(accept) == std::string::npos) {
LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path,
content_type->second);
- return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content", ""};
+ return WebResult{WebResult::Code::WrongContent, "Wrong content", ""};
}
- return Common::WebResult{Common::WebResult::Code::Success, "", response.body};
+ return WebResult{WebResult::Code::Success, "", response.body};
}
// Retrieve a new JWT from given username and token
@@ -150,7 +149,7 @@ struct Client::Impl {
}
auto result = GenericRequest("POST", "/jwt/internal", "", "text/html", "", username, token);
- if (result.result_code != Common::WebResult::Code::Success) {
+ if (result.result_code != WebResult::Code::Success) {
LOG_ERROR(WebService, "UpdateJWT failed");
} else {
std::lock_guard lock{jwt_cache.mutex};
@@ -180,29 +179,28 @@ Client::Client(std::string host, std::string username, std::string token)
Client::~Client() = default;
-Common::WebResult Client::PostJson(const std::string& path, const std::string& data,
- bool allow_anonymous) {
+WebResult Client::PostJson(const std::string& path, const std::string& data, bool allow_anonymous) {
return impl->GenericRequest("POST", path, data, allow_anonymous, "application/json");
}
-Common::WebResult Client::GetJson(const std::string& path, bool allow_anonymous) {
+WebResult Client::GetJson(const std::string& path, bool allow_anonymous) {
return impl->GenericRequest("GET", path, "", allow_anonymous, "application/json");
}
-Common::WebResult Client::DeleteJson(const std::string& path, const std::string& data,
- bool allow_anonymous) {
+WebResult Client::DeleteJson(const std::string& path, const std::string& data,
+ bool allow_anonymous) {
return impl->GenericRequest("DELETE", path, data, allow_anonymous, "application/json");
}
-Common::WebResult Client::GetPlain(const std::string& path, bool allow_anonymous) {
+WebResult Client::GetPlain(const std::string& path, bool allow_anonymous) {
return impl->GenericRequest("GET", path, "", allow_anonymous, "text/plain");
}
-Common::WebResult Client::GetImage(const std::string& path, bool allow_anonymous) {
+WebResult Client::GetImage(const std::string& path, bool allow_anonymous) {
return impl->GenericRequest("GET", path, "", allow_anonymous, "image/png");
}
-Common::WebResult Client::GetExternalJWT(const std::string& audience) {
+WebResult Client::GetExternalJWT(const std::string& audience) {
return impl->GenericRequest("POST", fmt::format("/jwt/external/{}", audience), "", false,
"text/html");
}
diff --git a/src/web_service/web_backend.h b/src/web_service/web_backend.h
index 04121f17e..81f58583c 100644
--- a/src/web_service/web_backend.h
+++ b/src/web_service/web_backend.h
@@ -7,12 +7,10 @@
#include <memory>
#include <string>
-namespace Common {
-struct WebResult;
-}
-
namespace WebService {
+struct WebResult;
+
class Client {
public:
Client(std::string host, std::string username, std::string token);
@@ -25,8 +23,7 @@ public:
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
* @return the result of the request.
*/
- Common::WebResult PostJson(const std::string& path, const std::string& data,
- bool allow_anonymous);
+ WebResult PostJson(const std::string& path, const std::string& data, bool allow_anonymous);
/**
* Gets JSON from the specified path.
@@ -34,7 +31,7 @@ public:
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
* @return the result of the request.
*/
- Common::WebResult GetJson(const std::string& path, bool allow_anonymous);
+ WebResult GetJson(const std::string& path, bool allow_anonymous);
/**
* Deletes JSON to the specified path.
@@ -43,8 +40,7 @@ public:
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
* @return the result of the request.
*/
- Common::WebResult DeleteJson(const std::string& path, const std::string& data,
- bool allow_anonymous);
+ WebResult DeleteJson(const std::string& path, const std::string& data, bool allow_anonymous);
/**
* Gets a plain string from the specified path.
@@ -52,7 +48,7 @@ public:
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
* @return the result of the request.
*/
- Common::WebResult GetPlain(const std::string& path, bool allow_anonymous);
+ WebResult GetPlain(const std::string& path, bool allow_anonymous);
/**
* Gets an PNG image from the specified path.
@@ -60,14 +56,14 @@ public:
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
* @return the result of the request.
*/
- Common::WebResult GetImage(const std::string& path, bool allow_anonymous);
+ WebResult GetImage(const std::string& path, bool allow_anonymous);
/**
* Requests an external JWT for the specific audience provided.
* @param audience the audience of the JWT requested.
* @return the result of the request.
*/
- Common::WebResult GetExternalJWT(const std::string& audience);
+ WebResult GetExternalJWT(const std::string& audience);
private:
struct Impl;
diff --git a/src/common/web_result.h b/src/web_service/web_result.h
index 8bfa2141d..3aeeb5288 100644
--- a/src/common/web_result.h
+++ b/src/web_service/web_result.h
@@ -7,7 +7,7 @@
#include <string>
#include "common/common_types.h"
-namespace Common {
+namespace WebService {
struct WebResult {
enum class Code : u32 {
Success,
@@ -22,4 +22,4 @@ struct WebResult {
std::string result_string;
std::string returned_data;
};
-} // namespace Common
+} // namespace WebService
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 656096c9f..cc0291b15 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -9,6 +9,9 @@ add_executable(yuzu
about_dialog.cpp
about_dialog.h
aboutdialog.ui
+ applets/controller.cpp
+ applets/controller.h
+ applets/controller.ui
applets/error.cpp
applets/error.h
applets/profile_select.cpp
@@ -39,6 +42,9 @@ add_executable(yuzu
configuration/configure_debug.cpp
configuration/configure_debug.h
configuration/configure_debug.ui
+ configuration/configure_debug_controller.cpp
+ configuration/configure_debug_controller.h
+ configuration/configure_debug_controller.ui
configuration/configure_dialog.cpp
configuration/configure_dialog.h
configuration/configure_filesystem.cpp
@@ -59,12 +65,18 @@ add_executable(yuzu
configuration/configure_input.cpp
configuration/configure_input.h
configuration/configure_input.ui
+ configuration/configure_input_advanced.cpp
+ configuration/configure_input_advanced.h
+ configuration/configure_input_advanced.ui
+ configuration/configure_input_dialog.cpp
+ configuration/configure_input_dialog.h
+ configuration/configure_input_dialog.ui
configuration/configure_input_player.cpp
configuration/configure_input_player.h
configuration/configure_input_player.ui
- configuration/configure_input_simple.cpp
- configuration/configure_input_simple.h
- configuration/configure_input_simple.ui
+ configuration/configure_motion_touch.cpp
+ configuration/configure_motion_touch.h
+ configuration/configure_motion_touch.ui
configuration/configure_mouse_advanced.cpp
configuration/configure_mouse_advanced.h
configuration/configure_mouse_advanced.ui
@@ -83,9 +95,13 @@ add_executable(yuzu
configuration/configure_system.cpp
configuration/configure_system.h
configuration/configure_system.ui
+ configuration/configure_touch_from_button.cpp
+ configuration/configure_touch_from_button.h
+ configuration/configure_touch_from_button.ui
configuration/configure_touchscreen_advanced.cpp
configuration/configure_touchscreen_advanced.h
configuration/configure_touchscreen_advanced.ui
+ configuration/configure_touch_widget.h
configuration/configure_ui.cpp
configuration/configure_ui.h
configuration/configure_ui.ui
diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp
new file mode 100644
index 000000000..9d45f2a01
--- /dev/null
+++ b/src/yuzu/applets/controller.cpp
@@ -0,0 +1,601 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+
+#include "common/assert.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/hle/lock.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_controller.h"
+#include "yuzu/applets/controller.h"
+#include "yuzu/configuration/configure_input_dialog.h"
+#include "yuzu/main.h"
+
+namespace {
+
+constexpr std::array<std::array<bool, 4>, 8> led_patterns = {{
+ {1, 0, 0, 0},
+ {1, 1, 0, 0},
+ {1, 1, 1, 0},
+ {1, 1, 1, 1},
+ {1, 0, 0, 1},
+ {1, 0, 1, 0},
+ {1, 0, 1, 1},
+ {0, 1, 1, 0},
+}};
+
+void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index,
+ bool connected) {
+ Core::System& system{Core::System::GetInstance()};
+
+ if (!system.IsPoweredOn()) {
+ return;
+ }
+
+ Service::SM::ServiceManager& sm = system.ServiceManager();
+
+ auto& npad =
+ sm.GetService<Service::HID::Hid>("hid")
+ ->GetAppletResource()
+ ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
+
+ npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected);
+}
+
+// Returns true if the given controller type is compatible with the given parameters.
+bool IsControllerCompatible(Settings::ControllerType controller_type,
+ Core::Frontend::ControllerParameters parameters) {
+ switch (controller_type) {
+ case Settings::ControllerType::ProController:
+ return parameters.allow_pro_controller;
+ case Settings::ControllerType::DualJoyconDetached:
+ return parameters.allow_dual_joycons;
+ case Settings::ControllerType::LeftJoycon:
+ return parameters.allow_left_joycon;
+ case Settings::ControllerType::RightJoycon:
+ return parameters.allow_right_joycon;
+ case Settings::ControllerType::Handheld:
+ return parameters.enable_single_mode && parameters.allow_handheld;
+ default:
+ return false;
+ }
+}
+
+/// Maps the controller type combobox index to Controller Type enum
+constexpr Settings::ControllerType GetControllerTypeFromIndex(int index) {
+ switch (index) {
+ case 0:
+ default:
+ return Settings::ControllerType::ProController;
+ case 1:
+ return Settings::ControllerType::DualJoyconDetached;
+ case 2:
+ return Settings::ControllerType::LeftJoycon;
+ case 3:
+ return Settings::ControllerType::RightJoycon;
+ case 4:
+ return Settings::ControllerType::Handheld;
+ }
+}
+
+/// Maps the Controller Type enum to controller type combobox index
+constexpr int GetIndexFromControllerType(Settings::ControllerType type) {
+ switch (type) {
+ case Settings::ControllerType::ProController:
+ default:
+ return 0;
+ case Settings::ControllerType::DualJoyconDetached:
+ return 1;
+ case Settings::ControllerType::LeftJoycon:
+ return 2;
+ case Settings::ControllerType::RightJoycon:
+ return 3;
+ case Settings::ControllerType::Handheld:
+ return 4;
+ }
+}
+
+} // namespace
+
+QtControllerSelectorDialog::QtControllerSelectorDialog(
+ QWidget* parent, Core::Frontend::ControllerParameters parameters_,
+ InputCommon::InputSubsystem* input_subsystem_)
+ : QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()),
+ parameters(std::move(parameters_)), input_subsystem(input_subsystem_) {
+ ui->setupUi(this);
+
+ player_widgets = {
+ ui->widgetPlayer1, ui->widgetPlayer2, ui->widgetPlayer3, ui->widgetPlayer4,
+ ui->widgetPlayer5, ui->widgetPlayer6, ui->widgetPlayer7, ui->widgetPlayer8,
+ };
+
+ player_groupboxes = {
+ ui->groupPlayer1Connected, ui->groupPlayer2Connected, ui->groupPlayer3Connected,
+ ui->groupPlayer4Connected, ui->groupPlayer5Connected, ui->groupPlayer6Connected,
+ ui->groupPlayer7Connected, ui->groupPlayer8Connected,
+ };
+
+ connected_controller_icons = {
+ ui->controllerPlayer1, ui->controllerPlayer2, ui->controllerPlayer3, ui->controllerPlayer4,
+ ui->controllerPlayer5, ui->controllerPlayer6, ui->controllerPlayer7, ui->controllerPlayer8,
+ };
+
+ led_patterns_boxes = {{
+ {ui->checkboxPlayer1LED1, ui->checkboxPlayer1LED2, ui->checkboxPlayer1LED3,
+ ui->checkboxPlayer1LED4},
+ {ui->checkboxPlayer2LED1, ui->checkboxPlayer2LED2, ui->checkboxPlayer2LED3,
+ ui->checkboxPlayer2LED4},
+ {ui->checkboxPlayer3LED1, ui->checkboxPlayer3LED2, ui->checkboxPlayer3LED3,
+ ui->checkboxPlayer3LED4},
+ {ui->checkboxPlayer4LED1, ui->checkboxPlayer4LED2, ui->checkboxPlayer4LED3,
+ ui->checkboxPlayer4LED4},
+ {ui->checkboxPlayer5LED1, ui->checkboxPlayer5LED2, ui->checkboxPlayer5LED3,
+ ui->checkboxPlayer5LED4},
+ {ui->checkboxPlayer6LED1, ui->checkboxPlayer6LED2, ui->checkboxPlayer6LED3,
+ ui->checkboxPlayer6LED4},
+ {ui->checkboxPlayer7LED1, ui->checkboxPlayer7LED2, ui->checkboxPlayer7LED3,
+ ui->checkboxPlayer7LED4},
+ {ui->checkboxPlayer8LED1, ui->checkboxPlayer8LED2, ui->checkboxPlayer8LED3,
+ ui->checkboxPlayer8LED4},
+ }};
+
+ explain_text_labels = {
+ ui->labelPlayer1Explain, ui->labelPlayer2Explain, ui->labelPlayer3Explain,
+ ui->labelPlayer4Explain, ui->labelPlayer5Explain, ui->labelPlayer6Explain,
+ ui->labelPlayer7Explain, ui->labelPlayer8Explain,
+ };
+
+ emulated_controllers = {
+ ui->comboPlayer1Emulated, ui->comboPlayer2Emulated, ui->comboPlayer3Emulated,
+ ui->comboPlayer4Emulated, ui->comboPlayer5Emulated, ui->comboPlayer6Emulated,
+ ui->comboPlayer7Emulated, ui->comboPlayer8Emulated,
+ };
+
+ player_labels = {
+ ui->labelPlayer1, ui->labelPlayer2, ui->labelPlayer3, ui->labelPlayer4,
+ ui->labelPlayer5, ui->labelPlayer6, ui->labelPlayer7, ui->labelPlayer8,
+ };
+
+ connected_controller_labels = {
+ ui->labelConnectedPlayer1, ui->labelConnectedPlayer2, ui->labelConnectedPlayer3,
+ ui->labelConnectedPlayer4, ui->labelConnectedPlayer5, ui->labelConnectedPlayer6,
+ ui->labelConnectedPlayer7, ui->labelConnectedPlayer8,
+ };
+
+ connected_controller_checkboxes = {
+ ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected,
+ ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected,
+ ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
+ };
+
+ // Setup/load everything prior to setting up connections.
+ // This avoids unintentionally changing the states of elements while loading them in.
+ SetSupportedControllers();
+ DisableUnsupportedPlayers();
+ LoadConfiguration();
+
+ for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
+ SetExplainText(i);
+ UpdateControllerIcon(i);
+ UpdateLEDPattern(i);
+ UpdateBorderColor(i);
+
+ connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) {
+ if (checked) {
+ 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);
+ }
+ }
+ });
+
+ connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
+ [this, i](int) {
+ UpdateControllerIcon(i);
+ UpdateControllerState(i);
+ UpdateLEDPattern(i);
+ CheckIfParametersMet();
+ });
+
+ connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) {
+ player_groupboxes[i]->setChecked(state == Qt::Checked);
+ UpdateControllerIcon(i);
+ UpdateControllerState(i);
+ UpdateLEDPattern(i);
+ UpdateBorderColor(i);
+ CheckIfParametersMet();
+ });
+
+ if (i == 0) {
+ connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
+ [this](int index) {
+ UpdateDockedState(GetControllerTypeFromIndex(index) ==
+ Settings::ControllerType::Handheld);
+ });
+ }
+ }
+
+ connect(ui->inputConfigButton, &QPushButton::clicked, this,
+ &QtControllerSelectorDialog::CallConfigureInputDialog);
+
+ connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
+ &QtControllerSelectorDialog::ApplyConfiguration);
+
+ // If keep_controllers_connected is false, forcefully disconnect all controllers
+ if (!parameters.keep_controllers_connected) {
+ for (auto player : player_groupboxes) {
+ player->setChecked(false);
+ }
+ }
+
+ CheckIfParametersMet();
+
+ resize(0, 0);
+}
+
+QtControllerSelectorDialog::~QtControllerSelectorDialog() = default;
+
+void QtControllerSelectorDialog::ApplyConfiguration() {
+ // Update the controller state once more, just to be sure they are properly applied.
+ for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
+ UpdateControllerState(index);
+ }
+
+ const bool pre_docked_mode = Settings::values.use_docked_mode;
+ Settings::values.use_docked_mode = ui->radioDocked->isChecked();
+ OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode);
+
+ Settings::values.vibration_enabled = ui->vibrationGroup->isChecked();
+}
+
+void QtControllerSelectorDialog::LoadConfiguration() {
+ for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
+ const auto connected = Settings::values.players[index].connected ||
+ (index == 0 && Settings::values.players[8].connected);
+ player_groupboxes[index]->setChecked(connected);
+ connected_controller_checkboxes[index]->setChecked(connected);
+ emulated_controllers[index]->setCurrentIndex(
+ GetIndexFromControllerType(Settings::values.players[index].controller_type));
+ }
+
+ UpdateDockedState(Settings::values.players[8].connected);
+
+ ui->vibrationGroup->setChecked(Settings::values.vibration_enabled);
+}
+
+void QtControllerSelectorDialog::CallConfigureInputDialog() {
+ const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
+
+ ConfigureInputDialog dialog(this, max_supported_players, input_subsystem);
+
+ dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
+ Qt::WindowSystemMenuHint);
+ dialog.setWindowModality(Qt::WindowModal);
+ dialog.exec();
+
+ dialog.ApplyConfiguration();
+
+ LoadConfiguration();
+ CheckIfParametersMet();
+}
+
+void QtControllerSelectorDialog::CheckIfParametersMet() {
+ // Here, we check and validate the current configuration against all applicable parameters.
+ const auto num_connected_players = static_cast<int>(
+ std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
+ [this](const QGroupBox* player) { return player->isChecked(); }));
+
+ const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
+ const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
+
+ // First, check against the number of connected players.
+ if (num_connected_players < min_supported_players ||
+ num_connected_players > max_supported_players) {
+ parameters_met = false;
+ ui->buttonBox->setEnabled(parameters_met);
+ return;
+ }
+
+ // Next, check against all connected controllers.
+ const auto all_controllers_compatible = [this] {
+ for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
+ // Skip controllers that are not used, we only care about the currently connected ones.
+ if (!player_groupboxes[index]->isChecked() || !player_groupboxes[index]->isEnabled()) {
+ continue;
+ }
+
+ const auto compatible = IsControllerCompatible(
+ GetControllerTypeFromIndex(emulated_controllers[index]->currentIndex()),
+ parameters);
+
+ // If any controller is found to be incompatible, return false early.
+ if (!compatible) {
+ return false;
+ }
+ }
+
+ // Reaching here means all currently connected controllers are compatible.
+ return true;
+ }();
+
+ if (!all_controllers_compatible) {
+ parameters_met = false;
+ ui->buttonBox->setEnabled(parameters_met);
+ return;
+ }
+
+ parameters_met = true;
+ ui->buttonBox->setEnabled(parameters_met);
+}
+
+void QtControllerSelectorDialog::SetSupportedControllers() {
+ const QString theme = [this] {
+ if (QIcon::themeName().contains(QStringLiteral("dark"))) {
+ return QStringLiteral("_dark");
+ } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
+ return QStringLiteral("_midnight");
+ } else {
+ return QString{};
+ }
+ }();
+
+ if (parameters.enable_single_mode && parameters.allow_handheld) {
+ ui->controllerSupported1->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_handheld%0); ").arg(theme));
+ } else {
+ ui->controllerSupported1->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_handheld%0_disabled); ").arg(theme));
+ }
+
+ if (parameters.allow_dual_joycons) {
+ ui->controllerSupported2->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ").arg(theme));
+ } else {
+ ui->controllerSupported2->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_dual_joycon%0_disabled); ").arg(theme));
+ }
+
+ if (parameters.allow_left_joycon) {
+ ui->controllerSupported3->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_joycon_left%0); ").arg(theme));
+ } else {
+ ui->controllerSupported3->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_joycon_left%0_disabled); ").arg(theme));
+ }
+
+ if (parameters.allow_right_joycon) {
+ ui->controllerSupported4->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_joycon_right%0); ").arg(theme));
+ } else {
+ ui->controllerSupported4->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme));
+ }
+
+ if (parameters.allow_pro_controller) {
+ ui->controllerSupported5->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme));
+ } else {
+ ui->controllerSupported5->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_pro_controller%0_disabled); ")
+ .arg(theme));
+ }
+
+ // enable_single_mode overrides min_players and max_players.
+ if (parameters.enable_single_mode) {
+ ui->numberSupportedLabel->setText(QStringLiteral("1"));
+ return;
+ }
+
+ if (parameters.min_players == parameters.max_players) {
+ ui->numberSupportedLabel->setText(QStringLiteral("%1").arg(parameters.max_players));
+ } else {
+ ui->numberSupportedLabel->setText(
+ QStringLiteral("%1 - %2").arg(parameters.min_players).arg(parameters.max_players));
+ }
+}
+
+void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) {
+ if (!player_groupboxes[player_index]->isChecked()) {
+ connected_controller_icons[player_index]->setStyleSheet(QString{});
+ player_labels[player_index]->show();
+ return;
+ }
+
+ const QString stylesheet = [this, player_index] {
+ switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex())) {
+ case Settings::ControllerType::ProController:
+ return QStringLiteral("image: url(:/controller/applet_pro_controller%0); ");
+ case Settings::ControllerType::DualJoyconDetached:
+ return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ");
+ case Settings::ControllerType::LeftJoycon:
+ return QStringLiteral("image: url(:/controller/applet_joycon_left%0); ");
+ case Settings::ControllerType::RightJoycon:
+ return QStringLiteral("image: url(:/controller/applet_joycon_right%0); ");
+ case Settings::ControllerType::Handheld:
+ return QStringLiteral("image: url(:/controller/applet_handheld%0); ");
+ default:
+ return QString{};
+ }
+ }();
+
+ const QString theme = [this] {
+ if (QIcon::themeName().contains(QStringLiteral("dark"))) {
+ return QStringLiteral("_dark");
+ } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
+ return QStringLiteral("_midnight");
+ } else {
+ return QString{};
+ }
+ }();
+
+ connected_controller_icons[player_index]->setStyleSheet(stylesheet.arg(theme));
+ player_labels[player_index]->hide();
+}
+
+void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) {
+ auto& player = Settings::values.players[player_index];
+
+ player.controller_type =
+ GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex());
+ player.connected = player_groupboxes[player_index]->isChecked();
+
+ // Player 2-8
+ if (player_index != 0) {
+ UpdateController(player.controller_type, player_index, player.connected);
+ return;
+ }
+
+ // Player 1 and Handheld
+ auto& handheld = Settings::values.players[8];
+ // If Handheld is selected, copy all the settings from Player 1 to Handheld.
+ if (player.controller_type == Settings::ControllerType::Handheld) {
+ handheld = player;
+ handheld.connected = player_groupboxes[player_index]->isChecked();
+ player.connected = false; // Disconnect Player 1
+ } else {
+ player.connected = player_groupboxes[player_index]->isChecked();
+ handheld.connected = false; // Disconnect Handheld
+ }
+
+ UpdateController(player.controller_type, player_index, player.connected);
+ UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected);
+}
+
+void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) {
+ if (!player_groupboxes[player_index]->isChecked() ||
+ GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex()) ==
+ Settings::ControllerType::Handheld) {
+ led_patterns_boxes[player_index][0]->setChecked(false);
+ led_patterns_boxes[player_index][1]->setChecked(false);
+ led_patterns_boxes[player_index][2]->setChecked(false);
+ led_patterns_boxes[player_index][3]->setChecked(false);
+ return;
+ }
+
+ led_patterns_boxes[player_index][0]->setChecked(led_patterns[player_index][0]);
+ led_patterns_boxes[player_index][1]->setChecked(led_patterns[player_index][1]);
+ led_patterns_boxes[player_index][2]->setChecked(led_patterns[player_index][2]);
+ led_patterns_boxes[player_index][3]->setChecked(led_patterns[player_index][3]);
+}
+
+void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) {
+ if (!parameters.enable_border_color ||
+ player_index >= static_cast<std::size_t>(parameters.max_players) ||
+ player_groupboxes[player_index]->styleSheet().contains(QStringLiteral("QGroupBox"))) {
+ return;
+ }
+
+ player_groupboxes[player_index]->setStyleSheet(
+ player_groupboxes[player_index]->styleSheet().append(
+ QStringLiteral("QGroupBox#groupPlayer%1Connected:checked "
+ "{ border: 1px solid rgba(%2, %3, %4, %5); }")
+ .arg(player_index + 1)
+ .arg(parameters.border_colors[player_index][0])
+ .arg(parameters.border_colors[player_index][1])
+ .arg(parameters.border_colors[player_index][2])
+ .arg(parameters.border_colors[player_index][3])));
+}
+
+void QtControllerSelectorDialog::SetExplainText(std::size_t player_index) {
+ if (!parameters.enable_explain_text ||
+ player_index >= static_cast<std::size_t>(parameters.max_players)) {
+ return;
+ }
+
+ explain_text_labels[player_index]->setText(QString::fromStdString(
+ Common::StringFromFixedZeroTerminatedBuffer(parameters.explain_text[player_index].data(),
+ parameters.explain_text[player_index].size())));
+}
+
+void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) {
+ // Disallow changing the console mode if the controller type is handheld.
+ ui->radioDocked->setEnabled(!is_handheld);
+ ui->radioUndocked->setEnabled(!is_handheld);
+
+ ui->radioDocked->setChecked(Settings::values.use_docked_mode);
+ ui->radioUndocked->setChecked(!Settings::values.use_docked_mode);
+
+ // Also force into undocked mode if the controller type is handheld.
+ if (is_handheld) {
+ ui->radioUndocked->setChecked(true);
+ }
+}
+
+void QtControllerSelectorDialog::DisableUnsupportedPlayers() {
+ const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
+
+ switch (max_supported_players) {
+ case 0:
+ default:
+ UNREACHABLE();
+ return;
+ case 1:
+ ui->widgetSpacer->hide();
+ ui->widgetSpacer2->hide();
+ ui->widgetSpacer3->hide();
+ ui->widgetSpacer4->hide();
+ break;
+ case 2:
+ ui->widgetSpacer->hide();
+ ui->widgetSpacer2->hide();
+ ui->widgetSpacer3->hide();
+ break;
+ case 3:
+ ui->widgetSpacer->hide();
+ ui->widgetSpacer2->hide();
+ break;
+ case 4:
+ ui->widgetSpacer->hide();
+ break;
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ break;
+ }
+
+ for (std::size_t index = max_supported_players; index < NUM_PLAYERS; ++index) {
+ // Disconnect any unsupported players here and disable or hide them if applicable.
+ Settings::values.players[index].connected = false;
+ UpdateController(Settings::values.players[index].controller_type, index, false);
+ // Hide the player widgets when max_supported_controllers is less than or equal to 4.
+ if (max_supported_players <= 4) {
+ player_widgets[index]->hide();
+ }
+
+ // Disable and hide the following to prevent these from interaction.
+ player_widgets[index]->setDisabled(true);
+ connected_controller_checkboxes[index]->setDisabled(true);
+ connected_controller_labels[index]->hide();
+ connected_controller_checkboxes[index]->hide();
+ }
+}
+
+QtControllerSelector::QtControllerSelector(GMainWindow& parent) {
+ connect(this, &QtControllerSelector::MainWindowReconfigureControllers, &parent,
+ &GMainWindow::ControllerSelectorReconfigureControllers, Qt::QueuedConnection);
+ connect(&parent, &GMainWindow::ControllerSelectorReconfigureFinished, this,
+ &QtControllerSelector::MainWindowReconfigureFinished, Qt::QueuedConnection);
+}
+
+QtControllerSelector::~QtControllerSelector() = default;
+
+void QtControllerSelector::ReconfigureControllers(
+ std::function<void()> callback, Core::Frontend::ControllerParameters parameters) const {
+ this->callback = std::move(callback);
+ emit MainWindowReconfigureControllers(parameters);
+}
+
+void QtControllerSelector::MainWindowReconfigureFinished() {
+ // Acquire the HLE mutex
+ std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ callback();
+}
diff --git a/src/yuzu/applets/controller.h b/src/yuzu/applets/controller.h
new file mode 100644
index 000000000..2d6d588c6
--- /dev/null
+++ b/src/yuzu/applets/controller.h
@@ -0,0 +1,133 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <QDialog>
+#include "core/frontend/applets/controller.h"
+
+class GMainWindow;
+class QCheckBox;
+class QComboBox;
+class QDialogButtonBox;
+class QGroupBox;
+class QLabel;
+
+namespace InputCommon {
+class InputSubsystem;
+}
+
+namespace Ui {
+class QtControllerSelectorDialog;
+}
+
+class QtControllerSelectorDialog final : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit QtControllerSelectorDialog(QWidget* parent,
+ Core::Frontend::ControllerParameters parameters_,
+ InputCommon::InputSubsystem* input_subsystem_);
+ ~QtControllerSelectorDialog() override;
+
+private:
+ // Applies the current configuration.
+ void ApplyConfiguration();
+
+ // Loads the current input configuration into the frontend applet.
+ void LoadConfiguration();
+
+ // Initializes the "Configure Input" Dialog.
+ void CallConfigureInputDialog();
+
+ // Checks the current configuration against the given parameters and
+ // sets the value of parameters_met.
+ void CheckIfParametersMet();
+
+ // Sets the controller icons for "Supported Controller Types".
+ void SetSupportedControllers();
+
+ // Updates the controller icons per player.
+ void UpdateControllerIcon(std::size_t player_index);
+
+ // Updates the controller state (type and connection status) per player.
+ void UpdateControllerState(std::size_t player_index);
+
+ // Updates the LED pattern per player.
+ void UpdateLEDPattern(std::size_t player_index);
+
+ // Updates the border color per player.
+ void UpdateBorderColor(std::size_t player_index);
+
+ // Sets the "Explain Text" per player.
+ void SetExplainText(std::size_t player_index);
+
+ // Updates the console mode.
+ void UpdateDockedState(bool is_handheld);
+
+ // Disables and disconnects unsupported players based on the given parameters.
+ void DisableUnsupportedPlayers();
+
+ std::unique_ptr<Ui::QtControllerSelectorDialog> ui;
+
+ // Parameters sent in from the backend HLE applet.
+ Core::Frontend::ControllerParameters parameters;
+
+ InputCommon::InputSubsystem* input_subsystem;
+
+ // This is true if and only if all parameters are met. Otherwise, this is false.
+ // This determines whether the "OK" button can be clicked to exit the applet.
+ bool parameters_met{false};
+
+ static constexpr std::size_t NUM_PLAYERS = 8;
+
+ // Widgets encapsulating the groupboxes and comboboxes per player.
+ std::array<QWidget*, NUM_PLAYERS> player_widgets;
+
+ // Groupboxes encapsulating the controller icons and LED patterns per player.
+ std::array<QGroupBox*, NUM_PLAYERS> player_groupboxes;
+
+ // Icons for currently connected controllers/players.
+ std::array<QWidget*, NUM_PLAYERS> connected_controller_icons;
+
+ // Labels that represent the player numbers in place of the controller icons.
+ std::array<QLabel*, NUM_PLAYERS> player_labels;
+
+ // LED patterns for currently connected controllers/players.
+ std::array<std::array<QCheckBox*, 4>, NUM_PLAYERS> led_patterns_boxes;
+
+ // Labels representing additional information known as "Explain Text" per player.
+ std::array<QLabel*, NUM_PLAYERS> explain_text_labels;
+
+ // Comboboxes with a list of emulated controllers per player.
+ std::array<QComboBox*, NUM_PLAYERS> emulated_controllers;
+
+ // Labels representing the number of connected controllers
+ // above the "Connected Controllers" checkboxes.
+ std::array<QLabel*, NUM_PLAYERS> connected_controller_labels;
+
+ // Checkboxes representing the "Connected Controllers".
+ std::array<QCheckBox*, NUM_PLAYERS> connected_controller_checkboxes;
+};
+
+class QtControllerSelector final : public QObject, public Core::Frontend::ControllerApplet {
+ Q_OBJECT
+
+public:
+ explicit QtControllerSelector(GMainWindow& parent);
+ ~QtControllerSelector() override;
+
+ void ReconfigureControllers(std::function<void()> callback,
+ Core::Frontend::ControllerParameters parameters) const override;
+
+signals:
+ void MainWindowReconfigureControllers(Core::Frontend::ControllerParameters parameters) const;
+
+private:
+ void MainWindowReconfigureFinished();
+
+ mutable std::function<void()> callback;
+};
diff --git a/src/yuzu/applets/controller.ui b/src/yuzu/applets/controller.ui
new file mode 100644
index 000000000..c4108a979
--- /dev/null
+++ b/src/yuzu/applets/controller.ui
@@ -0,0 +1,2672 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QtControllerSelectorDialog</class>
+ <widget class="QDialog" name="QtControllerSelectorDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>839</width>
+ <height>630</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Controller Applet</string>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout" stretch="0">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="mainControllerApplet" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,3,0">
+ <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>
+ <widget class="QWidget" name="topControllerApplet" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>10</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>10</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>10</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllersSupported" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_21">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="controllersSupportedLabel">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Supported Controller Types:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllerSupported1" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllerSupported2" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllerSupported3" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllerSupported4" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllerSupported5" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="playersSupported" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_20">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>16</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="maxSupportedLabel">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Players:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="numberSupportedLabel">
+ <property name="font">
+ <font>
+ <pointsize>14</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>1 - 8</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="middleControllerApplet" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <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>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <item row="1" column="7">
+ <widget class="QWidget" name="widgetPlayer4" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_27">
+ <property name="spacing">
+ <number>5</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="groupPlayer4Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_7" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer4" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_15">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer4">
+ <property name="text">
+ <string>P4</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player4LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_10">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer4LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer4LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer4LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer4LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player4Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_39">
+ <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>
+ <widget class="QLabel" name="labelPlayer4Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer4Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer4Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QWidget" name="widgetPlayer2" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_29">
+ <property name="spacing">
+ <number>5</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="groupPlayer2Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer2" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_13">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer2">
+ <property name="text">
+ <string>P2</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player2LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_8">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer2LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer2LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer2LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer2LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player2Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_37">
+ <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>
+ <widget class="QLabel" name="labelPlayer2Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer2Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer2Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QWidget" name="widgetPlayer1" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_30">
+ <property name="spacing">
+ <number>5</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="groupPlayer1Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer1" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_12">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer1">
+ <property name="text">
+ <string>P1</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player1LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer1LED1">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer1LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer1LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer1LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player1Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_36">
+ <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>
+ <widget class="QLabel" name="labelPlayer1Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer1Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Handheld</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer1Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="8">
+ <widget class="QWidget" name="widgetSpacer2" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>25</width>
+ <height>0</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_31">
+ <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="controllerAppletHorizontalSpacer8">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>25</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="4">
+ <widget class="QWidget" name="widgetSpacer4" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_33">
+ <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="controllerAppletHorizontalSpacer6">
+ <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 row="1" column="6">
+ <widget class="QWidget" name="widgetSpacer3" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_32">
+ <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="controllerAppletHorizontalSpacer7">
+ <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 row="1" column="5">
+ <widget class="QWidget" name="widgetPlayer3" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_28">
+ <property name="spacing">
+ <number>5</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="groupPlayer3Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer3" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_14">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer3">
+ <property name="text">
+ <string>P3</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player3LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_9">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer3LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer3LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer3LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer3LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player3Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_38">
+ <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>
+ <widget class="QLabel" name="labelPlayer3Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer3Emulated">
+ <property name="editable">
+ <bool>false</bool>
+ </property>
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer3Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QWidget" name="widgetSpacer5" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>25</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_34">
+ <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="controllerAppletVerticalSpacer3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>25</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="6" column="5">
+ <widget class="QWidget" name="widgetPlayer7" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_25">
+ <property name="spacing">
+ <number>5</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="groupPlayer7Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_10" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer7" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_18">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer7">
+ <property name="text">
+ <string>P7</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player7LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_13">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer7LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer7LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer7LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer7LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player7Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_42">
+ <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>
+ <widget class="QLabel" name="labelPlayer7Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer7Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer7Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="6" column="7">
+ <widget class="QWidget" name="widgetPlayer8" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_26">
+ <property name="spacing">
+ <number>5</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="groupPlayer8Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_11" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer8" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_19">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer8">
+ <property name="text">
+ <string>P8</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player8LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_14">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer8LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer8LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer8LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer8LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player8Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_35">
+ <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>
+ <widget class="QLabel" name="labelPlayer8Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer8Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer8Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QWidget" name="widgetPlayer5" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_23">
+ <property name="spacing">
+ <number>5</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="groupPlayer5Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_8" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer5" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_16">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer5">
+ <property name="text">
+ <string>P5</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player5LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_11">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer5LED1">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer5LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer5LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer5LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player5Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_40">
+ <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>
+ <widget class="QLabel" name="labelPlayer5Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer5Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer5Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="6" column="3">
+ <widget class="QWidget" name="widgetPlayer6" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_24">
+ <property name="spacing">
+ <number>5</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="groupPlayer6Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_9" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer6" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_17">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer6">
+ <property name="text">
+ <string>P6</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player6LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_12">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer6LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer6LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer6LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer6LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player6Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_41">
+ <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>
+ <widget class="QLabel" name="labelPlayer6Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer6Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer6Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="10" column="1">
+ <widget class="QWidget" name="widgetSpacer" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>25</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_22">
+ <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="controllerAppletVerticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>25</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QWidget" name="widgetSpacer6" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_15">
+ <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="controllerAppletHorizontalSpacer5">
+ <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 row="1" column="0">
+ <widget class="QWidget" name="widgetSpacer7" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>25</width>
+ <height>0</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_16">
+ <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="controllerAppletHorizontalSpacer4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>25</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QWidget" name="widgetSpacer9" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>25</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_17">
+ <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="controllerAppletVerticalSpacer2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>25</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="bottomControllerApplet" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <property name="spacing">
+ <number>15</number>
+ </property>
+ <property name="leftMargin">
+ <number>15</number>
+ </property>
+ <property name="topMargin">
+ <number>8</number>
+ </property>
+ <property name="rightMargin">
+ <number>15</number>
+ </property>
+ <property name="bottomMargin">
+ <number>15</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="handheldGroup">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Console Mode</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QRadioButton" name="radioDocked">
+ <property name="text">
+ <string>Docked</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="radioUndocked">
+ <property name="text">
+ <string>Undocked</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroup">
+ <property name="title">
+ <string>Vibration</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <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="QSpinBox" name="vibrationSpin">
+ <property name="minimumSize">
+ <size>
+ <width>65</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>65</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>200</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="motionGroup">
+ <property name="title">
+ <string>Motion</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <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="motionButton">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="inputConfigGroup">
+ <property name="title">
+ <string>Input Config</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <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="inputConfigButton">
+ <property name="maximumSize">
+ <size>
+ <width>65</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Open</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="connectedControllers" native="true">
+ <layout class="QGridLayout" name="gridLayout_2">
+ <property name="leftMargin">
+ <number>5</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item row="1" column="4">
+ <widget class="QCheckBox" name="checkboxPlayer4Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelControllers">
+ <property name="text">
+ <string>Controllers</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QCheckBox" name="checkboxPlayer2Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="labelConnectedPlayer1">
+ <property name="text">
+ <string>1</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QCheckBox" name="checkboxPlayer3Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QCheckBox" name="checkboxPlayer1Connected">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="labelConnectedPlayer2">
+ <property name="text">
+ <string>2</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="4">
+ <widget class="QLabel" name="labelConnectedPlayer4">
+ <property name="text">
+ <string>4</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QLabel" name="labelConnectedPlayer3">
+ <property name="text">
+ <string>3</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelConnected">
+ <property name="text">
+ <string>Connected</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="7">
+ <widget class="QCheckBox" name="checkboxPlayer7Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="5">
+ <widget class="QLabel" name="labelConnectedPlayer5">
+ <property name="text">
+ <string>5</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="6">
+ <widget class="QCheckBox" name="checkboxPlayer6Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="7">
+ <widget class="QLabel" name="labelConnectedPlayer7">
+ <property name="text">
+ <string>7</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="5">
+ <widget class="QCheckBox" name="checkboxPlayer5Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="6">
+ <widget class="QLabel" name="labelConnectedPlayer6">
+ <property name="text">
+ <string>6</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="8">
+ <widget class="QLabel" name="labelConnectedPlayer8">
+ <property name="text">
+ <string>8</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="8">
+ <widget class="QCheckBox" name="checkboxPlayer8Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignBottom">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>QtControllerSelectorDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/applets/profile_select.cpp b/src/yuzu/applets/profile_select.cpp
index 4bc8ee726..dca8835ed 100644
--- a/src/yuzu/applets/profile_select.cpp
+++ b/src/yuzu/applets/profile_select.cpp
@@ -26,7 +26,7 @@ QString FormatUserEntryText(const QString& username, Common::UUID uuid) {
}
QString GetImagePath(Common::UUID uuid) {
- const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
+ const auto path = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
return QString::fromStdString(path);
}
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 5738787ac..caa2d06d3 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -30,6 +30,7 @@
#include "common/scope_exit.h"
#include "core/core.h"
#include "core/frontend/framebuffer_layout.h"
+#include "core/hle/kernel/process.h"
#include "core/settings.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
@@ -63,7 +64,8 @@ void EmuThread::run() {
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
system.Renderer().Rasterizer().LoadDiskResources(
- stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
+ system.CurrentProcess()->GetTitleID(), stop_run,
+ [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
emit LoadProgress(stage, value, total);
});
@@ -304,8 +306,9 @@ static Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow*
return wsi;
}
-GRenderWindow::GRenderWindow(GMainWindow* parent_, EmuThread* emu_thread_)
- : QWidget(parent_), emu_thread(emu_thread_) {
+GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
+ std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_)
+ : QWidget(parent), emu_thread(emu_thread_), input_subsystem{std::move(input_subsystem_)} {
setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
.arg(QString::fromUtf8(Common::g_build_name),
QString::fromUtf8(Common::g_scm_branch),
@@ -314,15 +317,15 @@ GRenderWindow::GRenderWindow(GMainWindow* parent_, EmuThread* emu_thread_)
auto layout = new QHBoxLayout(this);
layout->setMargin(0);
setLayout(layout);
- InputCommon::Init();
+ input_subsystem->Initialize();
this->setMouseTracking(true);
- connect(this, &GRenderWindow::FirstFrameDisplayed, parent_, &GMainWindow::OnLoadComplete);
+ connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
}
GRenderWindow::~GRenderWindow() {
- InputCommon::Shutdown();
+ input_subsystem->Shutdown();
}
void GRenderWindow::PollEvents() {
@@ -391,11 +394,11 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
}
void GRenderWindow::keyPressEvent(QKeyEvent* event) {
- InputCommon::GetKeyboard()->PressKey(event->key());
+ input_subsystem->GetKeyboard()->PressKey(event->key());
}
void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
- InputCommon::GetKeyboard()->ReleaseKey(event->key());
+ input_subsystem->GetKeyboard()->ReleaseKey(event->key());
}
void GRenderWindow::mousePressEvent(QMouseEvent* event) {
@@ -409,7 +412,7 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) {
const auto [x, y] = ScaleTouch(pos);
this->TouchPressed(x, y);
} else if (event->button() == Qt::RightButton) {
- InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
+ input_subsystem->GetMotionEmu()->BeginTilt(pos.x(), pos.y());
}
QWidget::mousePressEvent(event);
}
@@ -423,7 +426,7 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
auto pos = event->pos();
const auto [x, y] = ScaleTouch(pos);
this->TouchMoved(x, y);
- InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
+ input_subsystem->GetMotionEmu()->Tilt(pos.x(), pos.y());
QWidget::mouseMoveEvent(event);
}
@@ -436,7 +439,7 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
if (event->button() == Qt::LeftButton) {
this->TouchReleased();
} else if (event->button() == Qt::RightButton) {
- InputCommon::GetMotionEmu()->EndTilt();
+ input_subsystem->GetMotionEmu()->EndTilt();
}
}
@@ -451,7 +454,7 @@ void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) {
int active_points = 0;
// average all active touch points
- for (const auto tp : event->touchPoints()) {
+ for (const auto& tp : event->touchPoints()) {
if (tp.state() & (Qt::TouchPointPressed | Qt::TouchPointMoved | Qt::TouchPointStationary)) {
active_points++;
pos += tp.pos();
@@ -485,7 +488,7 @@ bool GRenderWindow::event(QEvent* event) {
void GRenderWindow::focusOutEvent(QFocusEvent* event) {
QWidget::focusOutEvent(event);
- InputCommon::GetKeyboard()->ReleaseAllKeys();
+ input_subsystem->GetKeyboard()->ReleaseAllKeys();
}
void GRenderWindow::resizeEvent(QResizeEvent* event) {
@@ -567,7 +570,7 @@ void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_p
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
renderer.RequestScreenshot(
screenshot_image.bits(),
- [=] {
+ [=, this] {
const std::string std_screenshot_path = screenshot_path.toStdString();
if (screenshot_image.mirrored(false, true).save(screenshot_path)) {
LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path);
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 6c59b4d5c..ca35cf831 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -6,6 +6,7 @@
#include <atomic>
#include <condition_variable>
+#include <memory>
#include <mutex>
#include <QImage>
@@ -23,6 +24,10 @@ class QKeyEvent;
class QTouchEvent;
class QStringList;
+namespace InputCommon {
+class InputSubsystem;
+}
+
namespace VideoCore {
enum class LoadCallbackStage;
}
@@ -121,7 +126,8 @@ class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow {
Q_OBJECT
public:
- GRenderWindow(GMainWindow* parent, EmuThread* emu_thread);
+ explicit GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
+ std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_);
~GRenderWindow() override;
// EmuWindow implementation.
@@ -183,6 +189,7 @@ private:
QStringList GetUnsupportedGLExtensions() const;
EmuThread* emu_thread;
+ std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
// Main context that will be shared with all other contexts that are requested.
// If this is used in a shared context setting, then this should not be used directly, but
diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp
index 5477f050c..649912557 100644
--- a/src/yuzu/compatdb.cpp
+++ b/src/yuzu/compatdb.cpp
@@ -54,7 +54,8 @@ void CompatDB::Submit() {
back();
LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId());
Core::System::GetInstance().TelemetrySession().AddField(
- Telemetry::FieldType::UserFeedback, "Compatibility", compatibility->checkedId());
+ Common::Telemetry::FieldType::UserFeedback, "Compatibility",
+ compatibility->checkedId());
button(NextButton)->setEnabled(false);
button(NextButton)->setText(tr("Submitting"));
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 59a193edd..2bc55a26a 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -6,17 +6,18 @@
#include <QKeySequence>
#include <QSettings>
#include "common/file_util.h"
-#include "configure_input_simple.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "input_common/main.h"
#include "input_common/udp/client.h"
#include "yuzu/configuration/config.h"
+namespace FS = Common::FS;
+
Config::Config(const std::string& config_file, bool is_global) {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
- qt_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + config_file;
- FileUtil::CreateFullPath(qt_config_loc);
+ qt_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + config_file;
+ FS::CreateFullPath(qt_config_loc);
qt_config =
std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
global = is_global;
@@ -30,29 +31,31 @@ Config::~Config() {
}
const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = {
- Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_3, Qt::Key_4, Qt::Key_Q,
- Qt::Key_W, Qt::Key_1, Qt::Key_2, Qt::Key_N, Qt::Key_M, Qt::Key_F, Qt::Key_T,
- Qt::Key_H, Qt::Key_G, Qt::Key_Left, Qt::Key_Up, Qt::Key_Right, Qt::Key_Down, Qt::Key_J,
- Qt::Key_I, Qt::Key_L, Qt::Key_K, Qt::Key_D, Qt::Key_C, Qt::Key_B, Qt::Key_V,
+ Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_3, Qt::Key_4, Qt::Key_Q,
+ Qt::Key_W, Qt::Key_1, Qt::Key_2, Qt::Key_N, Qt::Key_M, Qt::Key_F, Qt::Key_T,
+ Qt::Key_H, Qt::Key_G, Qt::Key_D, Qt::Key_C, Qt::Key_B, Qt::Key_V,
};
-const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{
+const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{
{
Qt::Key_Up,
Qt::Key_Down,
Qt::Key_Left,
Qt::Key_Right,
- Qt::Key_E,
},
{
Qt::Key_I,
Qt::Key_K,
Qt::Key_J,
Qt::Key_L,
- Qt::Key_R,
},
}};
+const std::array<int, 2> Config::default_stick_mod = {
+ Qt::Key_E,
+ Qt::Key_R,
+};
+
const std::array<int, Settings::NativeMouseButton::NumMouseButtons> Config::default_mouse_buttons =
{
Qt::Key_BracketLeft, Qt::Key_BracketRight, Qt::Key_Apostrophe, Qt::Key_Minus, Qt::Key_Equal,
@@ -241,10 +244,10 @@ void Config::ReadPlayerValues() {
player.connected =
ReadSetting(QStringLiteral("player_%1_connected").arg(p), false).toBool();
- player.type = static_cast<Settings::ControllerType>(
+ player.controller_type = static_cast<Settings::ControllerType>(
qt_config
->value(QStringLiteral("player_%1_type").arg(p),
- static_cast<u8>(Settings::ControllerType::DualJoycon))
+ static_cast<u8>(Settings::ControllerType::ProController))
.toUInt());
player.body_color_left = qt_config
@@ -284,7 +287,7 @@ void Config::ReadPlayerValues() {
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_analogs[i][4], 0.5f);
+ default_analogs[i][3], default_stick_mod[i], 0.5f);
auto& player_analogs = player.analogs[i];
player_analogs = qt_config
@@ -298,12 +301,6 @@ void Config::ReadPlayerValues() {
}
}
}
-
- std::stable_partition(
- Settings::values.players.begin(),
- Settings::values.players.begin() +
- Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD),
- [](const auto& player) { return player.connected; });
}
void Config::ReadDebugValues() {
@@ -328,7 +325,7 @@ void Config::ReadDebugValues() {
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_analogs[i][4], 0.5f);
+ 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
@@ -395,13 +392,6 @@ void Config::ReadTouchscreenValues() {
ReadSetting(QStringLiteral("touchscreen_diameter_y"), 15).toUInt();
}
-void Config::ApplyDefaultProfileIfInputInvalid() {
- if (!std::any_of(Settings::values.players.begin(), Settings::values.players.end(),
- [](const Settings::PlayerInput& in) { return in.connected; })) {
- ApplyInputProfileConfiguration(UISettings::values.profile_index);
- }
-}
-
void Config::ReadAudioValues() {
qt_config->beginGroup(QStringLiteral("Audio"));
@@ -430,12 +420,64 @@ void Config::ReadControlValues() {
ReadKeyboardValues();
ReadMouseValues();
ReadTouchscreenValues();
+ ReadMotionTouchValues();
+
+ Settings::values.vibration_enabled =
+ ReadSetting(QStringLiteral("vibration_enabled"), true).toBool();
+ Settings::values.use_docked_mode =
+ ReadSetting(QStringLiteral("use_docked_mode"), false).toBool();
+
+ 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.motion_device =
ReadSetting(QStringLiteral("motion_device"),
QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"))
.toString()
.toStdString();
+ Settings::values.touch_device =
+ ReadSetting(QStringLiteral("touch_device"), QStringLiteral("engine:emu_window"))
+ .toString()
+ .toStdString();
+ Settings::values.use_touch_from_button =
+ ReadSetting(QStringLiteral("use_touch_from_button"), false).toBool();
+ Settings::values.touch_from_button_map_index =
+ ReadSetting(QStringLiteral("touch_from_button_map"), 0).toInt();
+ Settings::values.touch_from_button_map_index =
+ std::clamp(Settings::values.touch_from_button_map_index, 0, num_touch_from_button_maps - 1);
Settings::values.udp_input_address =
ReadSetting(QStringLiteral("udp_input_address"),
QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR))
@@ -446,10 +488,6 @@ void Config::ReadControlValues() {
.toInt());
Settings::values.udp_pad_index =
static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt());
- Settings::values.use_docked_mode =
- ReadSetting(QStringLiteral("use_docked_mode"), false).toBool();
-
- qt_config->endGroup();
}
void Config::ReadCoreValues() {
@@ -464,47 +502,42 @@ void Config::ReadDataStorageValues() {
qt_config->beginGroup(QStringLiteral("Data Storage"));
Settings::values.use_virtual_sd = ReadSetting(QStringLiteral("use_virtual_sd"), true).toBool();
- FileUtil::GetUserPath(
- FileUtil::UserPath::NANDDir,
- qt_config
- ->value(QStringLiteral("nand_directory"),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)))
- .toString()
- .toStdString());
- FileUtil::GetUserPath(
- FileUtil::UserPath::SDMCDir,
- qt_config
- ->value(QStringLiteral("sdmc_directory"),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)))
- .toString()
- .toStdString());
- FileUtil::GetUserPath(
- FileUtil::UserPath::LoadDir,
- qt_config
- ->value(QStringLiteral("load_directory"),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)))
- .toString()
- .toStdString());
- FileUtil::GetUserPath(
- FileUtil::UserPath::DumpDir,
- qt_config
- ->value(QStringLiteral("dump_directory"),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)))
- .toString()
- .toStdString());
- FileUtil::GetUserPath(
- FileUtil::UserPath::CacheDir,
- qt_config
- ->value(QStringLiteral("cache_directory"),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)))
- .toString()
- .toStdString());
+ FS::GetUserPath(FS::UserPath::NANDDir,
+ qt_config
+ ->value(QStringLiteral("nand_directory"),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir)))
+ .toString()
+ .toStdString());
+ FS::GetUserPath(FS::UserPath::SDMCDir,
+ qt_config
+ ->value(QStringLiteral("sdmc_directory"),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir)))
+ .toString()
+ .toStdString());
+ FS::GetUserPath(FS::UserPath::LoadDir,
+ qt_config
+ ->value(QStringLiteral("load_directory"),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir)))
+ .toString()
+ .toStdString());
+ FS::GetUserPath(FS::UserPath::DumpDir,
+ qt_config
+ ->value(QStringLiteral("dump_directory"),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)))
+ .toString()
+ .toStdString());
+ FS::GetUserPath(FS::UserPath::CacheDir,
+ qt_config
+ ->value(QStringLiteral("cache_directory"),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir)))
+ .toString()
+ .toStdString());
Settings::values.gamecard_inserted =
ReadSetting(QStringLiteral("gamecard_inserted"), false).toBool();
Settings::values.gamecard_current_game =
ReadSetting(QStringLiteral("gamecard_current_game"), false).toBool();
Settings::values.gamecard_path =
- ReadSetting(QStringLiteral("gamecard_path"), QStringLiteral("")).toString().toStdString();
+ ReadSetting(QStringLiteral("gamecard_path"), QString{}).toString().toStdString();
qt_config->endGroup();
}
@@ -518,7 +551,7 @@ void Config::ReadDebuggingValues() {
Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool();
Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt();
Settings::values.program_args =
- ReadSetting(QStringLiteral("program_args"), QStringLiteral("")).toString().toStdString();
+ ReadSetting(QStringLiteral("program_args"), QString{}).toString().toStdString();
Settings::values.dump_exefs = ReadSetting(QStringLiteral("dump_exefs"), false).toBool();
Settings::values.dump_nso = ReadSetting(QStringLiteral("dump_nso"), false).toBool();
Settings::values.reporting_services =
@@ -551,8 +584,7 @@ void Config::ReadDisabledAddOnValues() {
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"), QStringLiteral("")).toString().toStdString());
+ out.push_back(ReadSetting(QStringLiteral("d"), QString{}).toString().toStdString());
}
qt_config->endArray();
Settings::values.disabled_addons.insert_or_assign(title_id, out);
@@ -578,7 +610,6 @@ void Config::ReadPathValues() {
UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString();
UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString();
- UISettings::values.screenshot_path = ReadSetting(QStringLiteral("screenshotPath")).toString();
UISettings::values.game_dir_deprecated =
ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString();
UISettings::values.game_dir_deprecated_deepscan =
@@ -639,6 +670,11 @@ void Config::ReadCpuValues() {
ReadSetting(QStringLiteral("cpuopt_misc_ir"), true).toBool();
Settings::values.cpuopt_reduce_misalign_checks =
ReadSetting(QStringLiteral("cpuopt_reduce_misalign_checks"), true).toBool();
+
+ Settings::values.cpuopt_unsafe_unfuse_fma =
+ ReadSetting(QStringLiteral("cpuopt_unsafe_unfuse_fma"), true).toBool();
+ Settings::values.cpuopt_unsafe_reduce_fp_error =
+ ReadSetting(QStringLiteral("cpuopt_unsafe_reduce_fp_error"), true).toBool();
}
qt_config->endGroup();
@@ -666,8 +702,6 @@ void Config::ReadRendererValues() {
QStringLiteral("use_asynchronous_shaders"), false);
ReadSettingGlobal(Settings::values.use_fast_gpu_time, QStringLiteral("use_fast_gpu_time"),
true);
- ReadSettingGlobal(Settings::values.force_30fps_mode, QStringLiteral("force_30fps_mode"), false);
-
ReadSettingGlobal(Settings::values.bg_red, QStringLiteral("bg_red"), 0.0);
ReadSettingGlobal(Settings::values.bg_green, QStringLiteral("bg_green"), 0.0);
ReadSettingGlobal(Settings::values.bg_blue, QStringLiteral("bg_blue"), 0.0);
@@ -675,6 +709,22 @@ void Config::ReadRendererValues() {
qt_config->endGroup();
}
+void Config::ReadScreenshotValues() {
+ qt_config->beginGroup(QStringLiteral("Screenshots"));
+
+ UISettings::values.enable_screenshot_save_as =
+ ReadSetting(QStringLiteral("enable_screenshot_save_as"), true).toBool();
+ FS::GetUserPath(
+ FS::UserPath::ScreenshotsDir,
+ qt_config
+ ->value(QStringLiteral("screenshot_path"),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::ScreenshotsDir)))
+ .toString()
+ .toStdString());
+
+ qt_config->endGroup();
+}
+
void Config::ReadShortcutValues() {
qt_config->beginGroup(QStringLiteral("Shortcuts"));
@@ -756,6 +806,7 @@ void Config::ReadUIValues() {
ReadUIGamelistValues();
ReadUILayoutValues();
ReadPathValues();
+ ReadScreenshotValues();
ReadShortcutValues();
UISettings::values.single_window_mode =
@@ -772,14 +823,11 @@ void Config::ReadUIValues() {
UISettings::values.first_start = ReadSetting(QStringLiteral("firstStart"), true).toBool();
UISettings::values.callout_flags = ReadSetting(QStringLiteral("calloutFlags"), 0).toUInt();
UISettings::values.show_console = ReadSetting(QStringLiteral("showConsole"), false).toBool();
- UISettings::values.profile_index = ReadSetting(QStringLiteral("profileIndex"), 0).toUInt();
UISettings::values.pause_when_in_background =
ReadSetting(QStringLiteral("pauseWhenInBackground"), false).toBool();
UISettings::values.hide_mouse =
ReadSetting(QStringLiteral("hideInactiveMouse"), false).toBool();
- ApplyDefaultProfileIfInputInvalid();
-
qt_config->endGroup();
}
@@ -853,8 +901,9 @@ void Config::SavePlayerValues() {
const auto& player = Settings::values.players[p];
WriteSetting(QStringLiteral("player_%1_connected").arg(p), player.connected, false);
- WriteSetting(QStringLiteral("player_%1_type").arg(p), static_cast<u8>(player.type),
- static_cast<u8>(Settings::ControllerType::DualJoycon));
+ WriteSetting(QStringLiteral("player_%1_type").arg(p),
+ static_cast<u8>(player.controller_type),
+ static_cast<u8>(Settings::ControllerType::ProController));
WriteSetting(QStringLiteral("player_%1_body_color_left").arg(p), player.body_color_left,
Settings::JOYCON_BODY_NEON_BLUE);
@@ -876,7 +925,7 @@ void Config::SavePlayerValues() {
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_analogs[i][4], 0.5f);
+ default_analogs[i][3], default_stick_mod[i], 0.5f);
WriteSetting(QStringLiteral("player_%1_").arg(p) +
QString::fromStdString(Settings::NativeAnalog::mapping[i]),
QString::fromStdString(player.analogs[i]),
@@ -897,7 +946,7 @@ void Config::SaveDebugValues() {
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_analogs[i][4], 0.5f);
+ 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]),
@@ -931,6 +980,43 @@ void Config::SaveTouchscreenValues() {
WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15);
}
+void Config::SaveMotionTouchValues() {
+ WriteSetting(QStringLiteral("motion_device"),
+ QString::fromStdString(Settings::values.motion_device),
+ QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
+ WriteSetting(QStringLiteral("touch_device"),
+ QString::fromStdString(Settings::values.touch_device),
+ QStringLiteral("engine:emu_window"));
+ WriteSetting(QStringLiteral("use_touch_from_button"), Settings::values.use_touch_from_button,
+ false);
+ WriteSetting(QStringLiteral("touch_from_button_map"),
+ Settings::values.touch_from_button_map_index, 0);
+ WriteSetting(QStringLiteral("udp_input_address"),
+ QString::fromStdString(Settings::values.udp_input_address),
+ QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR));
+ WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port,
+ InputCommon::CemuhookUDP::DEFAULT_PORT);
+ WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0);
+
+ 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::SaveValues() {
if (global) {
SaveControlValues();
@@ -973,17 +1059,16 @@ void Config::SaveControlValues() {
SaveDebugValues();
SaveMouseValues();
SaveTouchscreenValues();
+ SaveMotionTouchValues();
+ WriteSetting(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true);
WriteSetting(QStringLiteral("motion_device"),
QString::fromStdString(Settings::values.motion_device),
QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
+ WriteSetting(QStringLiteral("touch_device"),
+ QString::fromStdString(Settings::values.touch_device),
+ QStringLiteral("engine:emu_window"));
WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false);
- WriteSetting(QStringLiteral("udp_input_address"),
- QString::fromStdString(Settings::values.udp_input_address),
- QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR));
- WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port,
- InputCommon::CemuhookUDP::DEFAULT_PORT);
- WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0);
WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false);
qt_config->endGroup();
@@ -1002,25 +1087,25 @@ void Config::SaveDataStorageValues() {
WriteSetting(QStringLiteral("use_virtual_sd"), Settings::values.use_virtual_sd, true);
WriteSetting(QStringLiteral("nand_directory"),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir)),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir)));
WriteSetting(QStringLiteral("sdmc_directory"),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir)),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir)));
WriteSetting(QStringLiteral("load_directory"),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)));
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir)),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir)));
WriteSetting(QStringLiteral("dump_directory"),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)));
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)));
WriteSetting(QStringLiteral("cache_directory"),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)),
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)));
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir)),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir)));
WriteSetting(QStringLiteral("gamecard_inserted"), Settings::values.gamecard_inserted, false);
WriteSetting(QStringLiteral("gamecard_current_game"), Settings::values.gamecard_current_game,
false);
WriteSetting(QStringLiteral("gamecard_path"),
- QString::fromStdString(Settings::values.gamecard_path), QStringLiteral(""));
+ QString::fromStdString(Settings::values.gamecard_path), QString{});
qt_config->endGroup();
}
@@ -1033,7 +1118,7 @@ void Config::SaveDebuggingValues() {
WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false);
WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689);
WriteSetting(QStringLiteral("program_args"),
- QString::fromStdString(Settings::values.program_args), QStringLiteral(""));
+ QString::fromStdString(Settings::values.program_args), QString{});
WriteSetting(QStringLiteral("dump_exefs"), Settings::values.dump_exefs, false);
WriteSetting(QStringLiteral("dump_nso"), Settings::values.dump_nso, false);
WriteSetting(QStringLiteral("quest_flag"), Settings::values.quest_flag, false);
@@ -1060,8 +1145,7 @@ void Config::SaveDisabledAddOnValues() {
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]),
- QStringLiteral(""));
+ WriteSetting(QStringLiteral("d"), QString::fromStdString(elem.second[j]), QString{});
}
qt_config->endArray();
++i;
@@ -1085,7 +1169,6 @@ void Config::SavePathValues() {
WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path);
WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path);
- WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path);
qt_config->beginWriteArray(QStringLiteral("gamedirs"));
for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {
qt_config->setArrayIndex(i);
@@ -1122,6 +1205,11 @@ void Config::SaveCpuValues() {
WriteSetting(QStringLiteral("cpuopt_misc_ir"), Settings::values.cpuopt_misc_ir, true);
WriteSetting(QStringLiteral("cpuopt_reduce_misalign_checks"),
Settings::values.cpuopt_reduce_misalign_checks, true);
+
+ WriteSetting(QStringLiteral("cpuopt_unsafe_unfuse_fma"),
+ Settings::values.cpuopt_unsafe_unfuse_fma, true);
+ WriteSetting(QStringLiteral("cpuopt_unsafe_reduce_fp_error"),
+ Settings::values.cpuopt_unsafe_reduce_fp_error, true);
}
qt_config->endGroup();
@@ -1153,9 +1241,6 @@ void Config::SaveRendererValues() {
Settings::values.use_asynchronous_shaders, false);
WriteSettingGlobal(QStringLiteral("use_fast_gpu_time"), Settings::values.use_fast_gpu_time,
true);
- WriteSettingGlobal(QStringLiteral("force_30fps_mode"), Settings::values.force_30fps_mode,
- false);
-
// Cast to double because Qt's written float values are not human-readable
WriteSettingGlobal(QStringLiteral("bg_red"), Settings::values.bg_red, 0.0);
WriteSettingGlobal(QStringLiteral("bg_green"), Settings::values.bg_green, 0.0);
@@ -1164,6 +1249,17 @@ void Config::SaveRendererValues() {
qt_config->endGroup();
}
+void Config::SaveScreenshotValues() {
+ qt_config->beginGroup(QStringLiteral("Screenshots"));
+
+ WriteSetting(QStringLiteral("enable_screenshot_save_as"),
+ UISettings::values.enable_screenshot_save_as);
+ WriteSetting(QStringLiteral("screenshot_path"),
+ QString::fromStdString(FS::GetUserPath(FS::UserPath::ScreenshotsDir)));
+
+ qt_config->endGroup();
+}
+
void Config::SaveShortcutValues() {
qt_config->beginGroup(QStringLiteral("Shortcuts"));
@@ -1226,6 +1322,7 @@ void Config::SaveUIValues() {
SaveUIGamelistValues();
SaveUILayoutValues();
SavePathValues();
+ SaveScreenshotValues();
SaveShortcutValues();
WriteSetting(QStringLiteral("singleWindowMode"), UISettings::values.single_window_mode, true);
@@ -1237,7 +1334,6 @@ void Config::SaveUIValues() {
WriteSetting(QStringLiteral("firstStart"), UISettings::values.first_start, true);
WriteSetting(QStringLiteral("calloutFlags"), UISettings::values.callout_flags, 0);
WriteSetting(QStringLiteral("showConsole"), UISettings::values.show_console, false);
- WriteSetting(QStringLiteral("profileIndex"), UISettings::values.profile_index, 0);
WriteSetting(QStringLiteral("pauseWhenInBackground"),
UISettings::values.pause_when_in_background, false);
WriteSetting(QStringLiteral("hideInactiveMouse"), UISettings::values.hide_mouse, false);
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index 8e815f829..ca0d29c6c 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -23,7 +23,8 @@ public:
void Save();
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
- static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
+ 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, Settings::NativeMouseButton::NumMouseButtons>
default_mouse_buttons;
static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;
@@ -37,7 +38,7 @@ private:
void ReadKeyboardValues();
void ReadMouseValues();
void ReadTouchscreenValues();
- void ApplyDefaultProfileIfInputInvalid();
+ void ReadMotionTouchValues();
// Read functions bases off the respective config section names.
void ReadAudioValues();
@@ -51,6 +52,7 @@ private:
void ReadPathValues();
void ReadCpuValues();
void ReadRendererValues();
+ void ReadScreenshotValues();
void ReadShortcutValues();
void ReadSystemValues();
void ReadUIValues();
@@ -63,6 +65,7 @@ private:
void SaveDebugValues();
void SaveMouseValues();
void SaveTouchscreenValues();
+ void SaveMotionTouchValues();
// Save functions based off the respective config section names.
void SaveAudioValues();
@@ -76,6 +79,7 @@ private:
void SavePathValues();
void SaveCpuValues();
void SaveRendererValues();
+ void SaveScreenshotValues();
void SaveShortcutValues();
void SaveSystemValues();
void SaveUIValues();
diff --git a/src/yuzu/configuration/configuration_shared.cpp b/src/yuzu/configuration/configuration_shared.cpp
index f9becab6e..18482795c 100644
--- a/src/yuzu/configuration/configuration_shared.cpp
+++ b/src/yuzu/configuration/configuration_shared.cpp
@@ -72,18 +72,18 @@ void ConfigurationShared::SetPerGameSetting(
ConfigurationShared::USE_GLOBAL_OFFSET);
}
-void ConfigurationShared::SetHighlight(QWidget* widget, const std::string& name, bool highlighted) {
+void ConfigurationShared::SetHighlight(QWidget* widget, bool highlighted) {
if (highlighted) {
widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,203,255,0.5) }")
- .arg(QString::fromStdString(name)));
+ .arg(widget->objectName()));
} else {
widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,0,0,0) }")
- .arg(QString::fromStdString(name)));
+ .arg(widget->objectName()));
}
widget->show();
}
-void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox, const std::string& name,
+void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox,
const Settings::Setting<bool>& setting,
CheckState& tracker) {
if (setting.UsingGlobal()) {
@@ -91,45 +91,39 @@ void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox, const std::str
} else {
tracker = (setting.GetValue() == setting.GetValue(true)) ? CheckState::On : CheckState::Off;
}
- SetHighlight(checkbox, name, tracker != CheckState::Global);
- QObject::connect(checkbox, &QCheckBox::clicked, checkbox,
- [checkbox, name, setting, &tracker]() {
- tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) %
- static_cast<int>(CheckState::Count));
- if (tracker == CheckState::Global) {
- checkbox->setChecked(setting.GetValue(true));
- }
- SetHighlight(checkbox, name, tracker != CheckState::Global);
- });
+ SetHighlight(checkbox, tracker != CheckState::Global);
+ QObject::connect(checkbox, &QCheckBox::clicked, checkbox, [checkbox, setting, &tracker] {
+ tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) %
+ static_cast<int>(CheckState::Count));
+ if (tracker == CheckState::Global) {
+ checkbox->setChecked(setting.GetValue(true));
+ }
+ SetHighlight(checkbox, tracker != CheckState::Global);
+ });
}
-void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox, const std::string& name,
- bool global, bool state, bool global_state,
- CheckState& tracker) {
+void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox, bool global, bool state,
+ bool global_state, CheckState& tracker) {
if (global) {
tracker = CheckState::Global;
} else {
tracker = (state == global_state) ? CheckState::On : CheckState::Off;
}
- SetHighlight(checkbox, name, tracker != CheckState::Global);
- QObject::connect(checkbox, &QCheckBox::clicked, checkbox,
- [checkbox, name, global_state, &tracker]() {
- tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) %
- static_cast<int>(CheckState::Count));
- if (tracker == CheckState::Global) {
- checkbox->setChecked(global_state);
- }
- SetHighlight(checkbox, name, tracker != CheckState::Global);
- });
+ SetHighlight(checkbox, tracker != CheckState::Global);
+ QObject::connect(checkbox, &QCheckBox::clicked, checkbox, [checkbox, global_state, &tracker] {
+ tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) %
+ static_cast<int>(CheckState::Count));
+ if (tracker == CheckState::Global) {
+ checkbox->setChecked(global_state);
+ }
+ SetHighlight(checkbox, tracker != CheckState::Global);
+ });
}
-void ConfigurationShared::SetColoredComboBox(QComboBox* combobox, QWidget* target,
- const std::string& target_name, int global) {
+void ConfigurationShared::SetColoredComboBox(QComboBox* combobox, QWidget* target, int global) {
InsertGlobalItem(combobox, global);
- QObject::connect(combobox, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), target,
- [target, target_name](int index) {
- ConfigurationShared::SetHighlight(target, target_name, index != 0);
- });
+ QObject::connect(combobox, qOverload<int>(&QComboBox::activated), target,
+ [target](int index) { SetHighlight(target, index != 0); });
}
void ConfigurationShared::InsertGlobalItem(QComboBox* combobox, int global_index) {
diff --git a/src/yuzu/configuration/configuration_shared.h b/src/yuzu/configuration/configuration_shared.h
index 003148c68..312b9e549 100644
--- a/src/yuzu/configuration/configuration_shared.h
+++ b/src/yuzu/configuration/configuration_shared.h
@@ -39,13 +39,12 @@ void SetPerGameSetting(QComboBox* combobox,
void SetPerGameSetting(QComboBox* combobox,
const Settings::Setting<Settings::GPUAccuracy>* setting);
-void SetHighlight(QWidget* widget, const std::string& name, bool highlighted);
-void SetColoredTristate(QCheckBox* checkbox, const std::string& name,
- const Settings::Setting<bool>& setting, CheckState& tracker);
-void SetColoredTristate(QCheckBox* checkbox, const std::string& name, bool global, bool state,
- bool global_state, CheckState& tracker);
-void SetColoredComboBox(QComboBox* combobox, QWidget* target, const std::string& target_name,
- int global);
+void SetHighlight(QWidget* widget, bool highlighted);
+void SetColoredTristate(QCheckBox* checkbox, const Settings::Setting<bool>& setting,
+ CheckState& tracker);
+void SetColoredTristate(QCheckBox* checkbox, bool global, bool state, bool global_state,
+ CheckState& tracker);
+void SetColoredComboBox(QComboBox* combobox, QWidget* target, int global);
void InsertGlobalItem(QComboBox* combobox, int global_index);
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index 5f5d8e571..fcf42cdcb 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
- <width>382</width>
+ <width>650</width>
<height>650</height>
</rect>
</property>
@@ -26,13 +26,13 @@
<widget class="QListWidget" name="selectorList">
<property name="minimumSize">
<size>
- <width>150</width>
+ <width>120</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>150</width>
+ <width>120</width>
<height>16777215</height>
</size>
</property>
@@ -44,76 +44,121 @@
<number>0</number>
</property>
<widget class="ConfigureGeneral" name="generalTab">
+ <property name="accessibleName">
+ <string>General</string>
+ </property>
<attribute name="title">
<string>General</string>
</attribute>
</widget>
<widget class="ConfigureUi" name="uiTab">
+ <property name="accessibleName">
+ <string>UI</string>
+ </property>
<attribute name="title">
<string>Game List</string>
</attribute>
</widget>
<widget class="ConfigureSystem" name="systemTab">
+ <property name="accessibleName">
+ <string>System</string>
+ </property>
<attribute name="title">
<string>System</string>
</attribute>
</widget>
<widget class="ConfigureProfileManager" name="profileManagerTab">
+ <property name="accessibleName">
+ <string>Profiles</string>
+ </property>
<attribute name="title">
<string>Profiles</string>
</attribute>
</widget>
<widget class="ConfigureFilesystem" name="filesystemTab">
+ <property name="accessibleName">
+ <string>Filesystem</string>
+ </property>
<attribute name="title">
<string>Filesystem</string>
</attribute>
</widget>
- <widget class="ConfigureInputSimple" name="inputTab">
+ <widget class="ConfigureInput" name="inputTab">
+ <property name="accessibleName">
+ <string>Controls</string>
+ </property>
<attribute name="title">
- <string>Input</string>
+ <string>Controls</string>
</attribute>
</widget>
<widget class="ConfigureHotkeys" name="hotkeysTab">
+ <property name="accessibleName">
+ <string>Hotkeys</string>
+ </property>
<attribute name="title">
<string>Hotkeys</string>
</attribute>
</widget>
<widget class="ConfigureCpu" name="cpuTab">
+ <property name="accessibleName">
+ <string>CPU</string>
+ </property>
<attribute name="title">
<string>CPU</string>
</attribute>
</widget>
<widget class="ConfigureCpuDebug" name="cpuDebugTab">
+ <property name="accessibleName">
+ <string>Debug</string>
+ </property>
<attribute name="title">
<string>Debug</string>
</attribute>
</widget>
<widget class="ConfigureGraphics" name="graphicsTab">
+ <property name="accessibleName">
+ <string>Graphics</string>
+ </property>
<attribute name="title">
<string>Graphics</string>
</attribute>
</widget>
<widget class="ConfigureGraphicsAdvanced" name="graphicsAdvancedTab">
+ <property name="accessibleName">
+ <string>Advanced</string>
+ </property>
<attribute name="title">
<string>GraphicsAdvanced</string>
</attribute>
</widget>
<widget class="ConfigureAudio" name="audioTab">
+ <property name="accessibleName">
+ <string>Audio</string>
+ </property>
<attribute name="title">
<string>Audio</string>
</attribute>
</widget>
<widget class="ConfigureDebug" name="debugTab">
+ <property name="accessibleName">
+ <string>Debug</string>
+ </property>
<attribute name="title">
<string>Debug</string>
</attribute>
</widget>
<widget class="ConfigureWeb" name="webTab">
+ <property name="accessibleName">
+ <string>Web</string>
+ </property>
<attribute name="title">
<string>Web</string>
</attribute>
</widget>
<widget class="ConfigureService" name="serviceTab">
+ <property name="accessibleName">
+ <string>Services</string>
+ </property>
<attribute name="title">
<string>Services</string>
</attribute>
@@ -205,9 +250,9 @@
<container>1</container>
</customwidget>
<customwidget>
- <class>ConfigureInputSimple</class>
+ <class>ConfigureInput</class>
<extends>QWidget</extends>
- <header>configuration/configure_input_simple.h</header>
+ <header>configuration/configure_input.h</header>
<container>1</container>
</customwidget>
<customwidget>
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index fea632531..fa9124ecf 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -59,7 +59,7 @@ void ConfigureAudio::SetConfiguration() {
ui->volume_combo_box->setCurrentIndex(1);
ui->volume_slider->setEnabled(true);
}
- ConfigurationShared::SetHighlight(ui->volume_layout, "volume_layout",
+ ConfigurationShared::SetHighlight(ui->volume_layout,
!Settings::values.volume.UsingGlobal());
}
SetVolumeIndicatorText(ui->volume_slider->sliderPosition());
@@ -173,14 +173,13 @@ void ConfigureAudio::SetupPerGameUI() {
return;
}
- ConfigurationShared::SetColoredTristate(ui->toggle_audio_stretching, "toggle_audio_stretching",
+ ConfigurationShared::SetColoredTristate(ui->toggle_audio_stretching,
Settings::values.enable_audio_stretching,
enable_audio_stretching);
- connect(ui->volume_combo_box, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated),
- this, [this](int index) {
- ui->volume_slider->setEnabled(index == 1);
- ConfigurationShared::SetHighlight(ui->volume_layout, "volume_layout", index == 1);
- });
+ connect(ui->volume_combo_box, qOverload<int>(&QComboBox::activated), this, [this](int index) {
+ ui->volume_slider->setEnabled(index == 1);
+ ConfigurationShared::SetHighlight(ui->volume_layout, index == 1);
+ });
ui->output_sink_combo_box->setVisible(false);
ui->output_sink_label->setVisible(false);
diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp
index 7493e5ffb..37fcd6adc 100644
--- a/src/yuzu/configuration/configure_cpu.cpp
+++ b/src/yuzu/configuration/configure_cpu.cpp
@@ -19,6 +19,8 @@ ConfigureCpu::ConfigureCpu(QWidget* parent) : QWidget(parent), ui(new Ui::Config
connect(ui->accuracy, qOverload<int>(&QComboBox::activated), this,
&ConfigureCpu::AccuracyUpdated);
+ connect(ui->accuracy, qOverload<int>(&QComboBox::currentIndexChanged), this,
+ &ConfigureCpu::UpdateGroup);
}
ConfigureCpu::~ConfigureCpu() = default;
@@ -28,6 +30,12 @@ void ConfigureCpu::SetConfiguration() {
ui->accuracy->setEnabled(runtime_lock);
ui->accuracy->setCurrentIndex(static_cast<int>(Settings::values.cpu_accuracy));
+ UpdateGroup(static_cast<int>(Settings::values.cpu_accuracy));
+
+ ui->cpuopt_unsafe_unfuse_fma->setEnabled(runtime_lock);
+ ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma);
+ ui->cpuopt_unsafe_reduce_fp_error->setEnabled(runtime_lock);
+ ui->cpuopt_unsafe_reduce_fp_error->setChecked(Settings::values.cpuopt_unsafe_reduce_fp_error);
}
void ConfigureCpu::AccuracyUpdated(int index) {
@@ -38,14 +46,21 @@ void ConfigureCpu::AccuracyUpdated(int index) {
QMessageBox::Yes | QMessageBox::No);
if (result == QMessageBox::No) {
ui->accuracy->setCurrentIndex(static_cast<int>(Settings::CPUAccuracy::Accurate));
- return;
+ UpdateGroup(static_cast<int>(Settings::CPUAccuracy::Accurate));
}
}
}
+void ConfigureCpu::UpdateGroup(int index) {
+ ui->unsafe_group->setVisible(static_cast<Settings::CPUAccuracy>(index) ==
+ Settings::CPUAccuracy::Unsafe);
+}
+
void ConfigureCpu::ApplyConfiguration() {
Settings::values.cpu_accuracy =
static_cast<Settings::CPUAccuracy>(ui->accuracy->currentIndex());
+ Settings::values.cpuopt_unsafe_unfuse_fma = ui->cpuopt_unsafe_unfuse_fma->isChecked();
+ Settings::values.cpuopt_unsafe_reduce_fp_error = ui->cpuopt_unsafe_reduce_fp_error->isChecked();
}
void ConfigureCpu::changeEvent(QEvent* event) {
diff --git a/src/yuzu/configuration/configure_cpu.h b/src/yuzu/configuration/configure_cpu.h
index e4741d3a4..3c5683d81 100644
--- a/src/yuzu/configuration/configure_cpu.h
+++ b/src/yuzu/configuration/configure_cpu.h
@@ -26,6 +26,7 @@ private:
void RetranslateUI();
void AccuracyUpdated(int index);
+ void UpdateGroup(int index);
void SetConfiguration();
diff --git a/src/yuzu/configuration/configure_cpu.ui b/src/yuzu/configuration/configure_cpu.ui
index bf6ea79bb..ebdd2e6e9 100644
--- a/src/yuzu/configuration/configure_cpu.ui
+++ b/src/yuzu/configuration/configure_cpu.ui
@@ -40,6 +40,11 @@
</item>
<item>
<property name="text">
+ <string>Unsafe</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
<string>Enable Debug Mode</string>
</property>
</item>
@@ -63,6 +68,53 @@
</layout>
</item>
<item>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QGroupBox" name="unsafe_group">
+ <property name="title">
+ <string>Unsafe CPU Optimization Settings</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QLabel">
+ <property name="wordWrap">
+ <bool>1</bool>
+ </property>
+ <property name="text">
+ <string>These settings reduce accuracy for speed.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="cpuopt_unsafe_unfuse_fma">
+ <property name="text">
+ <string>Unfuse FMA (improve performance on CPUs without FMA)</string>
+ </property>
+ <property name="toolTip">
+ <string>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="cpuopt_unsafe_reduce_fp_error">
+ <property name="text">
+ <string>Faster FRSQRTE and FRECPE</string>
+ </property>
+ <property name="toolTip">
+ <string>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index d0e71dd60..2bfe2c306 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -19,7 +19,8 @@ ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::Co
SetConfiguration();
connect(ui->open_log_button, &QPushButton::clicked, []() {
- QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LogDir));
+ const auto path =
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LogDir));
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
});
}
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 272bdd6b8..9d6feb9f7 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -171,26 +171,6 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
- <widget class="QCheckBox" name="dump_decompressed_nso">
- <property name="whatsThis">
- <string>When checked, any NSO yuzu tries to load or patch will be copied decompressed to the yuzu/dump directory.</string>
- </property>
- <property name="text">
- <string>Dump Decompressed NSOs</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="dump_exefs">
- <property name="whatsThis">
- <string>When checked, any game that yuzu loads will have its ExeFS dumped to the yuzu/dump directory.</string>
- </property>
- <property name="text">
- <string>Dump ExeFS</string>
- </property>
- </widget>
- </item>
- <item>
<widget class="QCheckBox" name="reporting_services">
<property name="text">
<string>Enable Verbose Reporting Services</string>
@@ -257,8 +237,6 @@
<tabstop>open_log_button</tabstop>
<tabstop>homebrew_args_edit</tabstop>
<tabstop>enable_graphics_debugging</tabstop>
- <tabstop>dump_decompressed_nso</tabstop>
- <tabstop>dump_exefs</tabstop>
<tabstop>reporting_services</tabstop>
<tabstop>quest_flag</tabstop>
</tabstops>
diff --git a/src/yuzu/configuration/configure_debug_controller.cpp b/src/yuzu/configuration/configure_debug_controller.cpp
new file mode 100644
index 000000000..0097c9a29
--- /dev/null
+++ b/src/yuzu/configuration/configure_debug_controller.cpp
@@ -0,0 +1,40 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "ui_configure_debug_controller.h"
+#include "yuzu/configuration/configure_debug_controller.h"
+
+ConfigureDebugController::ConfigureDebugController(QWidget* parent,
+ InputCommon::InputSubsystem* input_subsystem)
+ : QDialog(parent), ui(std::make_unique<Ui::ConfigureDebugController>()),
+ debug_controller(new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, true)) {
+ ui->setupUi(this);
+
+ ui->controllerLayout->addWidget(debug_controller);
+
+ connect(ui->clear_all_button, &QPushButton::clicked, this,
+ [this] { debug_controller->ClearAll(); });
+ connect(ui->restore_defaults_button, &QPushButton::clicked, this,
+ [this] { debug_controller->RestoreDefaults(); });
+
+ RetranslateUI();
+}
+
+ConfigureDebugController::~ConfigureDebugController() = default;
+
+void ConfigureDebugController::ApplyConfiguration() {
+ debug_controller->ApplyConfiguration();
+}
+
+void ConfigureDebugController::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QDialog::changeEvent(event);
+}
+
+void ConfigureDebugController::RetranslateUI() {
+ ui->retranslateUi(this);
+}
diff --git a/src/yuzu/configuration/configure_debug_controller.h b/src/yuzu/configuration/configure_debug_controller.h
new file mode 100644
index 000000000..34dcf705f
--- /dev/null
+++ b/src/yuzu/configuration/configure_debug_controller.h
@@ -0,0 +1,38 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QDialog>
+#include "yuzu/configuration/configure_input_player.h"
+
+class QPushButton;
+
+namespace InputCommon {
+class InputSubsystem;
+}
+
+namespace Ui {
+class ConfigureDebugController;
+}
+
+class ConfigureDebugController : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit ConfigureDebugController(QWidget* parent,
+ InputCommon::InputSubsystem* input_subsystem);
+ ~ConfigureDebugController() override;
+
+ void ApplyConfiguration();
+
+private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ std::unique_ptr<Ui::ConfigureDebugController> ui;
+
+ ConfigureInputPlayer* debug_controller;
+};
diff --git a/src/yuzu/configuration/configure_debug_controller.ui b/src/yuzu/configuration/configure_debug_controller.ui
new file mode 100644
index 000000000..a95ed50ff
--- /dev/null
+++ b/src/yuzu/configuration/configure_debug_controller.ui
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureDebugController</class>
+ <widget class="QDialog" name="ConfigureDebugController">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>780</width>
+ <height>500</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Configure Debug Controller</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>9</number>
+ </property>
+ <property name="topMargin">
+ <number>9</number>
+ </property>
+ <property name="rightMargin">
+ <number>9</number>
+ </property>
+ <property name="bottomMargin">
+ <number>9</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="controllerLayout"/>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPushButton" name="clear_all_button">
+ <property name="text">
+ <string>Clear</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="restore_defaults_button">
+ <property name="text">
+ <string>Defaults</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ConfigureDebugController</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>140</x>
+ <y>318</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>140</x>
+ <y>169</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ConfigureDebugController</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>140</x>
+ <y>318</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>140</x>
+ <y>169</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 4e30dc51e..8186929a6 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -12,7 +12,8 @@
#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/hotkeys.h"
-ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry)
+ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry,
+ InputCommon::InputSubsystem* input_subsystem)
: QDialog(parent), ui(new Ui::ConfigureDialog), registry(registry) {
Settings::configuring_global = true;
@@ -20,6 +21,8 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry)
ui->hotkeysTab->Populate(registry);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+ ui->inputTab->Initialize(input_subsystem);
+
SetConfiguration();
PopulateSelectionList();
@@ -80,12 +83,12 @@ Q_DECLARE_METATYPE(QList<QWidget*>);
void ConfigureDialog::PopulateSelectionList() {
const std::array<std::pair<QString, QList<QWidget*>>, 6> items{
- {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->uiTab}},
+ {{tr("General"), {ui->generalTab, ui->hotkeysTab, ui->uiTab, ui->webTab, ui->debugTab}},
{tr("System"), {ui->systemTab, ui->profileManagerTab, ui->serviceTab, ui->filesystemTab}},
{tr("CPU"), {ui->cpuTab, ui->cpuDebugTab}},
{tr("Graphics"), {ui->graphicsTab, ui->graphicsAdvancedTab}},
{tr("Audio"), {ui->audioTab}},
- {tr("Controls"), {ui->inputTab, ui->hotkeysTab}}},
+ {tr("Controls"), ui->inputTab->GetSubTabs()}},
};
[[maybe_unused]] const QSignalBlocker blocker(ui->selectorList);
@@ -117,7 +120,7 @@ void ConfigureDialog::UpdateVisibleTabs() {
{ui->generalTab, tr("General")},
{ui->systemTab, tr("System")},
{ui->profileManagerTab, tr("Profiles")},
- {ui->inputTab, tr("Input")},
+ {ui->inputTab, tr("Controls")},
{ui->hotkeysTab, tr("Hotkeys")},
{ui->cpuTab, tr("CPU")},
{ui->cpuDebugTab, tr("Debug")},
@@ -138,6 +141,6 @@ void ConfigureDialog::UpdateVisibleTabs() {
const QList<QWidget*> tabs = qvariant_cast<QList<QWidget*>>(items[0]->data(Qt::UserRole));
for (const auto tab : tabs) {
- ui->tabWidget->addTab(tab, widgets.at(tab));
+ ui->tabWidget->addTab(tab, tab->accessibleName());
}
}
diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h
index 4289bc225..570c3b941 100644
--- a/src/yuzu/configuration/configure_dialog.h
+++ b/src/yuzu/configuration/configure_dialog.h
@@ -9,6 +9,10 @@
class HotkeyRegistry;
+namespace InputCommon {
+class InputSubsystem;
+}
+
namespace Ui {
class ConfigureDialog;
}
@@ -17,7 +21,8 @@ class ConfigureDialog : public QDialog {
Q_OBJECT
public:
- explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry);
+ explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry,
+ InputCommon::InputSubsystem* input_subsystem);
~ConfigureDialog() override;
void ApplyConfiguration();
diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp
index a089f5733..7ab4a80f7 100644
--- a/src/yuzu/configuration/configure_filesystem.cpp
+++ b/src/yuzu/configuration/configure_filesystem.cpp
@@ -42,16 +42,16 @@ ConfigureFilesystem::~ConfigureFilesystem() = default;
void ConfigureFilesystem::setConfiguration() {
ui->nand_directory_edit->setText(
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir)));
ui->sdmc_directory_edit->setText(
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)));
ui->gamecard_path_edit->setText(QString::fromStdString(Settings::values.gamecard_path));
ui->dump_path_edit->setText(
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)));
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir)));
ui->load_path_edit->setText(
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)));
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir)));
ui->cache_directory_edit->setText(
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)));
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)));
ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted);
ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game);
@@ -64,14 +64,16 @@ void ConfigureFilesystem::setConfiguration() {
}
void ConfigureFilesystem::applyConfiguration() {
- FileUtil::GetUserPath(FileUtil::UserPath::NANDDir,
- ui->nand_directory_edit->text().toStdString());
- FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir,
- ui->sdmc_directory_edit->text().toStdString());
- FileUtil::GetUserPath(FileUtil::UserPath::DumpDir, ui->dump_path_edit->text().toStdString());
- FileUtil::GetUserPath(FileUtil::UserPath::LoadDir, ui->load_path_edit->text().toStdString());
- FileUtil::GetUserPath(FileUtil::UserPath::CacheDir,
- ui->cache_directory_edit->text().toStdString());
+ Common::FS::GetUserPath(Common::FS::UserPath::NANDDir,
+ ui->nand_directory_edit->text().toStdString());
+ Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir,
+ ui->sdmc_directory_edit->text().toStdString());
+ Common::FS::GetUserPath(Common::FS::UserPath::DumpDir,
+ ui->dump_path_edit->text().toStdString());
+ Common::FS::GetUserPath(Common::FS::UserPath::LoadDir,
+ ui->load_path_edit->text().toStdString());
+ Common::FS::GetUserPath(Common::FS::UserPath::CacheDir,
+ ui->cache_directory_edit->text().toStdString());
Settings::values.gamecard_path = ui->gamecard_path_edit->text().toStdString();
Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked();
@@ -121,12 +123,13 @@ void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit)
}
void ConfigureFilesystem::ResetMetadata() {
- if (!FileUtil::Exists(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP +
- "game_list")) {
+ if (!Common::FS::Exists(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
+ "game_list")) {
QMessageBox::information(this, tr("Reset Metadata Cache"),
tr("The metadata cache is already empty."));
- } else if (FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
- DIR_SEP + "game_list")) {
+ } else if (Common::FS::DeleteDirRecursively(
+ Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
+ "game_list")) {
QMessageBox::information(this, tr("Reset Metadata Cache"),
tr("The operation completed successfully."));
UISettings::values.is_game_list_reload_pending.exchange(true);
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index c0dbd9855..830096ea0 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -105,10 +105,10 @@ void ConfigureGeneral::SetupPerGameUI() {
ui->toggle_background_pause->setVisible(false);
ui->toggle_hide_mouse->setVisible(false);
- ConfigurationShared::SetColoredTristate(ui->toggle_frame_limit, "toggle_frame_limit",
+ ConfigurationShared::SetColoredTristate(ui->toggle_frame_limit,
Settings::values.use_frame_limit, use_frame_limit);
- ConfigurationShared::SetColoredTristate(ui->use_multi_core, "use_multi_core",
- Settings::values.use_multi_core, use_multi_core);
+ ConfigurationShared::SetColoredTristate(ui->use_multi_core, Settings::values.use_multi_core,
+ use_multi_core);
connect(ui->toggle_frame_limit, &QCheckBox::clicked, ui->frame_limit, [this]() {
ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked() &&
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index 3e42531c3..07d818548 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -34,9 +34,8 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this, [this] {
UpdateDeviceComboBox();
if (!Settings::configuring_global) {
- ConfigurationShared::SetHighlight(ui->api_layout, "api_layout",
- ui->api->currentIndex() !=
- ConfigurationShared::USE_GLOBAL_INDEX);
+ ConfigurationShared::SetHighlight(
+ ui->api_layout, ui->api->currentIndex() != ConfigurationShared::USE_GLOBAL_INDEX);
}
});
connect(ui->device, qOverload<int>(&QComboBox::activated), this,
@@ -80,17 +79,16 @@ void ConfigureGraphics::SetConfiguration() {
ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue());
} else {
ConfigurationShared::SetPerGameSetting(ui->api, &Settings::values.renderer_backend);
- ConfigurationShared::SetHighlight(ui->api_layout, "api_layout",
+ ConfigurationShared::SetHighlight(ui->api_layout,
!Settings::values.renderer_backend.UsingGlobal());
ConfigurationShared::SetPerGameSetting(ui->aspect_ratio_combobox,
&Settings::values.aspect_ratio);
ui->bg_combobox->setCurrentIndex(Settings::values.bg_red.UsingGlobal() ? 0 : 1);
ui->bg_button->setEnabled(!Settings::values.bg_red.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->ar_label, "ar_label",
+ ConfigurationShared::SetHighlight(ui->ar_label,
!Settings::values.aspect_ratio.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->bg_layout, "bg_layout",
- !Settings::values.bg_red.UsingGlobal());
+ ConfigurationShared::SetHighlight(ui->bg_layout, !Settings::values.bg_red.UsingGlobal());
}
UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red.GetValue(),
@@ -248,20 +246,18 @@ void ConfigureGraphics::SetupPerGameUI() {
return;
}
- connect(ui->bg_combobox, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this,
- [this](int index) {
- ui->bg_button->setEnabled(index == 1);
- ConfigurationShared::SetHighlight(ui->bg_layout, "bg_layout", index == 1);
- });
+ connect(ui->bg_combobox, qOverload<int>(&QComboBox::activated), this, [this](int index) {
+ ui->bg_button->setEnabled(index == 1);
+ ConfigurationShared::SetHighlight(ui->bg_layout, index == 1);
+ });
- ConfigurationShared::SetColoredTristate(ui->use_disk_shader_cache, "use_disk_shader_cache",
- Settings::values.use_disk_shader_cache,
- use_disk_shader_cache);
ConfigurationShared::SetColoredTristate(
- ui->use_asynchronous_gpu_emulation, "use_asynchronous_gpu_emulation",
- Settings::values.use_asynchronous_gpu_emulation, use_asynchronous_gpu_emulation);
+ ui->use_disk_shader_cache, Settings::values.use_disk_shader_cache, use_disk_shader_cache);
+ ConfigurationShared::SetColoredTristate(ui->use_asynchronous_gpu_emulation,
+ Settings::values.use_asynchronous_gpu_emulation,
+ use_asynchronous_gpu_emulation);
- ConfigurationShared::SetColoredComboBox(ui->aspect_ratio_combobox, ui->ar_label, "ar_label",
+ ConfigurationShared::SetColoredComboBox(ui->aspect_ratio_combobox, ui->ar_label,
Settings::values.aspect_ratio.GetValue(true));
ConfigurationShared::InsertGlobalItem(
ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true)));
diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp
index 8b9180811..73f276949 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.cpp
+++ b/src/yuzu/configuration/configure_graphics_advanced.cpp
@@ -25,14 +25,12 @@ void ConfigureGraphicsAdvanced::SetConfiguration() {
ui->use_vsync->setEnabled(runtime_lock);
ui->use_assembly_shaders->setEnabled(runtime_lock);
ui->use_asynchronous_shaders->setEnabled(runtime_lock);
- ui->force_30fps_mode->setEnabled(runtime_lock);
ui->anisotropic_filtering_combobox->setEnabled(runtime_lock);
ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue());
ui->use_assembly_shaders->setChecked(Settings::values.use_assembly_shaders.GetValue());
ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue());
ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue());
- ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode.GetValue());
if (Settings::configuring_global) {
ui->gpu_accuracy->setCurrentIndex(
@@ -43,9 +41,9 @@ void ConfigureGraphicsAdvanced::SetConfiguration() {
ConfigurationShared::SetPerGameSetting(ui->gpu_accuracy, &Settings::values.gpu_accuracy);
ConfigurationShared::SetPerGameSetting(ui->anisotropic_filtering_combobox,
&Settings::values.max_anisotropy);
- ConfigurationShared::SetHighlight(ui->label_gpu_accuracy, "label_gpu_accuracy",
+ ConfigurationShared::SetHighlight(ui->label_gpu_accuracy,
!Settings::values.gpu_accuracy.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->af_label, "af_label",
+ ConfigurationShared::SetHighlight(ui->af_label,
!Settings::values.max_anisotropy.UsingGlobal());
}
}
@@ -78,9 +76,6 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() {
if (Settings::values.use_fast_gpu_time.UsingGlobal()) {
Settings::values.use_fast_gpu_time.SetValue(ui->use_fast_gpu_time->isChecked());
}
- if (Settings::values.force_30fps_mode.UsingGlobal()) {
- Settings::values.force_30fps_mode.SetValue(ui->force_30fps_mode->isChecked());
- }
if (Settings::values.max_anisotropy.UsingGlobal()) {
Settings::values.max_anisotropy.SetValue(
ui->anisotropic_filtering_combobox->currentIndex());
@@ -97,8 +92,6 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() {
use_asynchronous_shaders);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time,
ui->use_fast_gpu_time, use_fast_gpu_time);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.force_30fps_mode,
- ui->force_30fps_mode, force_30fps_mode);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy,
ui->anisotropic_filtering_combobox);
@@ -132,29 +125,24 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
ui->use_asynchronous_shaders->setEnabled(
Settings::values.use_asynchronous_shaders.UsingGlobal());
ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal());
- ui->force_30fps_mode->setEnabled(Settings::values.force_30fps_mode.UsingGlobal());
ui->anisotropic_filtering_combobox->setEnabled(
Settings::values.max_anisotropy.UsingGlobal());
return;
}
- ConfigurationShared::SetColoredTristate(ui->use_vsync, "use_vsync", Settings::values.use_vsync,
- use_vsync);
- ConfigurationShared::SetColoredTristate(ui->use_assembly_shaders, "use_assembly_shaders",
- Settings::values.use_assembly_shaders,
- use_assembly_shaders);
+ ConfigurationShared::SetColoredTristate(ui->use_vsync, Settings::values.use_vsync, use_vsync);
ConfigurationShared::SetColoredTristate(
- ui->use_asynchronous_shaders, "use_asynchronous_shaders",
- Settings::values.use_asynchronous_shaders, use_asynchronous_shaders);
- ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time, "use_fast_gpu_time",
+ ui->use_assembly_shaders, Settings::values.use_assembly_shaders, use_assembly_shaders);
+ ConfigurationShared::SetColoredTristate(ui->use_asynchronous_shaders,
+ Settings::values.use_asynchronous_shaders,
+ use_asynchronous_shaders);
+ ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time,
Settings::values.use_fast_gpu_time, use_fast_gpu_time);
- ConfigurationShared::SetColoredTristate(ui->force_30fps_mode, "force_30fps_mode",
- Settings::values.force_30fps_mode, force_30fps_mode);
ConfigurationShared::SetColoredComboBox(
- ui->gpu_accuracy, ui->label_gpu_accuracy, "label_gpu_accuracy",
+ ui->gpu_accuracy, ui->label_gpu_accuracy,
static_cast<int>(Settings::values.gpu_accuracy.GetValue(true)));
ConfigurationShared::SetColoredComboBox(
- ui->anisotropic_filtering_combobox, ui->af_label, "af_label",
+ ui->anisotropic_filtering_combobox, ui->af_label,
static_cast<int>(Settings::values.max_anisotropy.GetValue(true)));
}
diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h
index 3c4f6f7bb..e61b571c7 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.h
+++ b/src/yuzu/configuration/configure_graphics_advanced.h
@@ -38,5 +38,4 @@ private:
ConfigurationShared::CheckState use_assembly_shaders;
ConfigurationShared::CheckState use_asynchronous_shaders;
ConfigurationShared::CheckState use_fast_gpu_time;
- ConfigurationShared::CheckState force_30fps_mode;
};
diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui
index 6a0d29c27..846a30586 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.ui
+++ b/src/yuzu/configuration/configure_graphics_advanced.ui
@@ -92,14 +92,7 @@
<string>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</string>
</property>
<property name="text">
- <string>Use asynchronous shader building (experimental, OpenGL or Assembly shaders only)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="force_30fps_mode">
- <property name="text">
- <string>Force 30 FPS mode</string>
+ <string>Use asynchronous shader building (experimental)</string>
</property>
</widget>
</item>
diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp
index 6f7fd4414..cbee51a5e 100644
--- a/src/yuzu/configuration/configure_hotkeys.cpp
+++ b/src/yuzu/configuration/configure_hotkeys.cpp
@@ -154,7 +154,7 @@ void ConfigureHotkeys::ClearAll() {
const QStandardItem* parent = model->item(r, 0);
for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
- model->item(r, 0)->child(r2, 1)->setText(tr(""));
+ model->item(r, 0)->child(r2, 1)->setText(QString{});
}
}
}
@@ -186,7 +186,7 @@ void ConfigureHotkeys::PopupContextMenu(const QPoint& menu_location) {
model->setData(selected, default_key_sequence.toString(QKeySequence::NativeText));
}
});
- connect(clear, &QAction::triggered, [this, selected] { model->setData(selected, tr("")); });
+ connect(clear, &QAction::triggered, [this, selected] { model->setData(selected, QString{}); });
context_menu.exec(ui->hotkey_list->viewport()->mapToGlobal(menu_location));
}
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index f2977719c..7ea17a4db 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -8,18 +8,33 @@
#include <QSignalBlocker>
#include <QTimer>
-#include "configuration/configure_touchscreen_advanced.h"
#include "core/core.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
-#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/sm/sm.h"
#include "ui_configure_input.h"
+#include "ui_configure_input_advanced.h"
#include "ui_configure_input_player.h"
+#include "yuzu/configuration/configure_debug_controller.h"
#include "yuzu/configuration/configure_input.h"
+#include "yuzu/configuration/configure_input_advanced.h"
#include "yuzu/configuration/configure_input_player.h"
+#include "yuzu/configuration/configure_motion_touch.h"
#include "yuzu/configuration/configure_mouse_advanced.h"
+#include "yuzu/configuration/configure_touchscreen_advanced.h"
+
+namespace {
+template <typename Dialog, typename... Args>
+void CallConfigureDialog(ConfigureInput& parent, Args&&... args) {
+ Dialog dialog(&parent, std::forward<Args>(args)...);
+
+ const auto res = dialog.exec();
+ if (res == QDialog::Accepted) {
+ dialog.ApplyConfiguration();
+ }
+}
+} // Anonymous namespace
void OnDockedModeChanged(bool last_state, bool new_state) {
if (last_state == new_state) {
@@ -48,97 +63,115 @@ void OnDockedModeChanged(bool last_state, bool new_state) {
}
}
-namespace {
-template <typename Dialog, typename... Args>
-void CallConfigureDialog(ConfigureInput& parent, Args&&... args) {
- parent.ApplyConfiguration();
- Dialog dialog(&parent, std::forward<Args>(args)...);
-
- const auto res = dialog.exec();
- if (res == QDialog::Accepted) {
- dialog.ApplyConfiguration();
- }
-}
-} // Anonymous namespace
-
ConfigureInput::ConfigureInput(QWidget* parent)
- : QDialog(parent), ui(std::make_unique<Ui::ConfigureInput>()) {
+ : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) {
ui->setupUi(this);
+}
- players_controller = {
- ui->player1_combobox, ui->player2_combobox, ui->player3_combobox, ui->player4_combobox,
- ui->player5_combobox, ui->player6_combobox, ui->player7_combobox, ui->player8_combobox,
+ConfigureInput::~ConfigureInput() = default;
+
+void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
+ std::size_t max_players) {
+ player_controllers = {
+ new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem),
+ new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem),
+ new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem),
+ new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem),
+ new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem),
+ new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem),
+ new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem),
+ new ConfigureInputPlayer(this, 7, ui->consoleInputSettings, input_subsystem),
};
- players_configure = {
- ui->player1_configure, ui->player2_configure, ui->player3_configure, ui->player4_configure,
- ui->player5_configure, ui->player6_configure, ui->player7_configure, ui->player8_configure,
+ player_tabs = {
+ ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4,
+ ui->tabPlayer5, ui->tabPlayer6, ui->tabPlayer7, ui->tabPlayer8,
};
- RetranslateUI();
- LoadConfiguration();
- UpdateUIEnabled();
+ player_connected = {
+ ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected,
+ ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected,
+ ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
+ };
- connect(ui->restore_defaults_button, &QPushButton::clicked, this,
- &ConfigureInput::RestoreDefaults);
+ std::array<QLabel*, 8> player_connected_labels = {
+ ui->label, ui->label_3, ui->label_4, ui->label_5,
+ ui->label_6, ui->label_7, ui->label_8, ui->label_9,
+ };
- for (auto* enabled : players_controller) {
- connect(enabled, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
- &ConfigureInput::UpdateUIEnabled);
- }
- connect(ui->use_docked_mode, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled);
- connect(ui->handheld_connected, &QCheckBox::stateChanged, this,
- &ConfigureInput::UpdateUIEnabled);
- connect(ui->mouse_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled);
- connect(ui->keyboard_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled);
- connect(ui->debug_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled);
- connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this,
- &ConfigureInput::UpdateUIEnabled);
-
- for (std::size_t i = 0; i < players_configure.size(); ++i) {
- connect(players_configure[i], &QPushButton::clicked, this,
- [this, i] { CallConfigureDialog<ConfigureInputPlayer>(*this, i, false); });
+ for (std::size_t i = 0; i < player_tabs.size(); ++i) {
+ player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i]));
+ player_tabs[i]->layout()->addWidget(player_controllers[i]);
+ connect(player_controllers[i], &ConfigureInputPlayer::Connected, [&, i](bool is_connected) {
+ if (is_connected) {
+ for (std::size_t index = 0; index <= i; ++index) {
+ player_connected[index]->setChecked(is_connected);
+ }
+ } else {
+ for (std::size_t index = i; index < player_tabs.size(); ++index) {
+ player_connected[index]->setChecked(is_connected);
+ }
+ }
+ });
+ connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices,
+ [this] { UpdateAllInputDevices(); });
+ connect(player_connected[i], &QCheckBox::stateChanged, [this, i](int state) {
+ 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();
+ }
}
+ // 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);
+ ui->tabAdvanced->setLayout(new QHBoxLayout(ui->tabAdvanced));
+ ui->tabAdvanced->layout()->addWidget(advanced);
+ connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog, [this, input_subsystem] {
+ CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem);
+ });
+ connect(advanced, &ConfigureInputAdvanced::CallMouseConfigDialog, [this, input_subsystem] {
+ CallConfigureDialog<ConfigureMouseAdvanced>(*this, input_subsystem);
+ });
+ connect(advanced, &ConfigureInputAdvanced::CallTouchscreenConfigDialog,
+ [this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); });
+ connect(advanced, &ConfigureInputAdvanced::CallMotionTouchConfigDialog,
+ [this, input_subsystem] {
+ CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
+ });
- connect(ui->handheld_configure, &QPushButton::clicked, this,
- [this] { CallConfigureDialog<ConfigureInputPlayer>(*this, 8, false); });
-
- connect(ui->debug_configure, &QPushButton::clicked, this,
- [this] { CallConfigureDialog<ConfigureInputPlayer>(*this, 9, true); });
-
- connect(ui->mouse_advanced, &QPushButton::clicked, this,
- [this] { CallConfigureDialog<ConfigureMouseAdvanced>(*this); });
+ connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
+ connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); });
- connect(ui->touchscreen_advanced, &QPushButton::clicked, this,
- [this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); });
+ RetranslateUI();
+ LoadConfiguration();
}
-ConfigureInput::~ConfigureInput() = default;
+QList<QWidget*> ConfigureInput::GetSubTabs() const {
+ return {
+ ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5,
+ ui->tabPlayer6, ui->tabPlayer7, ui->tabPlayer8, ui->tabAdvanced,
+ };
+}
void ConfigureInput::ApplyConfiguration() {
- for (std::size_t i = 0; i < players_controller.size(); ++i) {
- const auto controller_type_index = players_controller[i]->currentIndex();
-
- Settings::values.players[i].connected = controller_type_index != 0;
-
- if (controller_type_index > 0) {
- Settings::values.players[i].type =
- static_cast<Settings::ControllerType>(controller_type_index - 1);
- } else {
- Settings::values.players[i].type = Settings::ControllerType::DualJoycon;
- }
+ for (auto controller : player_controllers) {
+ controller->ApplyConfiguration();
}
+ advanced->ApplyConfiguration();
+
const bool pre_docked_mode = Settings::values.use_docked_mode;
- Settings::values.use_docked_mode = ui->use_docked_mode->isChecked();
+ Settings::values.use_docked_mode = ui->radioDocked->isChecked();
OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode);
- Settings::values
- .players[Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD)]
- .connected = ui->handheld_connected->isChecked();
- Settings::values.debug_pad_enabled = ui->debug_enabled->isChecked();
- Settings::values.mouse_enabled = ui->mouse_enabled->isChecked();
- Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked();
- Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked();
+
+ Settings::values.vibration_enabled = ui->vibrationGroup->isChecked();
}
void ConfigureInput::changeEvent(QEvent* event) {
@@ -146,94 +179,62 @@ void ConfigureInput::changeEvent(QEvent* event) {
RetranslateUI();
}
- QDialog::changeEvent(event);
+ QWidget::changeEvent(event);
}
void ConfigureInput::RetranslateUI() {
ui->retranslateUi(this);
- RetranslateControllerComboBoxes();
}
-void ConfigureInput::RetranslateControllerComboBoxes() {
- for (auto* controller_box : players_controller) {
- [[maybe_unused]] const QSignalBlocker blocker(controller_box);
-
- controller_box->clear();
- controller_box->addItems({tr("None"), tr("Pro Controller"), tr("Dual Joycons"),
- tr("Single Right Joycon"), tr("Single Left Joycon")});
- }
-
+void ConfigureInput::LoadConfiguration() {
LoadPlayerControllerIndices();
-}
+ UpdateDockedState(Settings::values.players[8].connected);
-void ConfigureInput::UpdateUIEnabled() {
- bool hit_disabled = false;
- for (auto* player : players_controller) {
- player->setDisabled(hit_disabled);
- if (hit_disabled) {
- player->setCurrentIndex(0);
- }
- if (!hit_disabled && player->currentIndex() == 0) {
- hit_disabled = true;
- }
- }
+ ui->vibrationGroup->setChecked(Settings::values.vibration_enabled);
+}
- for (std::size_t i = 0; i < players_controller.size(); ++i) {
- players_configure[i]->setEnabled(players_controller[i]->currentIndex() != 0);
+void ConfigureInput::LoadPlayerControllerIndices() {
+ for (std::size_t i = 0; i < player_connected.size(); ++i) {
+ const auto connected = Settings::values.players[i].connected ||
+ (i == 0 && Settings::values.players[8].connected);
+ player_connected[i]->setChecked(connected);
}
+}
- ui->handheld_connected->setChecked(ui->handheld_connected->isChecked() &&
- !ui->use_docked_mode->isChecked());
- ui->handheld_connected->setEnabled(!ui->use_docked_mode->isChecked());
- ui->handheld_configure->setEnabled(ui->handheld_connected->isChecked() &&
- !ui->use_docked_mode->isChecked());
- ui->mouse_advanced->setEnabled(ui->mouse_enabled->isChecked());
- ui->debug_configure->setEnabled(ui->debug_enabled->isChecked());
- ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked());
+void ConfigureInput::ClearAll() {
+ // We don't have a good way to know what tab is active, but we can find out by getting the
+ // parent of the consoleInputSettings
+ auto* player_tab = static_cast<ConfigureInputPlayer*>(ui->consoleInputSettings->parent());
+ player_tab->ClearAll();
}
-void ConfigureInput::LoadConfiguration() {
- std::stable_partition(
- Settings::values.players.begin(),
- Settings::values.players.begin() +
- Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD),
- [](const auto& player) { return player.connected; });
+void ConfigureInput::RestoreDefaults() {
+ // We don't have a good way to know what tab is active, but we can find out by getting the
+ // parent of the consoleInputSettings
+ auto* player_tab = static_cast<ConfigureInputPlayer*>(ui->consoleInputSettings->parent());
+ player_tab->RestoreDefaults();
+
+ ui->radioDocked->setChecked(true);
+ ui->radioUndocked->setChecked(false);
+ ui->vibrationGroup->setChecked(true);
+}
- LoadPlayerControllerIndices();
+void ConfigureInput::UpdateDockedState(bool is_handheld) {
+ // Disallow changing the console mode if the controller type is handheld.
+ ui->radioDocked->setEnabled(!is_handheld);
+ ui->radioUndocked->setEnabled(!is_handheld);
- ui->use_docked_mode->setChecked(Settings::values.use_docked_mode);
- ui->handheld_connected->setChecked(
- Settings::values
- .players[Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD)]
- .connected);
- ui->debug_enabled->setChecked(Settings::values.debug_pad_enabled);
- ui->mouse_enabled->setChecked(Settings::values.mouse_enabled);
- ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled);
- ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled);
-
- UpdateUIEnabled();
-}
+ ui->radioDocked->setChecked(Settings::values.use_docked_mode);
+ ui->radioUndocked->setChecked(!Settings::values.use_docked_mode);
-void ConfigureInput::LoadPlayerControllerIndices() {
- for (std::size_t i = 0; i < players_controller.size(); ++i) {
- const auto connected = Settings::values.players[i].connected;
- players_controller[i]->setCurrentIndex(
- connected ? static_cast<u8>(Settings::values.players[i].type) + 1 : 0);
+ // Also force into undocked mode if the controller type is handheld.
+ if (is_handheld) {
+ ui->radioUndocked->setChecked(true);
}
}
-void ConfigureInput::RestoreDefaults() {
- players_controller[0]->setCurrentIndex(2);
-
- for (std::size_t i = 1; i < players_controller.size(); ++i) {
- players_controller[i]->setCurrentIndex(0);
+void ConfigureInput::UpdateAllInputDevices() {
+ for (const auto& player : player_controllers) {
+ player->UpdateInputDevices();
}
-
- ui->use_docked_mode->setCheckState(Qt::Unchecked);
- ui->handheld_connected->setCheckState(Qt::Unchecked);
- ui->mouse_enabled->setCheckState(Qt::Unchecked);
- ui->keyboard_enabled->setCheckState(Qt::Unchecked);
- ui->debug_enabled->setCheckState(Qt::Unchecked);
- ui->touchscreen_enabled->setCheckState(Qt::Checked);
- UpdateUIEnabled();
}
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index 2f70cb3ca..0e8b2fd4e 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -7,37 +7,50 @@
#include <array>
#include <memory>
-#include <QDialog>
#include <QKeyEvent>
+#include <QWidget>
+
+#include "yuzu/configuration/configure_input_advanced.h"
+#include "yuzu/configuration/configure_input_player.h"
#include "ui_configure_input.h"
-class QPushButton;
+class QCheckBox;
class QString;
class QTimer;
+namespace InputCommon {
+class InputSubsystem;
+}
+
namespace Ui {
class ConfigureInput;
}
void OnDockedModeChanged(bool last_state, bool new_state);
-class ConfigureInput : public QDialog {
+class ConfigureInput : public QWidget {
Q_OBJECT
public:
explicit ConfigureInput(QWidget* parent = nullptr);
~ConfigureInput() override;
- /// Save all button configurations to settings file
+ /// Initializes the input dialog with the given input subsystem.
+ void Initialize(InputCommon::InputSubsystem* input_subsystem_, std::size_t max_players = 8);
+
+ /// Save all button configurations to settings file.
void ApplyConfiguration();
+ QList<QWidget*> GetSubTabs() const;
+
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
- void RetranslateControllerComboBoxes();
+ void ClearAll();
- void UpdateUIEnabled();
+ void UpdateDockedState(bool is_handheld);
+ void UpdateAllInputDevices();
/// Load configuration settings.
void LoadConfiguration();
@@ -48,6 +61,8 @@ private:
std::unique_ptr<Ui::ConfigureInput> ui;
- std::array<QComboBox*, 8> players_controller;
- std::array<QPushButton*, 8> players_configure;
+ std::array<ConfigureInputPlayer*, 8> player_controllers;
+ std::array<QWidget*, 8> player_tabs;
+ std::array<QCheckBox*, 8> player_connected;
+ ConfigureInputAdvanced* advanced;
};
diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui
index efffd8487..136955224 100644
--- a/src/yuzu/configuration/configure_input.ui
+++ b/src/yuzu/configuration/configure_input.ui
@@ -1,529 +1,554 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureInput</class>
- <widget class="QDialog" name="ConfigureInput">
+ <widget class="QWidget" name="ConfigureInput">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>384</width>
- <height>576</height>
+ <width>700</width>
+ <height>540</height>
</rect>
</property>
<property name="windowTitle">
- <string>Custom Input Settings</string>
+ <string>ConfigureInput</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="spacing">
+ <number>2</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>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QGroupBox" name="gridGroupBox_1">
- <property name="title">
- <string>Players</string>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="1" column="2">
- <widget class="QComboBox" name="player1_combobox">
- <property name="minimumSize">
- <size>
- <width>110</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="1" column="3">
- <widget class="QPushButton" name="player1_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Controller Type</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- <item row="2" column="2">
- <widget class="QComboBox" name="player2_combobox">
- <property name="minimumSize">
- <size>
- <width>110</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="3" column="2">
- <widget class="QComboBox" name="player3_combobox">
- <property name="minimumSize">
- <size>
- <width>110</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="4" column="2">
- <widget class="QComboBox" name="player4_combobox">
- <property name="minimumSize">
- <size>
- <width>110</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="5" column="2">
- <widget class="QComboBox" name="player5_combobox">
- <property name="minimumSize">
- <size>
- <width>110</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="6" column="2">
- <widget class="QComboBox" name="player6_combobox">
- <property name="minimumSize">
- <size>
- <width>110</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="7" column="2">
- <widget class="QComboBox" name="player7_combobox">
- <property name="minimumSize">
- <size>
- <width>110</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="8" column="2">
- <widget class="QComboBox" name="player8_combobox">
- <property name="minimumSize">
- <size>
- <width>110</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="2" column="3">
- <widget class="QPushButton" name="player2_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="3" column="3">
- <widget class="QPushButton" name="player3_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="4" column="3">
- <widget class="QPushButton" name="player4_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="5" column="3">
- <widget class="QPushButton" name="player5_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="6" column="3">
- <widget class="QPushButton" name="player6_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="7" column="3">
- <widget class="QPushButton" name="player7_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="8" column="3">
- <widget class="QPushButton" name="player8_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="0" column="4">
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="1" column="1">
- <widget class="QLabel" name="label_3">
- <property name="minimumSize">
- <size>
- <width>55</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>Player 1</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QLabel" name="label_4">
- <property name="text">
- <string>Player 2</string>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QLabel" name="label_5">
- <property name="text">
- <string>Player 3</string>
- </property>
- </widget>
- </item>
- <item row="4" column="1">
- <widget class="QLabel" name="label_6">
- <property name="text">
- <string>Player 4</string>
- </property>
- </widget>
- </item>
- <item row="5" column="1">
- <widget class="QLabel" name="label_7">
- <property name="text">
- <string>Player 5</string>
- </property>
- </widget>
- </item>
- <item row="6" column="1">
- <widget class="QLabel" name="label_8">
- <property name="text">
- <string>Player 6</string>
- </property>
- </widget>
- </item>
- <item row="7" column="1">
- <widget class="QLabel" name="label_9">
- <property name="text">
- <string>Player 7</string>
- </property>
- </widget>
- </item>
- <item row="8" column="1">
- <widget class="QLabel" name="label_10">
- <property name="text">
- <string>Player 8</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="gridGroupBox_2">
- <property name="title">
- <string>Handheld</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="1" column="2">
- <spacer name="horizontalSpacer_5">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Fixed</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>72</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="1" column="4">
- <spacer name="horizontalSpacer_4">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="1" column="3">
- <widget class="QPushButton" name="handheld_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <spacer name="horizontalSpacer_3">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="1" column="1">
- <widget class="QCheckBox" name="handheld_connected">
- <property name="text">
- <string>Joycons Docked</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QCheckBox" name="use_docked_mode">
- <property name="text">
- <string>Use Docked Mode</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="gridGroupBox_3">
- <property name="title">
- <string>Other</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="1" column="1">
- <widget class="QCheckBox" name="keyboard_enabled">
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>23</height>
- </size>
- </property>
- <property name="text">
- <string>Keyboard</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QCheckBox" name="debug_enabled">
- <property name="text">
- <string>Debug Controller</string>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QCheckBox" name="touchscreen_enabled">
- <property name="text">
- <string>Touchscreen</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QCheckBox" name="mouse_enabled">
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>23</height>
- </size>
- </property>
- <property name="text">
- <string>Mouse</string>
- </property>
- </widget>
- </item>
- <item row="0" column="4">
- <spacer name="horizontalSpacer_7">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="0" column="2">
- <spacer name="horizontalSpacer_8">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Fixed</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>76</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="0" column="0">
- <spacer name="horizontalSpacer_6">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="3" column="3">
- <widget class="QPushButton" name="touchscreen_advanced">
- <property name="text">
- <string>Advanced</string>
- </property>
- </widget>
- </item>
- <item row="2" column="3">
- <widget class="QPushButton" name="debug_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="0" column="3">
- <widget class="QPushButton" name="mouse_advanced">
- <property name="text">
- <string>Advanced</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
- <widget class="QPushButton" name="restore_defaults_button">
- <property name="text">
- <string>Restore Defaults</string>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tabPlayer1">
+ <property name="accessibleName">
+ <string>Player 1</string>
+ </property>
+ <attribute name="title">
+ <string>Player 1</string>
+ </attribute>
+ </widget>
+ <widget class="QWidget" name="tabPlayer2">
+ <property name="accessibleName">
+ <string>Player 2</string>
+ </property>
+ <attribute name="title">
+ <string>Player 2</string>
+ </attribute>
+ </widget>
+ <widget class="QWidget" name="tabPlayer3">
+ <property name="accessibleName">
+ <string>Player 3</string>
+ </property>
+ <attribute name="title">
+ <string>Player 3</string>
+ </attribute>
+ </widget>
+ <widget class="QWidget" name="tabPlayer4">
+ <property name="accessibleName">
+ <string>Player 4</string>
+ </property>
+ <attribute name="title">
+ <string>Player 4</string>
+ </attribute>
+ </widget>
+ <widget class="QWidget" name="tabPlayer5">
+ <property name="accessibleName">
+ <string>Player 5</string>
+ </property>
+ <attribute name="title">
+ <string>Player 5</string>
+ </attribute>
+ </widget>
+ <widget class="QWidget" name="tabPlayer6">
+ <property name="accessibleName">
+ <string>Player 6</string>
+ </property>
+ <attribute name="title">
+ <string>Player 6</string>
+ </attribute>
+ </widget>
+ <widget class="QWidget" name="tabPlayer7">
+ <property name="accessibleName">
+ <string>Player 7</string>
+ </property>
+ <attribute name="title">
+ <string>Player 7</string>
+ </attribute>
+ </widget>
+ <widget class="QWidget" name="tabPlayer8">
+ <property name="accessibleName">
+ <string>Player 8</string>
+ </property>
+ <attribute name="title">
+ <string>Player 8</string>
+ </attribute>
+ </widget>
+ <widget class="QWidget" name="tabAdvanced">
+ <property name="accessibleName">
+ <string>Advanced</string>
+ </property>
+ <attribute name="title">
+ <string>Advanced</string>
+ </attribute>
+ </widget>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignVCenter">
+ <widget class="QWidget" name="consoleInputSettings" native="true">
+ <layout class="QHBoxLayout" name="buttonsBottomRightHorizontalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignVCenter">
+ <widget class="QGroupBox" name="handheldGroup">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Console Mode</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QRadioButton" name="radioDocked">
+ <property name="text">
+ <string>Docked</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="radioUndocked">
+ <property name="text">
+ <string>Undocked</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroup">
+ <property name="title">
+ <string>Vibration</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <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="QSpinBox" name="vibrationSpin">
+ <property name="minimumSize">
+ <size>
+ <width>65</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>65</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>200</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="motionGroup">
+ <property name="title">
+ <string>Motion</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <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="motionButton">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignVCenter">
+ <widget class="QWidget" name="widget" native="true">
+ <layout class="QGridLayout" name="gridLayout_2">
+ <property name="leftMargin">
+ <number>5</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer_9">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <property name="rightMargin">
+ <number>0</number>
</property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
+ <property name="bottomMargin">
+ <number>0</number>
</property>
- </spacer>
- </item>
- <item>
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ <property name="spacing">
+ <number>3</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
+ <item row="1" column="2">
+ <widget class="QCheckBox" name="checkboxPlayer2Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Controllers</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="4">
+ <widget class="QCheckBox" name="checkboxPlayer4Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QCheckBox" name="checkboxPlayer3Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="5">
+ <widget class="QCheckBox" name="checkboxPlayer5Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>1</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="7">
+ <widget class="QCheckBox" name="checkboxPlayer7Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="6">
+ <widget class="QCheckBox" name="checkboxPlayer6Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QCheckBox" name="checkboxPlayer1Connected">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="8">
+ <widget class="QCheckBox" name="checkboxPlayer8Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>2</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>3</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="4">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>4</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="5">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>5</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="6">
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>6</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="7">
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>7</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="8">
+ <widget class="QLabel" name="label_9">
+ <property name="text">
+ <string>8</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_10">
+ <property name="text">
+ <string>Connected</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignBottom">
+ <widget class="QPushButton" name="buttonRestoreDefaults">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="sizeIncrement">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="baseSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Defaults</string>
+ </property>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignBottom">
+ <widget class="QPushButton" name="buttonClearAll">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="sizeIncrement">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="baseSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Clear</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
</item>
</layout>
</widget>
<resources/>
- <connections>
- <connection>
- <sender>buttonBox</sender>
- <signal>accepted()</signal>
- <receiver>ConfigureInput</receiver>
- <slot>accept()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>294</x>
- <y>553</y>
- </hint>
- <hint type="destinationlabel">
- <x>191</x>
- <y>287</y>
- </hint>
- </hints>
- </connection>
- <connection>
- <sender>buttonBox</sender>
- <signal>rejected()</signal>
- <receiver>ConfigureInput</receiver>
- <slot>reject()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>294</x>
- <y>553</y>
- </hint>
- <hint type="destinationlabel">
- <x>191</x>
- <y>287</y>
- </hint>
- </hints>
- </connection>
- </connections>
+ <connections/>
</ui>
diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp
new file mode 100644
index 000000000..81f9dc16c
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_advanced.cpp
@@ -0,0 +1,171 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QColorDialog>
+#include "core/core.h"
+#include "core/settings.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>()) {
+ ui->setupUi(this);
+
+ controllers_color_buttons = {{
+ {
+ ui->player1_left_body_button,
+ ui->player1_left_buttons_button,
+ ui->player1_right_body_button,
+ ui->player1_right_buttons_button,
+ },
+ {
+ ui->player2_left_body_button,
+ ui->player2_left_buttons_button,
+ ui->player2_right_body_button,
+ ui->player2_right_buttons_button,
+ },
+ {
+ ui->player3_left_body_button,
+ ui->player3_left_buttons_button,
+ ui->player3_right_body_button,
+ ui->player3_right_buttons_button,
+ },
+ {
+ ui->player4_left_body_button,
+ ui->player4_left_buttons_button,
+ ui->player4_right_body_button,
+ ui->player4_right_buttons_button,
+ },
+ {
+ ui->player5_left_body_button,
+ ui->player5_left_buttons_button,
+ ui->player5_right_body_button,
+ ui->player5_right_buttons_button,
+ },
+ {
+ ui->player6_left_body_button,
+ ui->player6_left_buttons_button,
+ ui->player6_right_body_button,
+ ui->player6_right_buttons_button,
+ },
+ {
+ ui->player7_left_body_button,
+ ui->player7_left_buttons_button,
+ ui->player7_right_body_button,
+ ui->player7_right_buttons_button,
+ },
+ {
+ ui->player8_left_body_button,
+ ui->player8_left_buttons_button,
+ ui->player8_right_body_button,
+ ui->player8_right_buttons_button,
+ },
+ }};
+
+ for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) {
+ auto& color_buttons = controllers_color_buttons[player_idx];
+ for (std::size_t button_idx = 0; button_idx < color_buttons.size(); ++button_idx) {
+ connect(color_buttons[button_idx], &QPushButton::clicked, this,
+ [this, player_idx, button_idx] {
+ OnControllerButtonClick(static_cast<int>(player_idx),
+ static_cast<int>(button_idx));
+ });
+ }
+ }
+
+ connect(ui->mouse_enabled, &QCheckBox::stateChanged, this,
+ &ConfigureInputAdvanced::UpdateUIEnabled);
+ connect(ui->debug_enabled, &QCheckBox::stateChanged, this,
+ &ConfigureInputAdvanced::UpdateUIEnabled);
+ connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this,
+ &ConfigureInputAdvanced::UpdateUIEnabled);
+
+ connect(ui->debug_configure, &QPushButton::clicked, this,
+ [this] { CallDebugControllerDialog(); });
+ connect(ui->mouse_advanced, &QPushButton::clicked, this, [this] { CallMouseConfigDialog(); });
+ connect(ui->touchscreen_advanced, &QPushButton::clicked, this,
+ [this] { CallTouchscreenConfigDialog(); });
+ connect(ui->buttonMotionTouch, &QPushButton::clicked, this,
+ &ConfigureInputAdvanced::CallMotionTouchConfigDialog);
+
+ LoadConfiguration();
+}
+
+ConfigureInputAdvanced::~ConfigureInputAdvanced() = default;
+
+void ConfigureInputAdvanced::OnControllerButtonClick(int player_idx, int button_idx) {
+ const QColor new_bg_color = QColorDialog::getColor(controllers_colors[player_idx][button_idx]);
+ if (!new_bg_color.isValid()) {
+ return;
+ }
+ controllers_colors[player_idx][button_idx] = new_bg_color;
+ controllers_color_buttons[player_idx][button_idx]->setStyleSheet(
+ QStringLiteral("background-color: %1; min-width: 55px;")
+ .arg(controllers_colors[player_idx][button_idx].name()));
+}
+
+void ConfigureInputAdvanced::ApplyConfiguration() {
+ for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) {
+ auto& player = Settings::values.players[player_idx];
+ std::array<u32, 4> colors{};
+ std::transform(controllers_colors[player_idx].begin(), controllers_colors[player_idx].end(),
+ colors.begin(), [](QColor color) { return color.rgb(); });
+
+ player.body_color_left = colors[0];
+ player.button_color_left = colors[1];
+ player.body_color_right = colors[2];
+ player.button_color_right = colors[3];
+ }
+
+ Settings::values.debug_pad_enabled = ui->debug_enabled->isChecked();
+ Settings::values.mouse_enabled = ui->mouse_enabled->isChecked();
+ Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked();
+ Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked();
+}
+
+void ConfigureInputAdvanced::LoadConfiguration() {
+ for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) {
+ auto& player = Settings::values.players[player_idx];
+ std::array<u32, 4> colors = {
+ player.body_color_left,
+ player.button_color_left,
+ player.body_color_right,
+ player.button_color_right,
+ };
+
+ std::transform(colors.begin(), colors.end(), controllers_colors[player_idx].begin(),
+ [](u32 rgb) { return QColor::fromRgb(rgb); });
+
+ for (std::size_t button_idx = 0; button_idx < colors.size(); ++button_idx) {
+ controllers_color_buttons[player_idx][button_idx]->setStyleSheet(
+ QStringLiteral("background-color: %1; min-width: 55px;")
+ .arg(controllers_colors[player_idx][button_idx].name()));
+ }
+ }
+
+ ui->debug_enabled->setChecked(Settings::values.debug_pad_enabled);
+ ui->mouse_enabled->setChecked(Settings::values.mouse_enabled);
+ ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled);
+ ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled);
+
+ UpdateUIEnabled();
+}
+
+void ConfigureInputAdvanced::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void ConfigureInputAdvanced::RetranslateUI() {
+ ui->retranslateUi(this);
+}
+
+void ConfigureInputAdvanced::UpdateUIEnabled() {
+ ui->mouse_advanced->setEnabled(ui->mouse_enabled->isChecked());
+ ui->debug_configure->setEnabled(ui->debug_enabled->isChecked());
+ ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked());
+}
diff --git a/src/yuzu/configuration/configure_input_advanced.h b/src/yuzu/configuration/configure_input_advanced.h
new file mode 100644
index 000000000..50bb87768
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_advanced.h
@@ -0,0 +1,46 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <QWidget>
+
+class QColor;
+class QPushButton;
+
+namespace Ui {
+class ConfigureInputAdvanced;
+}
+
+class ConfigureInputAdvanced : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit ConfigureInputAdvanced(QWidget* parent = nullptr);
+ ~ConfigureInputAdvanced() override;
+
+ void ApplyConfiguration();
+
+signals:
+ void CallDebugControllerDialog();
+ void CallMouseConfigDialog();
+ void CallTouchscreenConfigDialog();
+ void CallMotionTouchConfigDialog();
+
+private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+ void UpdateUIEnabled();
+
+ void OnControllerButtonClick(int player_idx, int button_idx);
+
+ void LoadConfiguration();
+
+ std::unique_ptr<Ui::ConfigureInputAdvanced> ui;
+
+ std::array<std::array<QColor, 4>, 8> controllers_colors;
+ std::array<std::array<QPushButton*, 4>, 8> controllers_color_buttons;
+};
diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui
new file mode 100644
index 000000000..5958435fc
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_advanced.ui
@@ -0,0 +1,2688 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureInputAdvanced</class>
+ <widget class="QWidget" name="ConfigureInputAdvanced">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>710</width>
+ <height>580</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Configure Input</string>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <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>
+ <widget class="QWidget" name="mainInputAdvanced" native="true">
+ <layout class="QHBoxLayout" name="main" stretch="1,1">
+ <property name="spacing">
+ <number>9</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="leftInputAdvanced" native="true">
+ <layout class="QVBoxLayout" name="leftLayout" stretch="0">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="joyconColorsGroup">
+ <property name="title">
+ <string>Joycon Colors</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3" stretch="1,1">
+ <property name="leftMargin">
+ <number>9</number>
+ </property>
+ <property name="topMargin">
+ <number>9</number>
+ </property>
+ <property name="rightMargin">
+ <number>9</number>
+ </property>
+ <property name="bottomMargin">
+ <number>9</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="topLeftInputAdvanced" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player12Widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="player1Group">
+ <property name="title">
+ <string>Player 1</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player1LeftJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_14">
+ <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="player1LeftBodyGroup">
+ <property name="title">
+ <string>L Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_66">
+ <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="player1_left_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player1LeftButtonsGroup">
+ <property name="title">
+ <string>L Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_67">
+ <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="player1_left_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player1RightJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_14">
+ <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="player1RightBodyGroup">
+ <property name="title">
+ <string>R Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_64">
+ <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="player1_right_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player1RightButtonsGroup">
+ <property name="title">
+ <string>R Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_65">
+ <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="player1_right_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="player2Group">
+ <property name="title">
+ <string>Player 2</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_14">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player2LeftJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_15">
+ <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="player2LeftBodyGroup">
+ <property name="title">
+ <string>L Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_70">
+ <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="player2_left_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player2LeftButtonsGroup">
+ <property name="title">
+ <string>L Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_71">
+ <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="player2_left_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player2RightJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_15">
+ <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="player2RightBodyGroup">
+ <property name="title">
+ <string>R Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_68">
+ <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="player2_right_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player2RightButtonsGroup">
+ <property name="title">
+ <string>R Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_69">
+ <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="player2_right_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player34Widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="player3Group">
+ <property name="title">
+ <string>Player 3</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_15">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player3LeftJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_16">
+ <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="player3LeftBodyGroup">
+ <property name="title">
+ <string>L Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_74">
+ <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="player3_left_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player3LeftButtonsGroup">
+ <property name="title">
+ <string>L Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_75">
+ <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="player3_left_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player3RightJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_16">
+ <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="player3RightBodyGroup">
+ <property name="title">
+ <string>R Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_72">
+ <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="player3_right_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player3RightButtonsGroup">
+ <property name="title">
+ <string>R Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_73">
+ <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="player3_right_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="player4Group">
+ <property name="title">
+ <string>Player 4</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_16">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player4LeftJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_17">
+ <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="player4LeftBodyGroup">
+ <property name="title">
+ <string>L Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_78">
+ <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="player4_left_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player4LeftButtonsGroup">
+ <property name="title">
+ <string>L Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_79">
+ <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="player4_left_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player4RightJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_17">
+ <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="player4RightBodyGroup">
+ <property name="title">
+ <string>R Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_76">
+ <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="player4_right_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player4RightButtonsGroup">
+ <property name="title">
+ <string>R Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_77">
+ <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="player4_right_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="bottomLeftInputAdvanced" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player56Widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="player5Group">
+ <property name="title">
+ <string>Player 5</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_10">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player5LeftJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_10">
+ <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="player5LeftBodyGroup">
+ <property name="title">
+ <string>L Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_50">
+ <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="player5_left_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player5LeftButtonsGroup">
+ <property name="title">
+ <string>L Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_51">
+ <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="player5_left_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player5RightJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_10">
+ <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="player5RightBodyGroup">
+ <property name="title">
+ <string>R Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_48">
+ <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="player5_right_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player5RightButtonsGroup">
+ <property name="title">
+ <string>R Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_49">
+ <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="player5_right_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="player6Group">
+ <property name="title">
+ <string>Player 6</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_11">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player6LeftJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_11">
+ <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="player6LeftBodyGroup">
+ <property name="title">
+ <string>L Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_54">
+ <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="player6_left_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player6LeftButtonsGroup">
+ <property name="title">
+ <string>L Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_55">
+ <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="player6_left_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player6RightJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_11">
+ <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="player6RightBodyGroup">
+ <property name="title">
+ <string>R Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_52">
+ <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="player6_right_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player6RightButtonsGroup">
+ <property name="title">
+ <string>R Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_53">
+ <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="player6_right_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player78Widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="player7Group">
+ <property name="title">
+ <string>Player 7</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_12">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player7LeftJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_12">
+ <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="player7LeftBodyGroup">
+ <property name="title">
+ <string>L Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_58">
+ <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="player7_left_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player7LeftButtonsGroup">
+ <property name="title">
+ <string>L Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_59">
+ <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="player7_left_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player7RightJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_12">
+ <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="player7RightBodyGroup">
+ <property name="title">
+ <string>R Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_56">
+ <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="player7_right_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player7RightButtonsGroup">
+ <property name="title">
+ <string>R Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_57">
+ <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="player7_right_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="player8Group">
+ <property name="title">
+ <string>Player 8</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_13">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player8LeftJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsLeftJoyconVerticalLayout_13">
+ <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="player8LeftBodyGroup">
+ <property name="title">
+ <string>L Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_62">
+ <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="player8_left_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player8LeftButtonsGroup">
+ <property name="title">
+ <string>L Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_63">
+ <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="player8_left_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player8RightJoycon" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsRightJoyconVerticalLayout_13">
+ <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="player8RightBodyGroup">
+ <property name="title">
+ <string>R Body</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_60">
+ <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="player8_right_body_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="player8RightButtonsGroup">
+ <property name="title">
+ <string>R Button</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_61">
+ <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="player8_right_buttons_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="rightInputAdvanced" native="true">
+ <layout class="QVBoxLayout" name="rightLayout" stretch="1,1">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="topRightInputAdvanced" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="gridGroupBox_3">
+ <property name="title">
+ <string>Other</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="keyboard_enabled">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>23</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Keyboard</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="2">
+ <widget class="QPushButton" name="touchscreen_advanced">
+ <property name="text">
+ <string>Advanced</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <spacer name="horizontalSpacer_8">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>76</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="2">
+ <widget class="QPushButton" name="mouse_advanced">
+ <property name="text">
+ <string>Advanced</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QCheckBox" name="touchscreen_enabled">
+ <property name="text">
+ <string>Touchscreen</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="mouse_enabled">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>23</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Mouse</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QLabel" name="motion_touch">
+ <property name="text">
+ <string>Motion / Touch</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="2">
+ <widget class="QPushButton" name="buttonMotionTouch">
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QCheckBox" name="debug_enabled">
+ <property name="text">
+ <string>Debug Controller</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="2">
+ <widget class="QPushButton" name="debug_configure">
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="bottomRightInputAdvanced" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_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>
+ <item>
+ <spacer name="mainVerticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ </resources>
+ <connections/>
+</ui>
diff --git a/src/yuzu/configuration/configure_input_dialog.cpp b/src/yuzu/configuration/configure_input_dialog.cpp
new file mode 100644
index 000000000..1866003c2
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_dialog.cpp
@@ -0,0 +1,37 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "ui_configure_input_dialog.h"
+#include "yuzu/configuration/configure_input_dialog.h"
+
+ConfigureInputDialog::ConfigureInputDialog(QWidget* parent, std::size_t max_players,
+ InputCommon::InputSubsystem* input_subsystem)
+ : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputDialog>()),
+ input_widget(new ConfigureInput(this)) {
+ ui->setupUi(this);
+
+ input_widget->Initialize(input_subsystem, max_players);
+
+ ui->inputLayout->addWidget(input_widget);
+
+ RetranslateUI();
+}
+
+ConfigureInputDialog::~ConfigureInputDialog() = default;
+
+void ConfigureInputDialog::ApplyConfiguration() {
+ input_widget->ApplyConfiguration();
+}
+
+void ConfigureInputDialog::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QDialog::changeEvent(event);
+}
+
+void ConfigureInputDialog::RetranslateUI() {
+ ui->retranslateUi(this);
+}
diff --git a/src/yuzu/configuration/configure_input_dialog.h b/src/yuzu/configuration/configure_input_dialog.h
new file mode 100644
index 000000000..d1bd865f9
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_dialog.h
@@ -0,0 +1,38 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QDialog>
+#include "yuzu/configuration/configure_input.h"
+
+class QPushButton;
+
+namespace InputCommon {
+class InputSubsystem;
+}
+
+namespace Ui {
+class ConfigureInputDialog;
+}
+
+class ConfigureInputDialog : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit ConfigureInputDialog(QWidget* parent, std::size_t max_players,
+ InputCommon::InputSubsystem* input_subsystem);
+ ~ConfigureInputDialog() override;
+
+ void ApplyConfiguration();
+
+private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ std::unique_ptr<Ui::ConfigureInputDialog> ui;
+
+ ConfigureInput* input_widget;
+};
diff --git a/src/yuzu/configuration/configure_input_dialog.ui b/src/yuzu/configuration/configure_input_dialog.ui
new file mode 100644
index 000000000..b92ddb200
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_dialog.ui
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureInputDialog</class>
+ <widget class="QDialog" name="ConfigureInputDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>70</width>
+ <height>540</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Configure Input</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>9</number>
+ </property>
+ <property name="topMargin">
+ <number>9</number>
+ </property>
+ <property name="rightMargin">
+ <number>9</number>
+ </property>
+ <property name="bottomMargin">
+ <number>9</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="inputLayout"/>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ConfigureInputDialog</receiver>
+ <slot>accept()</slot>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 00433926d..a53578837 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -5,39 +5,97 @@
#include <algorithm>
#include <memory>
#include <utility>
-#include <QColorDialog>
#include <QGridLayout>
+#include <QInputDialog>
#include <QKeyEvent>
#include <QMenu>
#include <QMessageBox>
#include <QTimer>
-#include "common/assert.h"
#include "common/param_package.h"
+#include "core/core.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/sm/sm.h"
+#include "input_common/gcadapter/gc_poller.h"
#include "input_common/main.h"
#include "ui_configure_input_player.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_input_player.h"
+constexpr std::size_t HANDHELD_INDEX = 8;
+
const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM>
ConfigureInputPlayer::analog_sub_buttons{{
"up",
"down",
"left",
"right",
- "modifier",
}};
-static void LayerGridElements(QGridLayout* grid, QWidget* item, QWidget* onTopOf) {
- const int index1 = grid->indexOf(item);
- const int index2 = grid->indexOf(onTopOf);
- int row, column, rowSpan, columnSpan;
- grid->getItemPosition(index2, &row, &column, &rowSpan, &columnSpan);
- grid->takeAt(index1);
- grid->addWidget(item, row, column, rowSpan, columnSpan);
+namespace {
+
+void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index,
+ bool connected) {
+ Core::System& system{Core::System::GetInstance()};
+ if (!system.IsPoweredOn()) {
+ return;
+ }
+ Service::SM::ServiceManager& sm = system.ServiceManager();
+
+ auto& npad =
+ sm.GetService<Service::HID::Hid>("hid")
+ ->GetAppletResource()
+ ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
+
+ npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected);
}
-static QString GetKeyName(int key_code) {
+/// Maps the controller type combobox index to Controller Type enum
+constexpr Settings::ControllerType GetControllerTypeFromIndex(int index) {
+ switch (index) {
+ case 0:
+ default:
+ return Settings::ControllerType::ProController;
+ case 1:
+ return Settings::ControllerType::DualJoyconDetached;
+ case 2:
+ return Settings::ControllerType::LeftJoycon;
+ case 3:
+ return Settings::ControllerType::RightJoycon;
+ case 4:
+ return Settings::ControllerType::Handheld;
+ }
+}
+
+/// Maps the Controller Type enum to controller type combobox index
+constexpr int GetIndexFromControllerType(Settings::ControllerType type) {
+ switch (type) {
+ case Settings::ControllerType::ProController:
+ default:
+ return 0;
+ case Settings::ControllerType::DualJoyconDetached:
+ return 1;
+ case Settings::ControllerType::LeftJoycon:
+ return 2;
+ case Settings::ControllerType::RightJoycon:
+ return 3;
+ case Settings::ControllerType::Handheld:
+ return 4;
+ }
+}
+
+QString GetKeyName(int key_code) {
switch (key_code) {
+ case Qt::LeftButton:
+ return QObject::tr("Click 0");
+ case Qt::RightButton:
+ return QObject::tr("Click 1");
+ case Qt::MiddleButton:
+ return QObject::tr("Click 2");
+ case Qt::BackButton:
+ return QObject::tr("Click 3");
+ case Qt::ForwardButton:
+ return QObject::tr("Click 4");
case Qt::Key_Shift:
return QObject::tr("Shift");
case Qt::Key_Control:
@@ -51,9 +109,16 @@ static QString GetKeyName(int key_code) {
}
}
-static void SetAnalogButton(const Common::ParamPackage& input_param,
- Common::ParamPackage& analog_param, const std::string& button_name) {
- if (analog_param.Get("engine", "") != "analog_from_button") {
+void SetAnalogParam(const Common::ParamPackage& input_param, Common::ParamPackage& analog_param,
+ const std::string& button_name) {
+ // The poller returned a complete axis, so set all the buttons
+ if (input_param.Has("axis_x") && input_param.Has("axis_y")) {
+ analog_param = input_param;
+ return;
+ }
+ // Check if the current configuration has either no engine or an axis binding.
+ // Clears out the old binding and adds one with analog_from_button.
+ if (!analog_param.Has("engine") || analog_param.Has("axis_x") || analog_param.Has("axis_y")) {
analog_param = {
{"engine", "analog_from_button"},
};
@@ -61,7 +126,7 @@ static void SetAnalogButton(const Common::ParamPackage& input_param,
analog_param.Set(button_name, input_param.Serialize());
}
-static QString ButtonToText(const Common::ParamPackage& param) {
+QString ButtonToText(const Common::ParamPackage& param) {
if (!param.Has("engine")) {
return QObject::tr("[not set]");
}
@@ -111,7 +176,7 @@ static QString ButtonToText(const Common::ParamPackage& param) {
return QObject::tr("[unknown]");
}
-static QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) {
+QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) {
if (!param.Has("engine")) {
return QObject::tr("[not set]");
}
@@ -161,22 +226,25 @@ static QString AnalogToText(const Common::ParamPackage& param, const std::string
}
return QObject::tr("[unknown]");
}
-
-ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index, bool debug)
- : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index),
- debug(debug), timeout_timer(std::make_unique<QTimer>()),
- poll_timer(std::make_unique<QTimer>()) {
+} // namespace
+
+ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index,
+ QWidget* bottom_row,
+ InputCommon::InputSubsystem* input_subsystem_,
+ bool debug)
+ : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index),
+ debug(debug), input_subsystem{input_subsystem_}, timeout_timer(std::make_unique<QTimer>()),
+ poll_timer(std::make_unique<QTimer>()), bottom_row(bottom_row) {
ui->setupUi(this);
+
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->buttonLStickLeft, ui->buttonLStickUp, ui->buttonLStickRight, ui->buttonLStickDown,
- ui->buttonRStickLeft, ui->buttonRStickUp, ui->buttonRStickRight, ui->buttonRStickDown,
- 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->buttonSL, ui->buttonSR, ui->buttonHome, ui->buttonScreenshot,
};
analog_map_buttons = {{
@@ -185,216 +253,165 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
ui->buttonLStickDown,
ui->buttonLStickLeft,
ui->buttonLStickRight,
- ui->buttonLStickMod,
},
{
ui->buttonRStickUp,
ui->buttonRStickDown,
ui->buttonRStickLeft,
ui->buttonRStickRight,
- ui->buttonRStickMod,
},
}};
- debug_hidden = {
- ui->buttonSL, ui->labelSL,
- ui->buttonSR, ui->labelSR,
- ui->buttonLStick, ui->labelLStickPressed,
- ui->buttonRStick, ui->labelRStickPressed,
- ui->buttonHome, ui->labelHome,
- ui->buttonScreenshot, ui->labelScreenshot,
+ analog_map_deadzone_label = {ui->labelLStickDeadzone, ui->labelRStickDeadzone};
+ analog_map_deadzone_slider = {ui->sliderLStickDeadzone, ui->sliderRStickDeadzone};
+ analog_map_modifier_groupbox = {ui->buttonLStickModGroup, ui->buttonRStickModGroup};
+ analog_map_modifier_label = {ui->labelLStickModifierRange, ui->labelRStickModifierRange};
+ analog_map_modifier_slider = {ui->sliderLStickModifierRange, ui->sliderRStickModifierRange};
+ analog_map_range_groupbox = {ui->buttonLStickRangeGroup, ui->buttonRStickRangeGroup};
+ analog_map_range_spinbox = {ui->spinboxLStickRange, ui->spinboxRStickRange};
+
+ const auto ConfigureButtonClick = [&](QPushButton* button, Common::ParamPackage* param,
+ int default_val) {
+ connect(button, &QPushButton::clicked, [=, this] {
+ HandleClick(
+ button,
+ [=, this](Common::ParamPackage params) {
+ // Workaround for ZL & ZR for analog triggers like on XBOX
+ // controllers. Analog triggers (from controllers like the XBOX
+ // controller) would not work due to a different range of their
+ // signals (from 0 to 255 on analog triggers instead of -32768 to
+ // 32768 on analog joysticks). The SDL driver misinterprets analog
+ // triggers as analog joysticks.
+ // TODO: reinterpret the signal range for analog triggers to map the
+ // values correctly. This is required for the correct emulation of
+ // the analog triggers of the GameCube controller.
+ if (button == ui->buttonZL || button == ui->buttonZR) {
+ params.Set("direction", "+");
+ params.Set("threshold", "0.5");
+ }
+ *param = std::move(params);
+ },
+ InputCommon::Polling::DeviceType::Button);
+ });
};
- auto layout = Settings::values.players[player_index].type;
- if (debug)
- layout = Settings::ControllerType::DualJoycon;
-
- switch (layout) {
- case Settings::ControllerType::ProController:
- case Settings::ControllerType::DualJoycon:
- layout_hidden = {
- ui->buttonSL,
- ui->labelSL,
- ui->buttonSR,
- ui->labelSR,
- };
- break;
- case Settings::ControllerType::LeftJoycon:
- layout_hidden = {
- ui->right_body_button,
- ui->right_buttons_button,
- ui->right_body_label,
- ui->right_buttons_label,
- ui->buttonR,
- ui->labelR,
- ui->buttonZR,
- ui->labelZR,
- ui->labelHome,
- ui->buttonHome,
- ui->buttonPlus,
- ui->labelPlus,
- ui->RStick,
- ui->faceButtons,
- };
- break;
- case Settings::ControllerType::RightJoycon:
- layout_hidden = {
- ui->left_body_button, ui->left_buttons_button,
- ui->left_body_label, ui->left_buttons_label,
- ui->buttonL, ui->labelL,
- ui->buttonZL, ui->labelZL,
- ui->labelScreenshot, ui->buttonScreenshot,
- ui->buttonMinus, ui->labelMinus,
- ui->LStick, ui->Dpad,
- };
- break;
- }
-
- if (debug || layout == Settings::ControllerType::ProController) {
- ui->controller_color->hide();
- } else {
- if (layout == Settings::ControllerType::LeftJoycon ||
- layout == Settings::ControllerType::RightJoycon) {
- ui->horizontalSpacer_4->setGeometry({0, 0, 0, 0});
-
- LayerGridElements(ui->buttons, ui->shoulderButtons, ui->Dpad);
- LayerGridElements(ui->buttons, ui->misc, ui->RStick);
- LayerGridElements(ui->buttons, ui->Dpad, ui->faceButtons);
- LayerGridElements(ui->buttons, ui->RStick, ui->LStick);
- }
- }
-
- for (auto* widget : layout_hidden)
- widget->setVisible(false);
-
- analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog};
- analog_map_deadzone_and_modifier_slider = {ui->sliderLStickDeadzoneAndModifier,
- ui->sliderRStickDeadzoneAndModifier};
- analog_map_deadzone_and_modifier_slider_label = {ui->labelLStickDeadzoneAndModifier,
- ui->labelRStickDeadzoneAndModifier};
-
- for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
+ for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
auto* const button = button_map[button_id];
if (button == nullptr) {
continue;
}
-
- button->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(button, &QPushButton::clicked, [=] {
- HandleClick(button_map[button_id],
- [=](Common::ParamPackage params) {
- // Workaround for ZL & ZR for analog triggers like on XBOX controllors.
- // Analog triggers (from controllers like the XBOX controller) would not
- // work due to a different range of their signals (from 0 to 255 on
- // analog triggers instead of -32768 to 32768 on analog joysticks). The
- // SDL driver misinterprets analog triggers as analog joysticks.
- // TODO: reinterpret the signal range for analog triggers to map the
- // values correctly. This is required for the correct emulation of the
- // analog triggers of the GameCube controller.
- if (button_id == Settings::NativeButton::ZL ||
- button_id == Settings::NativeButton::ZR) {
- params.Set("direction", "+");
- params.Set("threshold", "0.5");
- }
- buttons_param[button_id] = std::move(params);
- },
- InputCommon::Polling::DeviceType::Button);
- });
- connect(button, &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) {
- QMenu context_menu;
- context_menu.addAction(tr("Clear"), [&] {
- buttons_param[button_id].Clear();
- button_map[button_id]->setText(tr("[not set]"));
- });
- context_menu.addAction(tr("Restore Default"), [&] {
- buttons_param[button_id] = Common::ParamPackage{
- InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
- button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
- });
- context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
- });
+ ConfigureButtonClick(button_map[button_id], &buttons_param[button_id],
+ Config::default_buttons[button_id]);
}
- for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
- for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
+ // Handle clicks for the modifier buttons as well.
+ ConfigureButtonClick(ui->buttonLStickMod, &lstick_mod, Config::default_stick_mod[0]);
+ ConfigureButtonClick(ui->buttonRStickMod, &rstick_mod, Config::default_stick_mod[1]);
+
+ for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
+ for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
+
if (analog_button == nullptr) {
continue;
}
- analog_button->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(analog_button, &QPushButton::clicked, [=]() {
- HandleClick(analog_map_buttons[analog_id][sub_button_id],
- [=](const Common::ParamPackage& params) {
- SetAnalogButton(params, analogs_param[analog_id],
- analog_sub_buttons[sub_button_id]);
- },
- InputCommon::Polling::DeviceType::Button);
+ connect(analog_button, &QPushButton::clicked, [=, this] {
+ HandleClick(
+ analog_map_buttons[analog_id][sub_button_id],
+ [=, this](const Common::ParamPackage& params) {
+ SetAnalogParam(params, analogs_param[analog_id],
+ analog_sub_buttons[sub_button_id]);
+ },
+ InputCommon::Polling::DeviceType::AnalogPreferred);
});
- connect(analog_button, &QPushButton::customContextMenuRequested,
- [=](const QPoint& menu_location) {
- QMenu context_menu;
- context_menu.addAction(tr("Clear"), [&] {
- analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
- analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
- });
- context_menu.addAction(tr("Restore Default"), [&] {
- Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
- Config::default_analogs[analog_id][sub_button_id])};
- SetAnalogButton(params, analogs_param[analog_id],
- analog_sub_buttons[sub_button_id]);
- analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText(
- analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
- });
- context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(
- menu_location));
- });
}
- connect(analog_map_stick[analog_id], &QPushButton::clicked, [=] {
- if (QMessageBox::information(
- this, tr("Information"),
- tr("After pressing OK, first move your joystick horizontally, "
- "and then vertically."),
- QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
- HandleClick(
- analog_map_stick[analog_id],
- [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; },
- InputCommon::Polling::DeviceType::Analog);
- }
+
+ connect(analog_map_range_spinbox[analog_id], qOverload<int>(&QSpinBox::valueChanged),
+ [=, this] {
+ const auto spinbox_value = analog_map_range_spinbox[analog_id]->value();
+ analogs_param[analog_id].Set("range", spinbox_value / 100.0f);
+ });
+
+ connect(analog_map_deadzone_slider[analog_id], &QSlider::valueChanged, [=, this] {
+ const auto slider_value = analog_map_deadzone_slider[analog_id]->value();
+ analog_map_deadzone_label[analog_id]->setText(tr("Deadzone: %1%").arg(slider_value));
+ analogs_param[analog_id].Set("deadzone", slider_value / 100.0f);
});
- connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, [=] {
- const float slider_value = analog_map_deadzone_and_modifier_slider[analog_id]->value();
- if (analogs_param[analog_id].Get("engine", "") == "sdl" ||
- analogs_param[analog_id].Get("engine", "") == "gcpad") {
- analog_map_deadzone_and_modifier_slider_label[analog_id]->setText(
- tr("Deadzone: %1%").arg(slider_value));
- analogs_param[analog_id].Set("deadzone", slider_value / 100.0f);
- } else {
- analog_map_deadzone_and_modifier_slider_label[analog_id]->setText(
- tr("Modifier Scale: %1%").arg(slider_value));
- analogs_param[analog_id].Set("modifier_scale", slider_value / 100.0f);
- }
+ connect(analog_map_modifier_slider[analog_id], &QSlider::valueChanged, [=, this] {
+ const auto slider_value = analog_map_modifier_slider[analog_id]->value();
+ analog_map_modifier_label[analog_id]->setText(
+ tr("Modifier Range: %1%").arg(slider_value));
+ analogs_param[analog_id].Set("modifier_scale", slider_value / 100.0f);
});
}
- connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
- connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); });
+ // Player Connected checkbox
+ connect(ui->groupConnectedController, &QGroupBox::toggled,
+ [this](bool checked) { emit Connected(checked); });
+
+ // Set up controller type. Only Player 1 can choose Handheld.
+ ui->comboControllerType->clear();
+
+ QStringList controller_types = {
+ tr("Pro Controller"),
+ tr("Dual Joycons"),
+ tr("Left Joycon"),
+ tr("Right Joycon"),
+ };
+
+ if (player_index == 0) {
+ controller_types.append(tr("Handheld"));
+ connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged),
+ [this](int index) {
+ emit HandheldStateChanged(GetControllerTypeFromIndex(index) ==
+ Settings::ControllerType::Handheld);
+ });
+ }
+
+ // The Debug Controller can only choose the Pro Controller.
+ if (debug) {
+ ui->buttonScreenshot->setEnabled(false);
+ ui->buttonHome->setEnabled(false);
+ ui->groupConnectedController->setCheckable(false);
+ QStringList debug_controller_types = {
+ tr("Pro Controller"),
+ };
+ ui->comboControllerType->addItems(debug_controller_types);
+ } else {
+ ui->comboControllerType->addItems(controller_types);
+ }
+
+ UpdateControllerIcon();
+ UpdateControllerAvailableButtons();
+ connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) {
+ UpdateControllerIcon();
+ UpdateControllerAvailableButtons();
+ });
+
+ connect(ui->comboDevices, qOverload<int>(&QComboBox::currentIndexChanged), this,
+ &ConfigureInputPlayer::UpdateMappingWithDefaults);
+
+ ui->buttonRefreshDevices->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
+ UpdateInputDevices();
+ connect(ui->buttonRefreshDevices, &QPushButton::clicked,
+ [this] { emit RefreshInputDevices(); });
timeout_timer->setSingleShot(true);
connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
connect(poll_timer.get(), &QTimer::timeout, [this] {
Common::ParamPackage params;
- if (InputCommon::GetGCButtons()->IsPolling()) {
- params = InputCommon::GetGCButtons()->GetNextInput();
+ if (input_subsystem->GetGCButtons()->IsPolling()) {
+ params = input_subsystem->GetGCButtons()->GetNextInput();
if (params.Has("engine")) {
SetPollingResult(params, false);
return;
}
}
- if (InputCommon::GetGCAnalogs()->IsPolling()) {
- params = InputCommon::GetGCAnalogs()->GetNextInput();
+ if (input_subsystem->GetGCAnalogs()->IsPolling()) {
+ params = input_subsystem->GetGCAnalogs()->GetNextInput();
if (params.Has("engine")) {
SetPollingResult(params, false);
return;
@@ -409,20 +426,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
}
});
- controller_color_buttons = {
- ui->left_body_button,
- ui->left_buttons_button,
- ui->right_body_button,
- ui->right_buttons_button,
- };
-
- for (std::size_t i = 0; i < controller_color_buttons.size(); ++i) {
- connect(controller_color_buttons[i], &QPushButton::clicked, this,
- [this, i] { OnControllerButtonClick(static_cast<int>(i)); });
- }
-
LoadConfiguration();
- resize(0, 0);
// TODO(wwylele): enable this when we actually emulate it
ui->buttonHome->setEnabled(false);
@@ -431,27 +435,43 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
ConfigureInputPlayer::~ConfigureInputPlayer() = default;
void ConfigureInputPlayer::ApplyConfiguration() {
- auto& buttons =
- debug ? Settings::values.debug_pad_buttons : Settings::values.players[player_index].buttons;
- auto& analogs =
- debug ? Settings::values.debug_pad_analogs : Settings::values.players[player_index].analogs;
+ auto& player = Settings::values.players[player_index];
+ auto& buttons = debug ? Settings::values.debug_pad_buttons : player.buttons;
+ auto& analogs = debug ? Settings::values.debug_pad_analogs : player.analogs;
std::transform(buttons_param.begin(), buttons_param.end(), buttons.begin(),
[](const Common::ParamPackage& param) { return param.Serialize(); });
std::transform(analogs_param.begin(), analogs_param.end(), analogs.begin(),
[](const Common::ParamPackage& param) { return param.Serialize(); });
- if (debug)
+ if (debug) {
+ return;
+ }
+
+ player.controller_type =
+ static_cast<Settings::ControllerType>(ui->comboControllerType->currentIndex());
+ player.connected = ui->groupConnectedController->isChecked();
+
+ // Player 2-8
+ if (player_index != 0) {
+ UpdateController(player.controller_type, player_index, player.connected);
return;
+ }
- std::array<u32, 4> colors{};
- std::transform(controller_colors.begin(), controller_colors.end(), colors.begin(),
- [](QColor color) { return color.rgb(); });
+ // Player 1 and Handheld
+ auto& handheld = Settings::values.players[HANDHELD_INDEX];
+ // If Handheld is selected, copy all the settings from Player 1 to Handheld.
+ if (player.controller_type == Settings::ControllerType::Handheld) {
+ handheld = player;
+ handheld.connected = ui->groupConnectedController->isChecked();
+ player.connected = false; // Disconnect Player 1
+ } else {
+ player.connected = ui->groupConnectedController->isChecked();
+ handheld.connected = false; // Disconnect Handheld
+ }
- Settings::values.players[player_index].body_color_left = colors[0];
- Settings::values.players[player_index].button_color_left = colors[1];
- Settings::values.players[player_index].body_color_right = colors[2];
- Settings::values.players[player_index].button_color_right = colors[3];
+ UpdateController(player.controller_type, player_index, player.connected);
+ UpdateController(Settings::ControllerType::Handheld, HANDHELD_INDEX, handheld.connected);
}
void ConfigureInputPlayer::changeEvent(QEvent* event) {
@@ -459,24 +479,16 @@ void ConfigureInputPlayer::changeEvent(QEvent* event) {
RetranslateUI();
}
- QDialog::changeEvent(event);
+ QWidget::changeEvent(event);
}
void ConfigureInputPlayer::RetranslateUI() {
ui->retranslateUi(this);
- UpdateButtonLabels();
-}
-
-void ConfigureInputPlayer::OnControllerButtonClick(int i) {
- const QColor new_bg_color = QColorDialog::getColor(controller_colors[i]);
- if (!new_bg_color.isValid())
- return;
- controller_colors[i] = new_bg_color;
- controller_color_buttons[i]->setStyleSheet(
- QStringLiteral("QPushButton { background-color: %1 }").arg(controller_colors[i].name()));
+ UpdateUI();
}
void ConfigureInputPlayer::LoadConfiguration() {
+ auto& player = Settings::values.players[player_index];
if (debug) {
std::transform(Settings::values.debug_pad_buttons.begin(),
Settings::values.debug_pad_buttons.end(), buttons_param.begin(),
@@ -485,56 +497,60 @@ void ConfigureInputPlayer::LoadConfiguration() {
Settings::values.debug_pad_analogs.end(), analogs_param.begin(),
[](const std::string& str) { return Common::ParamPackage(str); });
} else {
- std::transform(Settings::values.players[player_index].buttons.begin(),
- Settings::values.players[player_index].buttons.end(), buttons_param.begin(),
+ std::transform(player.buttons.begin(), player.buttons.end(), buttons_param.begin(),
[](const std::string& str) { return Common::ParamPackage(str); });
- std::transform(Settings::values.players[player_index].analogs.begin(),
- Settings::values.players[player_index].analogs.end(), analogs_param.begin(),
+ std::transform(player.analogs.begin(), player.analogs.end(), analogs_param.begin(),
[](const std::string& str) { return Common::ParamPackage(str); });
}
- UpdateButtonLabels();
+ UpdateUI();
- if (debug)
+ if (debug) {
return;
+ }
- std::array<u32, 4> colors = {
- Settings::values.players[player_index].body_color_left,
- Settings::values.players[player_index].button_color_left,
- Settings::values.players[player_index].body_color_right,
- Settings::values.players[player_index].button_color_right,
- };
-
- std::transform(colors.begin(), colors.end(), controller_colors.begin(),
- [](u32 rgb) { return QColor::fromRgb(rgb); });
+ ui->comboControllerType->setCurrentIndex(static_cast<int>(player.controller_type));
+ ui->groupConnectedController->setChecked(
+ player.connected ||
+ (player_index == 0 && Settings::values.players[HANDHELD_INDEX].connected));
+}
- for (std::size_t i = 0; i < colors.size(); ++i) {
- controller_color_buttons[i]->setStyleSheet(
- QStringLiteral("QPushButton { background-color: %1 }")
- .arg(controller_colors[i].name()));
+void ConfigureInputPlayer::UpdateInputDevices() {
+ input_devices = input_subsystem->GetInputDevices();
+ ui->comboDevices->clear();
+ for (auto device : input_devices) {
+ ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {});
}
}
void ConfigureInputPlayer::RestoreDefaults() {
- for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
+ // Reset Buttons
+ for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
buttons_param[button_id] = Common::ParamPackage{
InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
}
- for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
- for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
+ // Reset Modifier Buttons
+ lstick_mod =
+ Common::ParamPackage(InputCommon::GenerateKeyboardParam(Config::default_stick_mod[0]));
+ rstick_mod =
+ Common::ParamPackage(InputCommon::GenerateKeyboardParam(Config::default_stick_mod[1]));
+
+ // Reset Analogs
+ for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
+ 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])};
- SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
+ SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
}
}
-
- UpdateButtonLabels();
- ApplyConfiguration();
+ UpdateUI();
+ UpdateInputDevices();
+ ui->comboControllerType->setCurrentIndex(0);
}
void ConfigureInputPlayer::ClearAll() {
- for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
+ for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
const auto* const button = button_map[button_id];
if (button == nullptr || !button->isEnabled()) {
continue;
@@ -543,8 +559,11 @@ void ConfigureInputPlayer::ClearAll() {
buttons_param[button_id].Clear();
}
- for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
- for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
+ lstick_mod.Clear();
+ rstick_mod.Clear();
+
+ for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
+ for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
const auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
if (analog_button == nullptr || !analog_button->isEnabled()) {
continue;
@@ -554,17 +573,20 @@ void ConfigureInputPlayer::ClearAll() {
}
}
- UpdateButtonLabels();
- ApplyConfiguration();
+ UpdateUI();
+ UpdateInputDevices();
}
-void ConfigureInputPlayer::UpdateButtonLabels() {
- for (int button = 0; button < Settings::NativeButton::NumButtons; button++) {
+void ConfigureInputPlayer::UpdateUI() {
+ for (int button = 0; button < Settings::NativeButton::NumButtons; ++button) {
button_map[button]->setText(ButtonToText(buttons_param[button]));
}
- for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
- for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
+ ui->buttonLStickMod->setText(ButtonToText(lstick_mod));
+ ui->buttonRStickMod->setText(ButtonToText(rstick_mod));
+
+ for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
+ for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
if (analog_button == nullptr) {
@@ -574,99 +596,141 @@ void ConfigureInputPlayer::UpdateButtonLabels() {
analog_button->setText(
AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
}
- analog_map_stick[analog_id]->setText(tr("Set Analog Stick"));
+ const auto deadzone_label = analog_map_deadzone_label[analog_id];
+ const auto deadzone_slider = analog_map_deadzone_slider[analog_id];
+ const auto modifier_groupbox = analog_map_modifier_groupbox[analog_id];
+ const auto modifier_label = analog_map_modifier_label[analog_id];
+ const auto modifier_slider = analog_map_modifier_slider[analog_id];
+ const auto range_groupbox = analog_map_range_groupbox[analog_id];
+ const auto range_spinbox = analog_map_range_spinbox[analog_id];
+
+ int slider_value;
auto& param = analogs_param[analog_id];
- auto* const analog_stick_slider = analog_map_deadzone_and_modifier_slider[analog_id];
- auto* const analog_stick_slider_label =
- analog_map_deadzone_and_modifier_slider_label[analog_id];
-
- if (param.Has("engine")) {
- if (param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad") {
- if (!param.Has("deadzone")) {
- param.Set("deadzone", 0.1f);
- }
-
- analog_stick_slider->setValue(static_cast<int>(param.Get("deadzone", 0.1f) * 100));
- if (analog_stick_slider->value() == 0) {
- analog_stick_slider_label->setText(tr("Deadzone: 0%"));
- }
- } else {
- if (!param.Has("modifier_scale")) {
- param.Set("modifier_scale", 0.5f);
- }
-
- analog_stick_slider->setValue(
- static_cast<int>(param.Get("modifier_scale", 0.5f) * 100));
- if (analog_stick_slider->value() == 0) {
- analog_stick_slider_label->setText(tr("Modifier Scale: 0%"));
- }
+ const bool is_controller =
+ param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad";
+
+ if (is_controller) {
+ if (!param.Has("deadzone")) {
+ param.Set("deadzone", 0.1f);
}
+ slider_value = static_cast<int>(param.Get("deadzone", 0.1f) * 100);
+ deadzone_label->setText(tr("Deadzone: %1%").arg(slider_value));
+ deadzone_slider->setValue(slider_value);
+ if (!param.Has("range")) {
+ param.Set("range", 1.0f);
+ }
+ range_spinbox->setValue(static_cast<int>(param.Get("range", 1.0f) * 100));
+ } else {
+ if (!param.Has("modifier_scale")) {
+ param.Set("modifier_scale", 0.5f);
+ }
+ slider_value = static_cast<int>(param.Get("modifier_scale", 0.5f) * 100);
+ modifier_label->setText(tr("Modifier Range: %1%").arg(slider_value));
+ modifier_slider->setValue(slider_value);
}
+
+ deadzone_label->setVisible(is_controller);
+ deadzone_slider->setVisible(is_controller);
+ modifier_groupbox->setVisible(!is_controller);
+ modifier_label->setVisible(!is_controller);
+ modifier_slider->setVisible(!is_controller);
+ range_groupbox->setVisible(is_controller);
}
}
+void ConfigureInputPlayer::UpdateMappingWithDefaults() {
+ if (ui->comboDevices->currentIndex() < 2) {
+ return;
+ }
+ const auto& device = input_devices[ui->comboDevices->currentIndex()];
+ auto button_mapping = input_subsystem->GetButtonMappingForDevice(device);
+ auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device);
+ for (std::size_t i = 0; i < buttons_param.size(); ++i) {
+ buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)];
+ }
+ for (std::size_t i = 0; i < analogs_param.size(); ++i) {
+ analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)];
+ }
+
+ UpdateUI();
+}
+
void ConfigureInputPlayer::HandleClick(
QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type) {
- button->setText(tr("[press key]"));
+ button->setText(tr("[waiting]"));
button->setFocus();
- // Keyboard keys can only be used as button devices
- want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button;
- if (want_keyboard_keys) {
- const auto iter = std::find(button_map.begin(), button_map.end(), button);
- ASSERT(iter != button_map.end());
- const auto index = std::distance(button_map.begin(), iter);
- ASSERT(index < Settings::NativeButton::NumButtons && index >= 0);
- }
+ // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a
+ // controller, then they don't want keyboard/mouse input
+ want_keyboard_mouse = ui->comboDevices->currentIndex() < 2;
input_setter = new_input_setter;
- device_pollers = InputCommon::Polling::GetPollers(type);
+ device_pollers = input_subsystem->GetPollers(type);
for (auto& poller : device_pollers) {
poller->Start();
}
- grabKeyboard();
- grabMouse();
+ QWidget::grabMouse();
+ QWidget::grabKeyboard();
+
if (type == InputCommon::Polling::DeviceType::Button) {
- InputCommon::GetGCButtons()->BeginConfiguration();
+ input_subsystem->GetGCButtons()->BeginConfiguration();
} else {
- InputCommon::GetGCAnalogs()->BeginConfiguration();
+ input_subsystem->GetGCAnalogs()->BeginConfiguration();
}
- timeout_timer->start(5000); // Cancel after 5 seconds
- poll_timer->start(200); // Check for new inputs every 200ms
+
+ timeout_timer->start(2500); // Cancel after 2.5 seconds
+ poll_timer->start(50); // Check for new inputs every 50ms
}
void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) {
- releaseKeyboard();
- releaseMouse();
timeout_timer->stop();
poll_timer->stop();
for (auto& poller : device_pollers) {
poller->Stop();
}
- InputCommon::GetGCButtons()->EndConfiguration();
- InputCommon::GetGCAnalogs()->EndConfiguration();
+ QWidget::releaseMouse();
+ QWidget::releaseKeyboard();
+
+ input_subsystem->GetGCButtons()->EndConfiguration();
+ input_subsystem->GetGCAnalogs()->EndConfiguration();
if (!abort) {
(*input_setter)(params);
}
- UpdateButtonLabels();
+ UpdateUI();
input_setter = std::nullopt;
}
+void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) {
+ if (!input_setter || !event) {
+ return;
+ }
+
+ if (want_keyboard_mouse) {
+ SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())},
+ false);
+ } else {
+ // We don't want any mouse buttons, so don't stop polling
+ return;
+ }
+
+ SetPollingResult({}, true);
+}
+
void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
if (!input_setter || !event) {
return;
}
if (event->key() != Qt::Key_Escape) {
- if (want_keyboard_keys) {
+ if (want_keyboard_mouse) {
SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
false);
} else {
@@ -674,5 +738,108 @@ void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
return;
}
}
+
SetPollingResult({}, true);
}
+
+void ConfigureInputPlayer::UpdateControllerIcon() {
+ // We aren't using Qt's built in theme support here since we aren't drawing an icon (and its
+ // "nonstandard" to use an image through the icon support)
+ const QString stylesheet = [this] {
+ switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) {
+ case Settings::ControllerType::ProController:
+ return QStringLiteral("image: url(:/controller/pro_controller%0)");
+ case Settings::ControllerType::DualJoyconDetached:
+ return QStringLiteral("image: url(:/controller/dual_joycon%0)");
+ case Settings::ControllerType::LeftJoycon:
+ return QStringLiteral("image: url(:/controller/single_joycon_left_vertical%0)");
+ case Settings::ControllerType::RightJoycon:
+ return QStringLiteral("image: url(:/controller/single_joycon_right_vertical%0)");
+ case Settings::ControllerType::Handheld:
+ return QStringLiteral("image: url(:/controller/handheld%0)");
+ default:
+ return QString{};
+ }
+ }();
+
+ const QString theme = [this] {
+ if (QIcon::themeName().contains(QStringLiteral("dark"))) {
+ return QStringLiteral("_dark");
+ } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
+ return QStringLiteral("_midnight");
+ } else {
+ return QString{};
+ }
+ }();
+
+ ui->controllerFrame->setStyleSheet(stylesheet.arg(theme));
+}
+
+void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
+ auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
+ if (debug) {
+ layout = Settings::ControllerType::ProController;
+ }
+
+ // 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*, 9> layout_show = {
+ ui->buttonShoulderButtonsSLSR,
+ ui->horizontalSpacerShoulderButtonsWidget,
+ ui->horizontalSpacerShoulderButtonsWidget2,
+ ui->buttonShoulderButtonsLeft,
+ ui->buttonMiscButtonsMinusScreenshot,
+ ui->bottomLeft,
+ ui->buttonShoulderButtonsRight,
+ ui->buttonMiscButtonsPlusHome,
+ ui->bottomRight,
+ };
+
+ for (auto* widget : layout_show) {
+ widget->show();
+ }
+
+ std::vector<QWidget*> layout_hidden;
+ switch (layout) {
+ case Settings::ControllerType::ProController:
+ case Settings::ControllerType::DualJoyconDetached:
+ case Settings::ControllerType::Handheld:
+ layout_hidden = {
+ ui->buttonShoulderButtonsSLSR,
+ ui->horizontalSpacerShoulderButtonsWidget2,
+ };
+ break;
+ case Settings::ControllerType::LeftJoycon:
+ layout_hidden = {
+ ui->horizontalSpacerShoulderButtonsWidget2,
+ ui->buttonShoulderButtonsRight,
+ ui->buttonMiscButtonsPlusHome,
+ ui->bottomRight,
+ };
+ break;
+ case Settings::ControllerType::RightJoycon:
+ layout_hidden = {
+ ui->horizontalSpacerShoulderButtonsWidget,
+ ui->buttonShoulderButtonsLeft,
+ ui->buttonMiscButtonsMinusScreenshot,
+ ui->bottomLeft,
+ };
+ break;
+ }
+
+ for (auto* widget : layout_hidden) {
+ widget->hide();
+ }
+}
+
+void ConfigureInputPlayer::showEvent(QShowEvent* event) {
+ if (bottom_row == nullptr) {
+ return;
+ }
+ QWidget::showEvent(event);
+ ui->main->addWidget(bottom_row);
+}
+
+void ConfigureInputPlayer::ConnectPlayer(bool connected) {
+ ui->groupConnectedController->setChecked(connected);
+}
diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h
index 95afa5375..a25bc3bd9 100644
--- a/src/yuzu/configuration/configure_input_player.h
+++ b/src/yuzu/configuration/configure_input_player.h
@@ -10,16 +10,25 @@
#include <optional>
#include <string>
-#include <QDialog>
+#include <QWidget>
#include "common/param_package.h"
#include "core/settings.h"
#include "ui_configure_input.h"
+class QCheckBox;
class QKeyEvent;
+class QLabel;
class QPushButton;
+class QSlider;
+class QSpinBox;
class QString;
class QTimer;
+class QWidget;
+
+namespace InputCommon {
+class InputSubsystem;
+}
namespace InputCommon::Polling {
class DevicePoller;
@@ -30,77 +39,116 @@ namespace Ui {
class ConfigureInputPlayer;
}
-class ConfigureInputPlayer : public QDialog {
+class ConfigureInputPlayer : public QWidget {
Q_OBJECT
public:
- explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, bool debug = false);
+ explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, QWidget* bottom_row,
+ InputCommon::InputSubsystem* input_subsystem_,
+ bool debug = false);
~ConfigureInputPlayer() override;
- /// Save all button configurations to settings file
+ /// Save all button configurations to settings file.
void ApplyConfiguration();
+ /// Update the input devices combobox.
+ void UpdateInputDevices();
+
+ /// Restore all buttons to their default values.
+ void RestoreDefaults();
+
+ /// Clear all input configuration.
+ void ClearAll();
+
+ /// Set the connection state checkbox (used to sync state).
+ void ConnectPlayer(bool connected);
+
+signals:
+ /// Emitted when this controller is 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);
+ /// Emitted when the input devices combobox is being refreshed.
+ void RefreshInputDevices();
+
+protected:
+ void showEvent(QShowEvent* event) override;
+
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
- void OnControllerButtonClick(int i);
-
/// Load configuration settings.
void LoadConfiguration();
- /// Restore all buttons to their default values.
- void RestoreDefaults();
- /// Clear all input configuration
- void ClearAll();
-
- /// Update UI to reflect current configuration.
- void UpdateButtonLabels();
/// Called when the button was pressed.
void HandleClick(QPushButton* button,
std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type);
- /// Finish polling and configure input using the input_setter
+ /// Finish polling and configure input using the input_setter.
void SetPollingResult(const Common::ParamPackage& params, bool abort);
+ /// Handle mouse button press events.
+ void mousePressEvent(QMouseEvent* event) override;
+
/// Handle key press events.
void keyPressEvent(QKeyEvent* event) override;
+ /// Update UI to reflect current configuration.
+ void UpdateUI();
+
+ /// Update the controller selection combobox
+ void UpdateControllerCombobox();
+
+ /// Update the current controller icon.
+ void UpdateControllerIcon();
+
+ /// Hides and disables controller settings based on the current controller type.
+ void UpdateControllerAvailableButtons();
+
+ /// Gets the default controller mapping for this device and auto configures the input to match.
+ void UpdateMappingWithDefaults();
+
std::unique_ptr<Ui::ConfigureInputPlayer> ui;
std::size_t player_index;
bool debug;
+ InputCommon::InputSubsystem* input_subsystem;
+
std::unique_ptr<QTimer> timeout_timer;
std::unique_ptr<QTimer> poll_timer;
+ 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;
std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param;
std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param;
- static constexpr int ANALOG_SUB_BUTTONS_NUM = 5;
+ static constexpr int ANALOG_SUB_BUTTONS_NUM = 4;
/// Each button input is represented by a QPushButton.
std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map;
+ /// Extra buttons for the modifiers.
+ Common::ParamPackage lstick_mod;
+ Common::ParamPackage rstick_mod;
- std::vector<QWidget*> debug_hidden;
- std::vector<QWidget*> layout_hidden;
-
- /// A group of five QPushButtons represent one analog input. The buttons each represent up,
- /// down, left, right, and modifier, respectively.
+ /// A group of four QPushButtons represent one analog input. The buttons each represent up,
+ /// down, left, right, respectively.
std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs>
analog_map_buttons;
- /// Analog inputs are also represented each with a single button, used to configure with an
- /// actual analog stick
- std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_stick;
- std::array<QSlider*, Settings::NativeAnalog::NumAnalogs>
- analog_map_deadzone_and_modifier_slider;
- std::array<QLabel*, Settings::NativeAnalog::NumAnalogs>
- analog_map_deadzone_and_modifier_slider_label;
+ std::array<QLabel*, Settings::NativeAnalog::NumAnalogs> analog_map_deadzone_label;
+ std::array<QSlider*, Settings::NativeAnalog::NumAnalogs> analog_map_deadzone_slider;
+ std::array<QGroupBox*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_groupbox;
+ std::array<QLabel*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_label;
+ std::array<QSlider*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_slider;
+ std::array<QGroupBox*, Settings::NativeAnalog::NumAnalogs> analog_map_range_groupbox;
+ std::array<QSpinBox*, Settings::NativeAnalog::NumAnalogs> analog_map_range_spinbox;
static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons;
@@ -108,8 +156,14 @@ private:
/// A flag to indicate if keyboard keys are okay when configuring an input. If this is false,
/// keyboard events are ignored.
- bool want_keyboard_keys = false;
+ bool want_keyboard_mouse = false;
+
+ /// List of physical devices users can map with. If a SDL backed device is selected, then you
+ /// can usue this device to get a default mapping.
+ std::vector<Common::ParamPackage> input_devices;
- std::array<QPushButton*, 4> controller_color_buttons;
- std::array<QColor, 4> controller_colors;
+ /// Bottom row is where console wide settings are held, and its "owned" by the parent
+ /// ConfigureInput widget. On show, add this widget to the main layout. This will change the
+ /// parent of the widget to this widget (but thats fine).
+ QWidget* bottom_row;
};
diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui
index f27a77180..9bc681894 100644
--- a/src/yuzu/configuration/configure_input_player.ui
+++ b/src/yuzu/configuration/configure_input_player.ui
@@ -1,1243 +1,2974 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureInputPlayer</class>
- <widget class="QDialog" name="ConfigureInputPlayer">
+ <widget class="QWidget" name="ConfigureInputPlayer">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>408</width>
- <height>731</height>
+ <width>780</width>
+ <height>487</height>
</rect>
</property>
<property name="windowTitle">
<string>Configure Input</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_5">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <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>
- <layout class="QGridLayout" name="buttons">
- <item row="1" column="1">
- <widget class="QGroupBox" name="RStick">
- <property name="title">
- <string>Right Stick</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
- </property>
- <property name="flat">
- <bool>false</bool>
+ <layout class="QVBoxLayout" name="main">
+ <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>
+ <layout class="QHBoxLayout" name="top" stretch="0,1,2">
+ <property name="spacing">
+ <number>3</number>
</property>
- <property name="checkable">
- <bool>false</bool>
+ <property name="topMargin">
+ <number>0</number>
</property>
- <layout class="QGridLayout" name="gridLayout_5">
- <item row="1" column="1">
- <layout class="QVBoxLayout" name="buttonRStickDownVerticalLayout">
+ <item>
+ <widget class="QGroupBox" name="groupConnectedController">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="title">
+ <string>Connect Controller</string>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="leftMargin">
+ <number>5</number>
+ </property>
+ <property name="topMargin">
+ <number>5</number>
+ </property>
+ <property name="rightMargin">
+ <number>5</number>
+ </property>
+ <property name="bottomMargin">
+ <number>5</number>
+ </property>
<item>
- <layout class="QHBoxLayout" name="buttonRStickDownHorizontalLayout">
+ <widget class="QComboBox" name="comboControllerType">
<item>
- <widget class="QLabel" name="labelRStickDown">
- <property name="text">
- <string>Down:</string>
- </property>
- </widget>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
</item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonRStickDown">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="0" column="1">
- <layout class="QVBoxLayout" name="buttonRStickRightVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonRStickRightHorizontalLayout">
<item>
- <widget class="QLabel" name="labelRStickRight">
- <property name="text">
- <string>Right:</string>
- </property>
- </widget>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
</item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonRStickRight">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="3" column="0" colspan="2">
- <widget class="QPushButton" name="buttonRStickAnalog">
- <property name="text">
- <string>Set Analog Stick</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <layout class="QVBoxLayout" name="buttonRStickLeftVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonRStickLeftHorizontalLayout">
<item>
- <widget class="QLabel" name="labelRStickLeft">
- <property name="text">
- <string>Left:</string>
- </property>
- </widget>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
</item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonRStickLeft">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="0">
- <layout class="QVBoxLayout" name="buttonRStickUpVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonRStickUpHorizontalLayout">
<item>
- <widget class="QLabel" name="labelRStickUp">
- <property name="text">
- <string>Up:</string>
- </property>
- </widget>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
</item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonRStickUp">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="2" column="0">
- <layout class="QVBoxLayout" name="buttonRStickPressedVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonRStickPressedHorizontalLayout">
<item>
- <widget class="QLabel" name="labelRStickPressed">
- <property name="text">
- <string>Pressed:</string>
- </property>
- </widget>
+ <property name="text">
+ <string>Handheld</string>
+ </property>
</item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonRStick">
- <property name="text">
- <string/>
- </property>
</widget>
</item>
</layout>
- </item>
- <item row="2" column="1">
- <layout class="QVBoxLayout" name="buttonRStickModVerticalLayout">
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="devicesGroup">
+ <property name="title">
+ <string>Input Device</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>5</number>
+ </property>
+ <property name="topMargin">
+ <number>5</number>
+ </property>
+ <property name="rightMargin">
+ <number>5</number>
+ </property>
+ <property name="bottomMargin">
+ <number>5</number>
+ </property>
<item>
- <layout class="QHBoxLayout" name="buttonRStickModHorizontalLayout">
+ <widget class="QComboBox" name="comboDevices">
<item>
- <widget class="QLabel" name="labelRStickMod">
- <property name="text">
- <string>Modifier:</string>
- </property>
- </widget>
+ <property name="text">
+ <string>Any</string>
+ </property>
</item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonRStickMod">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="4" column="0" colspan="2">
- <layout class="QVBoxLayout" name="sliderRStickDeadzoneAndModifierVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="sliderRStickDeadzoneAndModifierHorizontalLayout">
<item>
- <widget class="QLabel" name="labelRStickDeadzoneAndModifier">
- <property name="text">
- <string>Deadzone: 0</string>
- </property>
- <property name="alignment">
- <enum>Qt::AlignHCenter</enum>
- </property>
- </widget>
+ <property name="text">
+ <string>Keyboard/Mouse</string>
+ </property>
</item>
- </layout>
+ </widget>
</item>
<item>
- <widget class="QSlider" name="sliderRStickDeadzoneAndModifier">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <widget class="QPushButton" name="buttonRefreshDevices">
+ <property name="minimumSize">
+ <size>
+ <width>24</width>
+ <height>22</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>24</width>
+ <height>22</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
</property>
</widget>
</item>
</layout>
- </item>
- <item row="5" column="0">
- <spacer name="RStick_verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="profilesGroup">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Profile</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_4" stretch="2,0,0,0">
+ <property name="spacing">
+ <number>3</number>
</property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
+ <property name="leftMargin">
+ <number>5</number>
+ </property>
+ <property name="topMargin">
+ <number>5</number>
+ </property>
+ <property name="rightMargin">
+ <number>5</number>
+ </property>
+ <property name="bottomMargin">
+ <number>5</number>
</property>
- </spacer>
- </item>
- </layout>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QGroupBox" name="Dpad">
- <property name="title">
- <string>Directional Pad</string>
- </property>
- <property name="flat">
- <bool>false</bool>
- </property>
- <property name="checkable">
- <bool>false</bool>
- </property>
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="1" column="0">
- <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout">
<item>
- <layout class="QHBoxLayout" name="buttonDpadUpHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelDpadUp">
- <property name="text">
- <string>Up:</string>
- </property>
- </widget>
- </item>
- </layout>
+ <widget class="QComboBox" name="comboProfiles"/>
</item>
<item>
- <widget class="QPushButton" name="buttonDpadUp">
- <property name="text">
- <string/>
+ <widget class="QPushButton" name="buttonProfilesSave">
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="1">
- <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonDpadDownHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelDpadDown">
- <property name="text">
- <string>Down:</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonDpadDown">
<property name="text">
- <string/>
+ <string>Save</string>
</property>
</widget>
</item>
- </layout>
- </item>
- <item row="0" column="0">
- <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonDpadLeftHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelDpadLeft">
- <property name="minimumSize">
- <size>
- <width>80</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>Left:</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
<item>
- <widget class="QPushButton" name="buttonDpadLeft">
+ <widget class="QPushButton" name="buttonProfilesNew">
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
<property name="text">
- <string/>
+ <string>New</string>
</property>
</widget>
</item>
- </layout>
- </item>
- <item row="0" column="1">
- <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonDpadRightHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelDpadRight">
- <property name="minimumSize">
- <size>
- <width>80</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>Right:</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
<item>
- <widget class="QPushButton" name="buttonDpadRight">
+ <widget class="QPushButton" name="buttonProfilesDelete">
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
<property name="text">
- <string/>
+ <string>Delete</string>
</property>
</widget>
</item>
</layout>
- </item>
- </layout>
- </widget>
+ </widget>
+ </item>
+ </layout>
</item>
- <item row="0" column="0">
- <widget class="QGroupBox" name="faceButtons">
- <property name="title">
- <string>Face Buttons</string>
- </property>
- <property name="flat">
- <bool>false</bool>
- </property>
- <property name="checkable">
- <bool>false</bool>
+ <item>
+ <widget class="QFrame" name="bottom">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="0" column="0">
- <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonFaceButtonsAHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelA">
- <property name="minimumSize">
- <size>
- <width>80</width>
- <height>0</height>
- </size>
+ <layout class="QHBoxLayout" name="_2">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetMinimumSize</enum>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="bottomLeft" native="true">
+ <layout class="QVBoxLayout" name="bottomLeftLayout" stretch="0,0,0,0">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="LStick">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Left Stick</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="spacing">
+ <number>0</number>
</property>
- <property name="text">
- <string>A:</string>
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonA">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="0" column="1">
- <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonFaceButtonsBHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelB">
- <property name="minimumSize">
- <size>
- <width>80</width>
- <height>0</height>
- </size>
+ <property name="leftMargin">
+ <number>3</number>
</property>
- <property name="text">
- <string>B:</string>
+ <property name="topMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonB">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="0">
- <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonFaceButtonsXHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelX">
- <property name="text">
- <string>X:</string>
+ <property name="rightMargin">
+ <number>3</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonX">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="1">
- <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonFaceButtonsYHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelY">
- <property name="text">
- <string>Y:</string>
+ <property name="bottomMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonY">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item row="5" column="0" colspan="2">
- <widget class="QGroupBox" name="controller_color">
- <property name="title">
- <string>Controller Color</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_10" columnstretch="0,0,0,0,0,0,0">
- <item row="0" column="0">
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="0" column="1">
- <widget class="QLabel" name="left_body_label">
- <property name="text">
- <string>Left Body</string>
- </property>
- </widget>
- </item>
- <item row="0" column="6">
- <spacer name="horizontalSpacer_3">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="1" column="1">
- <widget class="QLabel" name="left_buttons_label">
- <property name="minimumSize">
- <size>
- <width>90</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>Left Buttons</string>
- </property>
- </widget>
- </item>
- <item row="1" column="5">
- <widget class="QPushButton" name="right_buttons_button">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>32</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>40</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="4">
- <widget class="QLabel" name="right_body_label">
- <property name="text">
- <string>Right Body</string>
- </property>
- </widget>
- </item>
- <item row="1" column="4">
- <widget class="QLabel" name="right_buttons_label">
- <property name="minimumSize">
- <size>
- <width>90</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>Right Buttons</string>
- </property>
- </widget>
- </item>
- <item row="1" column="2">
- <widget class="QPushButton" name="left_buttons_button">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>32</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>40</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QPushButton" name="left_body_button">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>32</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>40</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="5">
- <widget class="QPushButton" name="right_body_button">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>32</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>40</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="3">
- <spacer name="horizontalSpacer_4">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Fixed</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QGroupBox" name="LStick">
- <property name="title">
- <string>Left Stick</string>
- </property>
- <property name="flat">
- <bool>false</bool>
- </property>
- <property name="checkable">
- <bool>false</bool>
- </property>
- <layout class="QGridLayout" name="gridLayout_4">
- <item row="1" column="1">
- <layout class="QVBoxLayout" name="buttonLStickUpVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonLStickUpHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelLStickUp">
- <property name="text">
- <string>Up:</string>
+ <item>
+ <widget class="QWidget" name="buttonLStickUpWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_20">
+ <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="horizontalSpacerLStickUpLeft">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonLStickUpGroup">
+ <property name="title">
+ <string>Up</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="buttonLStickUpVerticalLayout">
+ <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="buttonLStickUp">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Up</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacerLStickUpRight">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="buttonLStickLeftRightHorizontaLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonLStickLeftGroup">
+ <property name="title">
+ <string>Left</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonLStickLeftVerticalLayout">
+ <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="buttonLStickLeft">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Left</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonLStickRightGroup">
+ <property name="title">
+ <string>Right</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonLStickRightVerticalLayout">
+ <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="buttonLStickRight">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Right</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QWidget" name="buttonLStickDownWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_22">
+ <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="horizontalSpacerLStickDownLeft">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonLStickDownGroup">
+ <property name="title">
+ <string>Down</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonLStickDownVerticalLayout">
+ <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="buttonLStickDown">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Down</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacerLStickDownRight">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="buttonLStickPressedModifierHorizontalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonLStickPressedGroup">
+ <property name="title">
+ <string>Pressed</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonLStickPressedVerticalLayout" stretch="0">
+ <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="buttonLStick">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Pressed</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonLStickModGroup">
+ <property name="title">
+ <string>Modifier</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonLStickModVerticalLayout">
+ <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="buttonLStickMod">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Modifier</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="buttonLStickRangeGroup">
+ <property name="title">
+ <string>Range</string>
+ </property>
+ <layout class="QHBoxLayout" name="buttonLStickRangeGroupHorizontalLayout">
+ <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="QSpinBox" name="spinboxLStickRange">
+ <property name="minimumSize">
+ <size>
+ <width>55</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>50</number>
+ </property>
+ <property name="maximum">
+ <number>150</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="sliderLStickDeadzoneModifierRangeVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="sliderLStickDeadzoneHorizontalLayout">
+ <item>
+ <widget class="QLabel" name="labelLStickDeadzone">
+ <property name="text">
+ <string>Deadzone: 0%</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignHCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QSlider" name="sliderLStickDeadzone">
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="sliderLStickModifierRangeHorizontalLayout">
+ <item>
+ <widget class="QLabel" name="labelLStickModifierRange">
+ <property name="text">
+ <string>Modifier Range: 0%</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignHCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QSlider" name="sliderLStickModifierRange">
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacerBottomLeft">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="Dpad">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>D-Pad</string>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ <property name="checkable">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="spacing">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonLStickUp">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="0" column="2">
- <layout class="QVBoxLayout" name="buttonLStickRightVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonLStickRightHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelLStickRight">
- <property name="text">
- <string>Right:</string>
+ <property name="leftMargin">
+ <number>3</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonLStickRight">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="4" column="1" colspan="2">
- <widget class="QPushButton" name="buttonLStickAnalog">
- <property name="text">
- <string>Set Analog Stick</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <layout class="QVBoxLayout" name="buttonLStickLeftVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonLStickLeftHorizontalLayout_2">
- <item>
- <widget class="QLabel" name="labelLStickLeft">
- <property name="text">
- <string>Left:</string>
+ <property name="topMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonLStickLeft">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="2">
- <layout class="QVBoxLayout" name="buttonLStickDownVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonLStickDownHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelLStickDown">
- <property name="text">
- <string>Down:</string>
+ <property name="rightMargin">
+ <number>3</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonLStickDown">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="3" column="2">
- <layout class="QVBoxLayout" name="buttonLStickModVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonLStickModHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelLStickMod">
- <property name="text">
- <string>Modifier:</string>
+ <property name="bottomMargin">
+ <number>3</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonLStickMod">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
+ <item>
+ <widget class="QWidget" name="buttonDpadUpWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_23">
+ <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="horizontalSpacerDpadUpLeft">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonDpadUpGroup">
+ <property name="title">
+ <string>Up</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout">
+ <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="buttonDpadUp">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Up</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacerDpadUpRight">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="buttonDpadLeftRightHorizontalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonDpadLeftGroup">
+ <property name="title">
+ <string>Left</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout">
+ <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="buttonDpadLeft">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Left</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonDpadRightGroup">
+ <property name="title">
+ <string>Right</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout">
+ <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="buttonDpadRight">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Right</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QWidget" name="buttonDpadDownWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_24">
+ <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="horizontalSpacerDpadDownLeft">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonDpadDownGroup">
+ <property name="title">
+ <string>Down</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout">
+ <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="buttonDpadDown">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Down</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacerDpadDownRight">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacerBottomLeft_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
</item>
- <item row="3" column="1">
- <layout class="QVBoxLayout" name="buttonLStickPressedVerticalLayout" stretch="0,0">
- <item>
- <layout class="QHBoxLayout" name="buttonLStickPressedHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelLStickPressed">
- <property name="text">
- <string>Pressed:</string>
+ <item>
+ <widget class="QWidget" name="bottomMiddle" native="true">
+ <layout class="QVBoxLayout" stretch="0,0,0">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="shoulderButtons">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="buttonShoulderButtonsLeft" native="true">
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsLeftVerticalLayout">
+ <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>
+ <widget class="QGroupBox" name="buttonShoulderButtonsButtonLGroup">
+ <property name="title">
+ <string>L</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsLVerticalLayout">
+ <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="buttonL">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>L</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="buttonShoulderButtonsButtonZLGroup">
+ <property name="title">
+ <string>ZL</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsZLVerticalLayout">
+ <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="buttonZL">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>ZL</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="horizontalSpacerShoulderButtonsWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalSpacerShoulderButtonsWidgetLayout">
+ <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="horizontalSpacerShoulderButtons1">
+ <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="buttonMiscButtonsMinusScreenshot" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsMinusScreenshotVerticalLayout">
+ <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="buttonMiscButtonsMinusGroup">
+ <property name="title">
+ <string>Minus</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonMiscMinusVerticalLayout">
+ <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="buttonMinus">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Minus</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonMiscButtonsScreenshotGroup">
+ <property name="title">
+ <string>Capture</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonMiscScrCapVerticalLayout">
+ <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="buttonScreenshot">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Capture</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="buttonMiscButtonsPlusHome" native="true">
+ <layout class="QVBoxLayout" name="buttonMiscButtonsPlusHomeVerticalLayout">
+ <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="buttonMiscButtonsPlusGroup">
+ <property name="title">
+ <string>Plus</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout">
+ <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="buttonPlus">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Plus</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonMiscButtonsHomeGroup">
+ <property name="title">
+ <string>Home</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonMiscHomeVerticalLayout">
+ <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="buttonHome">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Home</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="horizontalSpacerShoulderButtonsWidget3" native="true">
+ <layout class="QHBoxLayout" name="horizontalSpacerShoulderButtonsWidget3Layout">
+ <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="horizontalSpacerShoulderButtons2">
+ <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="buttonShoulderButtonsRight" native="true">
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsRightVerticalLayout">
+ <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>
+ <widget class="QGroupBox" name="buttonShoulderButtonsRGroup">
+ <property name="title">
+ <string>R</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsRVerticalLayout">
+ <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="buttonR">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>R</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="buttonShoulderButtonsZRGroup">
+ <property name="title">
+ <string>ZR</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsZRVerticalLayout">
+ <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="buttonZR">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>ZR</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="horizontalSpacerShoulderButtonsWidget2" native="true">
+ <layout class="QHBoxLayout" name="horizontalSpacerShoulderButtonsWidget2Layout">
+ <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="horizontalSpacerShoulderButtons3">
+ <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="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>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</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>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>SR</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QFrame" name="controllerFrame">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">image: url(:/controller/pro);</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <property name="leftMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonLStick">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="5" column="1" colspan="2">
- <layout class="QVBoxLayout" name="sliderLStickDeadzoneAndModifierVerticalLayout">
- <property name="sizeConstraint">
- <enum>QLayout::SetDefaultConstraint</enum>
- </property>
- <item>
- <layout class="QHBoxLayout" name="sliderLStickDeadzoneAndModifierHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelLStickDeadzoneAndModifier">
- <property name="text">
- <string>Deadzone: 0</string>
+ <property name="topMargin">
+ <number>0</number>
</property>
- <property name="alignment">
- <enum>Qt::AlignHCenter</enum>
+ <property name="rightMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QSlider" name="sliderLStickDeadzoneAndModifier">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="6" column="1">
- <spacer name="LStick_verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QGroupBox" name="shoulderButtons">
- <property name="title">
- <string>Shoulder Buttons</string>
- </property>
- <property name="flat">
- <bool>false</bool>
- </property>
- <property name="checkable">
- <bool>false</bool>
- </property>
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="0" column="0">
- <layout class="QVBoxLayout" name="buttonShoulderButtonsLVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonShoulderButtonsLHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelL">
- <property name="text">
- <string>L:</string>
+ <property name="bottomMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonL">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="miscButtons">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item>
+ <spacer name="horizontalSpacerMiscButtons1">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <spacer name="horizontalSpacerMiscButtons4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
</item>
- <item row="0" column="1">
- <layout class="QVBoxLayout" name="buttonShoulderButtonsRVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonShoulderButtonsRHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelR">
- <property name="text">
- <string>R:</string>
+ <item>
+ <widget class="QWidget" name="bottomRight" native="true">
+ <layout class="QVBoxLayout" name="bottomRightLayout">
+ <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>
+ <widget class="QGroupBox" name="faceButtons">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Face Buttons</string>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ <property name="checkable">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonR">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="0">
- <layout class="QVBoxLayout" name="buttonShoulderButtonsZLVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonShoulderButtonsZLHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelZL">
- <property name="text">
- <string>ZL:</string>
+ <property name="leftMargin">
+ <number>3</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonZL">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="1">
- <layout class="QVBoxLayout" name="buttonShoulderButtonsZRVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonShoulderButtonsZRHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelZR">
- <property name="text">
- <string>ZR:</string>
+ <property name="topMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonZR">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="0" column="2">
- <layout class="QVBoxLayout" name="buttonShoulderButtonsSLVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonShoulderButtonsSLHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelSL">
- <property name="text">
- <string>SL:</string>
+ <property name="rightMargin">
+ <number>3</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonSL">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="2">
- <layout class="QVBoxLayout" name="buttonShoulderButtonsSRVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonShoulderButtonsSRHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelSR">
- <property name="text">
- <string>SR:</string>
+ <property name="bottomMargin">
+ <number>3</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonSR">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QGroupBox" name="misc">
- <property name="title">
- <string>Misc.</string>
- </property>
- <property name="flat">
- <bool>false</bool>
- </property>
- <property name="checkable">
- <bool>false</bool>
- </property>
- <layout class="QGridLayout" name="gridLayout_6">
- <item row="1" column="0">
- <layout class="QVBoxLayout" name="buttonMiscMinusVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonMiscMinusHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelMinus">
- <property name="text">
- <string>Minus:</string>
+ <item>
+ <widget class="QWidget" name="buttonFaceButtonsBWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <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="horizontalSpacerBLeft">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonFaceButtonsXGroup">
+ <property name="title">
+ <string>X</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout">
+ <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="buttonX">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>X</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacerBRight">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="buttonFaceButtonsYAHorizontalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonFaceButtonsYGroup">
+ <property name="title">
+ <string>Y</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout">
+ <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="buttonY">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Y</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonFaceButtonsAGroup">
+ <property name="title">
+ <string>A</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout">
+ <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="buttonA">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>A</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QWidget" name="buttonFaceButtonsXWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_10">
+ <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="horizontalSpacerXLeft">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonFaceButtonsBWidget_2">
+ <property name="title">
+ <string>B</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout">
+ <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="buttonB">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>B</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacerXRight">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacerBottomRight">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="RStick">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Right Stick</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ <property name="checkable">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="spacing">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonMinus">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="3" column="1">
- <spacer name="verticalSpacer_2">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="0" column="0">
- <layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonMiscPlusHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelPlus">
- <property name="text">
- <string>Plus:</string>
+ <property name="leftMargin">
+ <number>3</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonPlus">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="0" column="1">
- <layout class="QVBoxLayout" name="buttonMiscHomeVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonMiscHomeHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelHome">
- <property name="text">
- <string>Home:</string>
+ <property name="topMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonHome">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="1">
- <layout class="QVBoxLayout" name="buttonMiscScrCapVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="buttonMiscScrCapHorizontalLayout">
- <item>
- <widget class="QLabel" name="labelScreenshot">
- <property name="text">
- <string>Screen Capture:</string>
+ <property name="rightMargin">
+ <number>3</number>
</property>
- <property name="wordWrap">
- <bool>false</bool>
+ <property name="bottomMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="buttonScreenshot">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="maximumSize">
- <size>
- <width>80</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
+ <item>
+ <widget class="QWidget" name="buttonRStickUpWidget" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_9">
+ <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="horizontalSpacerRStickUpLeft">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonRStickUpGroup">
+ <property name="title">
+ <string>Up</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonRStickUpVerticalLayout">
+ <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="buttonRStickUp">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Up</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacerRStickUpRight">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="buttonRStickLeftRightHorizontalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonRStickLeftGroup">
+ <property name="title">
+ <string>Left</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonRStickLeftVerticalLayout">
+ <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="buttonRStickLeft">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Left</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonRStickRightGroup">
+ <property name="title">
+ <string>Right</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonRStickRightVerticalLayout">
+ <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="buttonRStickRight">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Right</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QWidget" name="buttonRStickDownWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_11">
+ <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="horizontalSpacerRStickDownLeft">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="buttonRStickDownGroup">
+ <property name="title">
+ <string>Down</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonRStickDownVerticalLayout">
+ <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="buttonRStickDown">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Down</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacerRStickDownRight">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="buttonRStickPressedModifierHorizontalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupRStickPressed">
+ <property name="title">
+ <string>Pressed</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonRStickPressedVerticalLayout">
+ <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="buttonRStick">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Pressed</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonRStickModGroup">
+ <property name="title">
+ <string>Modifier</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonRStickModVerticalLayout">
+ <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="buttonRStickMod">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 55px;</string>
+ </property>
+ <property name="text">
+ <string>Modifier</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="buttonRStickRangeGroup">
+ <property name="title">
+ <string>Range</string>
+ </property>
+ <layout class="QHBoxLayout" name="buttonRStickRangeGroupHorizontalLayout">
+ <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="QSpinBox" name="spinboxRStickRange">
+ <property name="minimumSize">
+ <size>
+ <width>55</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>55</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>50</number>
+ </property>
+ <property name="maximum">
+ <number>150</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="sliderRStickDeadzoneModifierRangeVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="sliderRStickDeadzoneHorizontalLayout">
+ <item>
+ <widget class="QLabel" name="labelRStickDeadzone">
+ <property name="text">
+ <string>Deadzone: 0%</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignHCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QSlider" name="sliderRStickDeadzone">
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="sliderRStickModifierRangeHorizontalLayout">
+ <item>
+ <widget class="QLabel" name="labelRStickModifierRange">
+ <property name="text">
+ <string>Modifier Range: 0%</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignHCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QSlider" name="sliderRStickModifierRange">
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacerBottomRight_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout"/>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <item>
- <widget class="QPushButton" name="buttonClearAll">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="sizeIncrement">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
- </property>
- <property name="baseSize">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
- </property>
- <property name="layoutDirection">
- <enum>Qt::LeftToRight</enum>
- </property>
- <property name="text">
- <string>Clear All</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="buttonRestoreDefaults">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="sizeIncrement">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
- </property>
- <property name="baseSize">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
- </property>
- <property name="layoutDirection">
- <enum>Qt::LeftToRight</enum>
- </property>
- <property name="text">
- <string>Restore Defaults</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
- </property>
- </widget>
- </item>
- </layout>
- </item>
</layout>
</widget>
- <resources/>
- <connections>
- <connection>
- <sender>buttonBox</sender>
- <signal>accepted()</signal>
- <receiver>ConfigureInputPlayer</receiver>
- <slot>accept()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>371</x>
- <y>730</y>
- </hint>
- <hint type="destinationlabel">
- <x>229</x>
- <y>375</y>
- </hint>
- </hints>
- </connection>
- <connection>
- <sender>buttonBox</sender>
- <signal>rejected()</signal>
- <receiver>ConfigureInputPlayer</receiver>
- <slot>reject()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>371</x>
- <y>730</y>
- </hint>
- <hint type="destinationlabel">
- <x>229</x>
- <y>375</y>
- </hint>
- </hints>
- </connection>
- </connections>
+ <resources>
+ <include location="../../../dist/icons/controller/controller.qrc"/>
+ </resources>
+ <connections/>
</ui>
diff --git a/src/yuzu/configuration/configure_input_simple.cpp b/src/yuzu/configuration/configure_input_simple.cpp
deleted file mode 100644
index 0e0e8f113..000000000
--- a/src/yuzu/configuration/configure_input_simple.cpp
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <array>
-#include <tuple>
-
-#include "ui_configure_input_simple.h"
-#include "yuzu/configuration/configure_input.h"
-#include "yuzu/configuration/configure_input_player.h"
-#include "yuzu/configuration/configure_input_simple.h"
-#include "yuzu/uisettings.h"
-
-namespace {
-
-template <typename Dialog, typename... Args>
-void CallConfigureDialog(ConfigureInputSimple* caller, Args&&... args) {
- caller->ApplyConfiguration();
- Dialog dialog(caller, std::forward<Args>(args)...);
-
- const auto res = dialog.exec();
- if (res == QDialog::Accepted) {
- dialog.ApplyConfiguration();
- }
-}
-
-// OnProfileSelect functions should (when applicable):
-// - Set controller types
-// - Set controller enabled
-// - Set docked mode
-// - Set advanced controller config/enabled (i.e. debug, kbd, mouse, touch)
-//
-// OnProfileSelect function should NOT however:
-// - Reset any button mappings
-// - Open any dialogs
-// - Block in any way
-
-constexpr std::size_t PLAYER_0_INDEX = 0;
-constexpr std::size_t HANDHELD_INDEX = 8;
-
-void HandheldOnProfileSelect() {
- Settings::values.players[HANDHELD_INDEX].connected = true;
- Settings::values.players[HANDHELD_INDEX].type = Settings::ControllerType::DualJoycon;
-
- for (std::size_t player = 0; player < HANDHELD_INDEX; ++player) {
- Settings::values.players[player].connected = false;
- }
-
- Settings::values.use_docked_mode = false;
- Settings::values.keyboard_enabled = false;
- Settings::values.mouse_enabled = false;
- Settings::values.debug_pad_enabled = false;
- Settings::values.touchscreen.enabled = true;
-}
-
-void DualJoyconsDockedOnProfileSelect() {
- Settings::values.players[PLAYER_0_INDEX].connected = true;
- Settings::values.players[PLAYER_0_INDEX].type = Settings::ControllerType::DualJoycon;
-
- for (std::size_t player = 1; player <= HANDHELD_INDEX; ++player) {
- Settings::values.players[player].connected = false;
- }
-
- Settings::values.use_docked_mode = true;
- Settings::values.keyboard_enabled = false;
- Settings::values.mouse_enabled = false;
- Settings::values.debug_pad_enabled = false;
- Settings::values.touchscreen.enabled = true;
-}
-
-// Name, OnProfileSelect (called when selected in drop down), OnConfigure (called when configure
-// is clicked)
-using InputProfile = std::tuple<const char*, void (*)(), void (*)(ConfigureInputSimple*)>;
-
-constexpr std::array<InputProfile, 3> INPUT_PROFILES{{
- {QT_TR_NOOP("Single Player - Handheld - Undocked"), HandheldOnProfileSelect,
- [](ConfigureInputSimple* caller) {
- CallConfigureDialog<ConfigureInputPlayer>(caller, HANDHELD_INDEX, false);
- }},
- {QT_TR_NOOP("Single Player - Dual Joycons - Docked"), DualJoyconsDockedOnProfileSelect,
- [](ConfigureInputSimple* caller) {
- CallConfigureDialog<ConfigureInputPlayer>(caller, PLAYER_0_INDEX, false);
- }},
- {QT_TR_NOOP("Custom"), [] {}, CallConfigureDialog<ConfigureInput>},
-}};
-
-} // namespace
-
-void ApplyInputProfileConfiguration(int profile_index) {
- std::get<1>(
- INPUT_PROFILES.at(std::min(profile_index, static_cast<int>(INPUT_PROFILES.size() - 1))))();
-}
-
-ConfigureInputSimple::ConfigureInputSimple(QWidget* parent)
- : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputSimple>()) {
- ui->setupUi(this);
-
- for (const auto& profile : INPUT_PROFILES) {
- const QString label = tr(std::get<0>(profile));
- ui->profile_combobox->addItem(label, label);
- }
-
- connect(ui->profile_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
- &ConfigureInputSimple::OnSelectProfile);
- connect(ui->profile_configure, &QPushButton::clicked, this, &ConfigureInputSimple::OnConfigure);
-
- LoadConfiguration();
-}
-
-ConfigureInputSimple::~ConfigureInputSimple() = default;
-
-void ConfigureInputSimple::ApplyConfiguration() {
- auto index = ui->profile_combobox->currentIndex();
- // Make the stored index for "Custom" very large so that if new profiles are added it
- // doesn't change.
- if (index >= static_cast<int>(INPUT_PROFILES.size() - 1)) {
- index = std::numeric_limits<int>::max();
- }
-
- UISettings::values.profile_index = index;
-}
-
-void ConfigureInputSimple::changeEvent(QEvent* event) {
- if (event->type() == QEvent::LanguageChange) {
- RetranslateUI();
- }
-
- QWidget::changeEvent(event);
-}
-
-void ConfigureInputSimple::RetranslateUI() {
- ui->retranslateUi(this);
-}
-
-void ConfigureInputSimple::LoadConfiguration() {
- const auto index = UISettings::values.profile_index;
- if (index >= static_cast<int>(INPUT_PROFILES.size()) || index < 0) {
- ui->profile_combobox->setCurrentIndex(static_cast<int>(INPUT_PROFILES.size() - 1));
- } else {
- ui->profile_combobox->setCurrentIndex(index);
- }
-}
-
-void ConfigureInputSimple::OnSelectProfile(int index) {
- const auto old_docked = Settings::values.use_docked_mode;
- ApplyInputProfileConfiguration(index);
- OnDockedModeChanged(old_docked, Settings::values.use_docked_mode);
-}
-
-void ConfigureInputSimple::OnConfigure() {
- std::get<2>(INPUT_PROFILES.at(ui->profile_combobox->currentIndex()))(this);
-}
diff --git a/src/yuzu/configuration/configure_input_simple.h b/src/yuzu/configuration/configure_input_simple.h
deleted file mode 100644
index bb5050224..000000000
--- a/src/yuzu/configuration/configure_input_simple.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-
-#include <QWidget>
-
-class QPushButton;
-class QString;
-class QTimer;
-
-namespace Ui {
-class ConfigureInputSimple;
-}
-
-// Used by configuration loader to apply a profile if the input is invalid.
-void ApplyInputProfileConfiguration(int profile_index);
-
-class ConfigureInputSimple : public QWidget {
- Q_OBJECT
-
-public:
- explicit ConfigureInputSimple(QWidget* parent = nullptr);
- ~ConfigureInputSimple() override;
-
- /// Save all button configurations to settings file
- void ApplyConfiguration();
-
-private:
- void changeEvent(QEvent* event) override;
- void RetranslateUI();
-
- /// Load configuration settings.
- void LoadConfiguration();
-
- void OnSelectProfile(int index);
- void OnConfigure();
-
- std::unique_ptr<Ui::ConfigureInputSimple> ui;
-};
diff --git a/src/yuzu/configuration/configure_input_simple.ui b/src/yuzu/configuration/configure_input_simple.ui
deleted file mode 100644
index c4889caa9..000000000
--- a/src/yuzu/configuration/configure_input_simple.ui
+++ /dev/null
@@ -1,97 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>ConfigureInputSimple</class>
- <widget class="QWidget" name="ConfigureInputSimple">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>473</width>
- <height>685</height>
- </rect>
- </property>
- <property name="windowTitle">
- <string>ConfigureInputSimple</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_5">
- <item>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QGroupBox" name="gridGroupBox">
- <property name="title">
- <string>Profile</string>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="1" column="2">
- <widget class="QPushButton" name="profile_configure">
- <property name="text">
- <string>Configure</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="1" column="3">
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="1" column="1">
- <widget class="QComboBox" name="profile_combobox">
- <property name="minimumSize">
- <size>
- <width>250</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="0" column="1" colspan="2">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Choose a controller configuration:</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
- <resources/>
- <connections/>
-</ui>
diff --git a/src/yuzu/configuration/configure_motion_touch.cpp b/src/yuzu/configuration/configure_motion_touch.cpp
new file mode 100644
index 000000000..c7d085151
--- /dev/null
+++ b/src/yuzu/configuration/configure_motion_touch.cpp
@@ -0,0 +1,314 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <QCloseEvent>
+#include <QLabel>
+#include <QMessageBox>
+#include <QPushButton>
+#include <QVBoxLayout>
+#include "common/logging/log.h"
+#include "core/settings.h"
+#include "input_common/main.h"
+#include "input_common/udp/client.h"
+#include "input_common/udp/udp.h"
+#include "ui_configure_motion_touch.h"
+#include "yuzu/configuration/configure_motion_touch.h"
+#include "yuzu/configuration/configure_touch_from_button.h"
+
+CalibrationConfigurationDialog::CalibrationConfigurationDialog(QWidget* parent,
+ const std::string& host, u16 port,
+ u8 pad_index, u16 client_id)
+ : QDialog(parent) {
+ layout = new QVBoxLayout;
+ status_label = new QLabel(tr("Communicating with the server..."));
+ cancel_button = new QPushButton(tr("Cancel"));
+ connect(cancel_button, &QPushButton::clicked, this, [this] {
+ if (!completed) {
+ job->Stop();
+ }
+ accept();
+ });
+ layout->addWidget(status_label);
+ layout->addWidget(cancel_button);
+ setLayout(layout);
+
+ using namespace InputCommon::CemuhookUDP;
+ job = std::make_unique<CalibrationConfigurationJob>(
+ host, port, pad_index, client_id,
+ [this](CalibrationConfigurationJob::Status status) {
+ QString text;
+ switch (status) {
+ case CalibrationConfigurationJob::Status::Ready:
+ text = tr("Touch the top left corner <br>of your touchpad.");
+ break;
+ case CalibrationConfigurationJob::Status::Stage1Completed:
+ text = tr("Now touch the bottom right corner <br>of your touchpad.");
+ break;
+ case CalibrationConfigurationJob::Status::Completed:
+ text = tr("Configuration completed!");
+ break;
+ }
+ QMetaObject::invokeMethod(this, "UpdateLabelText", Q_ARG(QString, text));
+ if (status == CalibrationConfigurationJob::Status::Completed) {
+ QMetaObject::invokeMethod(this, "UpdateButtonText", Q_ARG(QString, tr("OK")));
+ }
+ },
+ [this](u16 min_x_, u16 min_y_, u16 max_x_, u16 max_y_) {
+ completed = true;
+ min_x = min_x_;
+ min_y = min_y_;
+ max_x = max_x_;
+ max_y = max_y_;
+ });
+}
+
+CalibrationConfigurationDialog::~CalibrationConfigurationDialog() = default;
+
+void CalibrationConfigurationDialog::UpdateLabelText(const QString& text) {
+ status_label->setText(text);
+}
+
+void CalibrationConfigurationDialog::UpdateButtonText(const QString& text) {
+ cancel_button->setText(text);
+}
+
+constexpr std::array<std::pair<const char*, const char*>, 2> MotionProviders = {{
+ {"motion_emu", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Mouse (Right Click)")},
+ {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")},
+}};
+
+constexpr std::array<std::pair<const char*, const char*>, 2> TouchProviders = {{
+ {"emu_window", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Emulator Window")},
+ {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")},
+}};
+
+ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent,
+ InputCommon::InputSubsystem* input_subsystem_)
+ : QDialog(parent), input_subsystem{input_subsystem_},
+ ui(std::make_unique<Ui::ConfigureMotionTouch>()) {
+ ui->setupUi(this);
+ for (const auto& [provider, name] : MotionProviders) {
+ ui->motion_provider->addItem(tr(name), QString::fromUtf8(provider));
+ }
+ for (const auto& [provider, name] : TouchProviders) {
+ ui->touch_provider->addItem(tr(name), QString::fromUtf8(provider));
+ }
+
+ ui->udp_learn_more->setOpenExternalLinks(true);
+ ui->udp_learn_more->setText(
+ tr("<a "
+ "href='https://yuzu-emu.org/wiki/"
+ "using-a-controller-or-android-phone-for-motion-or-touch-input'><span "
+ "style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>"));
+
+ SetConfiguration();
+ UpdateUiDisplay();
+ ConnectEvents();
+}
+
+ConfigureMotionTouch::~ConfigureMotionTouch() = default;
+
+void ConfigureMotionTouch::SetConfiguration() {
+ const Common::ParamPackage motion_param(Settings::values.motion_device);
+ const Common::ParamPackage touch_param(Settings::values.touch_device);
+ const std::string motion_engine = motion_param.Get("engine", "motion_emu");
+ const std::string touch_engine = touch_param.Get("engine", "emu_window");
+
+ ui->motion_provider->setCurrentIndex(
+ ui->motion_provider->findData(QString::fromStdString(motion_engine)));
+ ui->touch_provider->setCurrentIndex(
+ ui->touch_provider->findData(QString::fromStdString(touch_engine)));
+ ui->touch_from_button_checkbox->setChecked(Settings::values.use_touch_from_button);
+ touch_from_button_maps = Settings::values.touch_from_button_maps;
+ for (const auto& touch_map : touch_from_button_maps) {
+ ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name));
+ }
+ ui->touch_from_button_map->setCurrentIndex(Settings::values.touch_from_button_map_index);
+ ui->motion_sensitivity->setValue(motion_param.Get("sensitivity", 0.01f));
+
+ min_x = touch_param.Get("min_x", 100);
+ min_y = touch_param.Get("min_y", 50);
+ max_x = touch_param.Get("max_x", 1800);
+ max_y = touch_param.Get("max_y", 850);
+
+ ui->udp_server->setText(QString::fromStdString(Settings::values.udp_input_address));
+ ui->udp_port->setText(QString::number(Settings::values.udp_input_port));
+ ui->udp_pad_index->setCurrentIndex(Settings::values.udp_pad_index);
+}
+
+void ConfigureMotionTouch::UpdateUiDisplay() {
+ const QString motion_engine = ui->motion_provider->currentData().toString();
+ const QString touch_engine = ui->touch_provider->currentData().toString();
+ const QString cemuhook_udp = QStringLiteral("cemuhookudp");
+
+ if (motion_engine == QStringLiteral("motion_emu")) {
+ ui->motion_sensitivity_label->setVisible(true);
+ ui->motion_sensitivity->setVisible(true);
+ } else {
+ ui->motion_sensitivity_label->setVisible(false);
+ ui->motion_sensitivity->setVisible(false);
+ }
+
+ if (touch_engine == cemuhook_udp) {
+ ui->touch_calibration->setVisible(true);
+ ui->touch_calibration_config->setVisible(true);
+ ui->touch_calibration_label->setVisible(true);
+ ui->touch_calibration->setText(
+ QStringLiteral("(%1, %2) - (%3, %4)").arg(min_x).arg(min_y).arg(max_x).arg(max_y));
+ } else {
+ ui->touch_calibration->setVisible(false);
+ ui->touch_calibration_config->setVisible(false);
+ ui->touch_calibration_label->setVisible(false);
+ }
+
+ if (motion_engine == cemuhook_udp || touch_engine == cemuhook_udp) {
+ ui->udp_config_group_box->setVisible(true);
+ } else {
+ ui->udp_config_group_box->setVisible(false);
+ }
+}
+
+void ConfigureMotionTouch::ConnectEvents() {
+ connect(ui->motion_provider, qOverload<int>(&QComboBox::currentIndexChanged), this,
+ [this](int index) { UpdateUiDisplay(); });
+ connect(ui->touch_provider, qOverload<int>(&QComboBox::currentIndexChanged), this,
+ [this](int index) { UpdateUiDisplay(); });
+ connect(ui->udp_test, &QPushButton::clicked, this, &ConfigureMotionTouch::OnCemuhookUDPTest);
+ connect(ui->touch_calibration_config, &QPushButton::clicked, this,
+ &ConfigureMotionTouch::OnConfigureTouchCalibration);
+ connect(ui->touch_from_button_config_btn, &QPushButton::clicked, this,
+ &ConfigureMotionTouch::OnConfigureTouchFromButton);
+ connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] {
+ if (CanCloseDialog()) {
+ reject();
+ }
+ });
+}
+
+void ConfigureMotionTouch::OnCemuhookUDPTest() {
+ ui->udp_test->setEnabled(false);
+ ui->udp_test->setText(tr("Testing"));
+ udp_test_in_progress = true;
+ InputCommon::CemuhookUDP::TestCommunication(
+ ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toInt()),
+ static_cast<u8>(ui->udp_pad_index->currentIndex()), 24872,
+ [this] {
+ LOG_INFO(Frontend, "UDP input test success");
+ QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, true));
+ },
+ [this] {
+ LOG_ERROR(Frontend, "UDP input test failed");
+ QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, false));
+ });
+}
+
+void ConfigureMotionTouch::OnConfigureTouchCalibration() {
+ ui->touch_calibration_config->setEnabled(false);
+ ui->touch_calibration_config->setText(tr("Configuring"));
+ CalibrationConfigurationDialog dialog(
+ this, ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toUInt()),
+ static_cast<u8>(ui->udp_pad_index->currentIndex()), 24872);
+ dialog.exec();
+ if (dialog.completed) {
+ min_x = dialog.min_x;
+ min_y = dialog.min_y;
+ max_x = dialog.max_x;
+ max_y = dialog.max_y;
+ LOG_INFO(Frontend,
+ "UDP touchpad calibration config success: min_x={}, min_y={}, max_x={}, max_y={}",
+ min_x, min_y, max_x, max_y);
+ UpdateUiDisplay();
+ } else {
+ LOG_ERROR(Frontend, "UDP touchpad calibration config failed");
+ }
+ ui->touch_calibration_config->setEnabled(true);
+ ui->touch_calibration_config->setText(tr("Configure"));
+}
+
+void ConfigureMotionTouch::closeEvent(QCloseEvent* event) {
+ if (CanCloseDialog()) {
+ event->accept();
+ } else {
+ event->ignore();
+ }
+}
+
+void ConfigureMotionTouch::ShowUDPTestResult(bool result) {
+ udp_test_in_progress = false;
+ if (result) {
+ QMessageBox::information(this, tr("Test Successful"),
+ tr("Successfully received data from the server."));
+ } else {
+ QMessageBox::warning(this, tr("Test Failed"),
+ tr("Could not receive valid data from the server.<br>Please verify "
+ "that the server is set up correctly and "
+ "the address and port are correct."));
+ }
+ ui->udp_test->setEnabled(true);
+ ui->udp_test->setText(tr("Test"));
+}
+
+void ConfigureMotionTouch::OnConfigureTouchFromButton() {
+ ConfigureTouchFromButton dialog{this, touch_from_button_maps, input_subsystem,
+ ui->touch_from_button_map->currentIndex()};
+ if (dialog.exec() != QDialog::Accepted) {
+ return;
+ }
+ touch_from_button_maps = dialog.GetMaps();
+
+ while (ui->touch_from_button_map->count() > 0) {
+ ui->touch_from_button_map->removeItem(0);
+ }
+ for (const auto& touch_map : touch_from_button_maps) {
+ ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name));
+ }
+ ui->touch_from_button_map->setCurrentIndex(dialog.GetSelectedIndex());
+}
+
+bool ConfigureMotionTouch::CanCloseDialog() {
+ if (udp_test_in_progress) {
+ QMessageBox::warning(this, tr("Citra"),
+ tr("UDP Test or calibration configuration is in progress.<br>Please "
+ "wait for them to finish."));
+ return false;
+ }
+ return true;
+}
+
+void ConfigureMotionTouch::ApplyConfiguration() {
+ if (!CanCloseDialog()) {
+ return;
+ }
+
+ std::string motion_engine = ui->motion_provider->currentData().toString().toStdString();
+ std::string touch_engine = ui->touch_provider->currentData().toString().toStdString();
+
+ Common::ParamPackage motion_param{}, touch_param{};
+ motion_param.Set("engine", std::move(motion_engine));
+ touch_param.Set("engine", std::move(touch_engine));
+
+ if (motion_engine == "motion_emu") {
+ motion_param.Set("sensitivity", static_cast<float>(ui->motion_sensitivity->value()));
+ }
+
+ if (touch_engine == "cemuhookudp") {
+ touch_param.Set("min_x", min_x);
+ touch_param.Set("min_y", min_y);
+ touch_param.Set("max_x", max_x);
+ touch_param.Set("max_y", max_y);
+ }
+
+ Settings::values.motion_device = motion_param.Serialize();
+ Settings::values.touch_device = touch_param.Serialize();
+ Settings::values.use_touch_from_button = ui->touch_from_button_checkbox->isChecked();
+ Settings::values.touch_from_button_map_index = ui->touch_from_button_map->currentIndex();
+ Settings::values.touch_from_button_maps = touch_from_button_maps;
+ Settings::values.udp_input_address = ui->udp_server->text().toStdString();
+ Settings::values.udp_input_port = static_cast<u16>(ui->udp_port->text().toInt());
+ Settings::values.udp_pad_index = static_cast<u8>(ui->udp_pad_index->currentIndex());
+ input_subsystem->ReloadInputDevices();
+
+ accept();
+}
diff --git a/src/yuzu/configuration/configure_motion_touch.h b/src/yuzu/configuration/configure_motion_touch.h
new file mode 100644
index 000000000..3d4b5d659
--- /dev/null
+++ b/src/yuzu/configuration/configure_motion_touch.h
@@ -0,0 +1,90 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QDialog>
+#include "common/param_package.h"
+
+class QLabel;
+class QPushButton;
+class QVBoxLayout;
+
+namespace InputCommon {
+class InputSubsystem;
+}
+
+namespace InputCommon::CemuhookUDP {
+class CalibrationConfigurationJob;
+}
+
+namespace Ui {
+class ConfigureMotionTouch;
+}
+
+/// A dialog for touchpad calibration configuration.
+class CalibrationConfigurationDialog : public QDialog {
+ Q_OBJECT
+public:
+ explicit CalibrationConfigurationDialog(QWidget* parent, const std::string& host, u16 port,
+ u8 pad_index, u16 client_id);
+ ~CalibrationConfigurationDialog() override;
+
+private:
+ Q_INVOKABLE void UpdateLabelText(const QString& text);
+ Q_INVOKABLE void UpdateButtonText(const QString& text);
+
+ QVBoxLayout* layout;
+ QLabel* status_label;
+ QPushButton* cancel_button;
+ std::unique_ptr<InputCommon::CemuhookUDP::CalibrationConfigurationJob> job;
+
+ // Configuration results
+ bool completed{};
+ u16 min_x{};
+ u16 min_y{};
+ u16 max_x{};
+ u16 max_y{};
+
+ friend class ConfigureMotionTouch;
+};
+
+class ConfigureMotionTouch : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit ConfigureMotionTouch(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_);
+ ~ConfigureMotionTouch() override;
+
+public slots:
+ void ApplyConfiguration();
+
+private slots:
+ void OnCemuhookUDPTest();
+ void OnConfigureTouchCalibration();
+ void OnConfigureTouchFromButton();
+
+private:
+ void closeEvent(QCloseEvent* event) override;
+ Q_INVOKABLE void ShowUDPTestResult(bool result);
+ void SetConfiguration();
+ void UpdateUiDisplay();
+ void ConnectEvents();
+ bool CanCloseDialog();
+
+ InputCommon::InputSubsystem* input_subsystem;
+
+ std::unique_ptr<Ui::ConfigureMotionTouch> ui;
+
+ // Coordinate system of the CemuhookUDP touch provider
+ int min_x{};
+ int min_y{};
+ int max_x{};
+ int max_y{};
+
+ bool udp_test_in_progress{};
+
+ std::vector<Settings::TouchFromButtonMap> touch_from_button_maps;
+};
diff --git a/src/yuzu/configuration/configure_motion_touch.ui b/src/yuzu/configuration/configure_motion_touch.ui
new file mode 100644
index 000000000..602cf8cd8
--- /dev/null
+++ b/src/yuzu/configuration/configure_motion_touch.ui
@@ -0,0 +1,327 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureMotionTouch</class>
+ <widget class="QDialog" name="ConfigureMotionTouch">
+ <property name="windowTitle">
+ <string>Configure Motion / Touch</string>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>500</width>
+ <height>450</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QGroupBox" name="motion_group_box">
+ <property name="title">
+ <string>Motion</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel" name="motion_provider_label">
+ <property name="text">
+ <string>Motion Provider:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="motion_provider"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel" name="motion_sensitivity_label">
+ <property name="text">
+ <string>Sensitivity:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDoubleSpinBox" name="motion_sensitivity">
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="decimals">
+ <number>4</number>
+ </property>
+ <property name="minimum">
+ <double>0.010000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>10.000000000000000</double>
+ </property>
+ <property name="singleStep">
+ <double>0.001000000000000</double>
+ </property>
+ <property name="value">
+ <double>0.010000000000000</double>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="touch_group_box">
+ <property name="title">
+ <string>Touch</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel" name="touch_provider_label">
+ <property name="text">
+ <string>Touch Provider:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="touch_provider"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel" name="touch_calibration_label">
+ <property name="text">
+ <string>Calibration:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="touch_calibration">
+ <property name="text">
+ <string>(100, 50) - (1800, 850)</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="touch_calibration_config">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QCheckBox" name="touch_from_button_checkbox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Use button mapping:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="touch_from_button_map"/>
+ </item>
+ <item>
+ <widget class="QPushButton" name="touch_from_button_config_btn">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="udp_config_group_box">
+ <property name="title">
+ <string>CemuhookUDP Config</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QLabel" name="udp_help">
+ <property name="text">
+ <string>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel" name="udp_server_label">
+ <property name="text">
+ <string>Server:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="udp_server">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel" name="udp_port_label">
+ <property name="text">
+ <string>Port:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="udp_port">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel" name="udp_pad_index_label">
+ <property name="text">
+ <string>Pad:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="udp_pad_index">
+ <item>
+ <property name="text">
+ <string>Pad 1</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Pad 2</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Pad 3</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Pad 4</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel" name="udp_learn_more">
+ <property name="text">
+ <string>Learn More</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="udp_test">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Test</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>167</width>
+ <height>55</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ConfigureMotionTouch</receiver>
+ <slot>ApplyConfiguration()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>220</x>
+ <y>380</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>220</x>
+ <y>200</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/configuration/configure_mouse_advanced.cpp b/src/yuzu/configuration/configure_mouse_advanced.cpp
index e0647ea5b..2af3afda8 100644
--- a/src/yuzu/configuration/configure_mouse_advanced.cpp
+++ b/src/yuzu/configuration/configure_mouse_advanced.cpp
@@ -18,6 +18,16 @@
static QString GetKeyName(int key_code) {
switch (key_code) {
+ case Qt::LeftButton:
+ return QObject::tr("Click 0");
+ case Qt::RightButton:
+ return QObject::tr("Click 1");
+ case Qt::MiddleButton:
+ return QObject::tr("Click 2");
+ case Qt::BackButton:
+ return QObject::tr("Click 3");
+ case Qt::ForwardButton:
+ return QObject::tr("Click 4");
case Qt::Key_Shift:
return QObject::tr("Shift");
case Qt::Key_Control:
@@ -66,8 +76,10 @@ static QString ButtonToText(const Common::ParamPackage& param) {
return QObject::tr("[unknown]");
}
-ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent)
- : QDialog(parent), ui(std::make_unique<Ui::ConfigureMouseAdvanced>()),
+ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent,
+ InputCommon::InputSubsystem* input_subsystem_)
+ : QDialog(parent),
+ ui(std::make_unique<Ui::ConfigureMouseAdvanced>()), input_subsystem{input_subsystem_},
timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
@@ -83,25 +95,29 @@ ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent)
}
button->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(button, &QPushButton::clicked, [=] {
+ connect(button, &QPushButton::clicked, [=, this] {
HandleClick(
button_map[button_id],
- [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
+ [=, this](const Common::ParamPackage& params) {
+ buttons_param[button_id] = params;
+ },
InputCommon::Polling::DeviceType::Button);
});
- connect(button, &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) {
- QMenu context_menu;
- context_menu.addAction(tr("Clear"), [&] {
- buttons_param[button_id].Clear();
- button_map[button_id]->setText(tr("[not set]"));
- });
- context_menu.addAction(tr("Restore Default"), [&] {
- buttons_param[button_id] = Common::ParamPackage{
- InputCommon::GenerateKeyboardParam(Config::default_mouse_buttons[button_id])};
- button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
- });
- context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
- });
+ connect(button, &QPushButton::customContextMenuRequested,
+ [=, this](const QPoint& menu_location) {
+ QMenu context_menu;
+ context_menu.addAction(tr("Clear"), [&] {
+ buttons_param[button_id].Clear();
+ button_map[button_id]->setText(tr("[not set]"));
+ });
+ context_menu.addAction(tr("Restore Default"), [&] {
+ buttons_param[button_id] =
+ Common::ParamPackage{InputCommon::GenerateKeyboardParam(
+ Config::default_mouse_buttons[button_id])};
+ button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
+ });
+ context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
+ });
}
connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
@@ -184,9 +200,9 @@ void ConfigureMouseAdvanced::HandleClick(
button->setText(tr("[press key]"));
button->setFocus();
- // Keyboard keys can only be used as button devices
- want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button;
- if (want_keyboard_keys) {
+ // Keyboard keys or mouse buttons can only be used as button devices
+ want_keyboard_mouse = type == InputCommon::Polling::DeviceType::Button;
+ if (want_keyboard_mouse) {
const auto iter = std::find(button_map.begin(), button_map.end(), button);
ASSERT(iter != button_map.end());
const auto index = std::distance(button_map.begin(), iter);
@@ -195,27 +211,29 @@ void ConfigureMouseAdvanced::HandleClick(
input_setter = new_input_setter;
- device_pollers = InputCommon::Polling::GetPollers(type);
+ device_pollers = input_subsystem->GetPollers(type);
for (auto& poller : device_pollers) {
poller->Start();
}
- grabKeyboard();
- grabMouse();
- timeout_timer->start(5000); // Cancel after 5 seconds
- poll_timer->start(200); // Check for new inputs every 200ms
+ QWidget::grabMouse();
+ QWidget::grabKeyboard();
+
+ timeout_timer->start(2500); // Cancel after 2.5 seconds
+ poll_timer->start(50); // Check for new inputs every 50ms
}
void ConfigureMouseAdvanced::SetPollingResult(const Common::ParamPackage& params, bool abort) {
- releaseKeyboard();
- releaseMouse();
timeout_timer->stop();
poll_timer->stop();
for (auto& poller : device_pollers) {
poller->Stop();
}
+ QWidget::releaseMouse();
+ QWidget::releaseKeyboard();
+
if (!abort) {
(*input_setter)(params);
}
@@ -224,13 +242,29 @@ void ConfigureMouseAdvanced::SetPollingResult(const Common::ParamPackage& params
input_setter = std::nullopt;
}
+void ConfigureMouseAdvanced::mousePressEvent(QMouseEvent* event) {
+ if (!input_setter || !event) {
+ return;
+ }
+
+ if (want_keyboard_mouse) {
+ SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())},
+ false);
+ } else {
+ // We don't want any mouse buttons, so don't stop polling
+ return;
+ }
+
+ SetPollingResult({}, true);
+}
+
void ConfigureMouseAdvanced::keyPressEvent(QKeyEvent* event) {
if (!input_setter || !event) {
return;
}
if (event->key() != Qt::Key_Escape) {
- if (want_keyboard_keys) {
+ if (want_keyboard_mouse) {
SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
false);
} else {
diff --git a/src/yuzu/configuration/configure_mouse_advanced.h b/src/yuzu/configuration/configure_mouse_advanced.h
index 342b82412..65b6fca9a 100644
--- a/src/yuzu/configuration/configure_mouse_advanced.h
+++ b/src/yuzu/configuration/configure_mouse_advanced.h
@@ -8,12 +8,14 @@
#include <optional>
#include <QDialog>
-#include "core/settings.h"
-
class QCheckBox;
class QPushButton;
class QTimer;
+namespace InputCommon {
+class InputSubsystem;
+}
+
namespace Ui {
class ConfigureMouseAdvanced;
}
@@ -22,7 +24,7 @@ class ConfigureMouseAdvanced : public QDialog {
Q_OBJECT
public:
- explicit ConfigureMouseAdvanced(QWidget* parent);
+ explicit ConfigureMouseAdvanced(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_);
~ConfigureMouseAdvanced() override;
void ApplyConfiguration();
@@ -49,11 +51,16 @@ private:
/// Finish polling and configure input using the input_setter
void SetPollingResult(const Common::ParamPackage& params, bool abort);
+ /// Handle mouse button press events.
+ void mousePressEvent(QMouseEvent* event) override;
+
/// Handle key press events.
void keyPressEvent(QKeyEvent* event) override;
std::unique_ptr<Ui::ConfigureMouseAdvanced> ui;
+ InputCommon::InputSubsystem* input_subsystem;
+
/// This will be the the setting function when an input is awaiting configuration.
std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
@@ -67,5 +74,5 @@ private:
/// A flag to indicate if keyboard keys are okay when configuring an input. If this is false,
/// keyboard events are ignored.
- bool want_keyboard_keys = false;
+ bool want_keyboard_mouse = false;
};
diff --git a/src/yuzu/configuration/configure_mouse_advanced.ui b/src/yuzu/configuration/configure_mouse_advanced.ui
index 08245ecf0..74552fdbd 100644
--- a/src/yuzu/configuration/configure_mouse_advanced.ui
+++ b/src/yuzu/configuration/configure_mouse_advanced.ui
@@ -6,13 +6,18 @@
<rect>
<x>0</x>
<y>0</y>
- <width>250</width>
- <height>261</height>
+ <width>310</width>
+ <height>193</height>
</rect>
</property>
<property name="windowTitle">
<string>Configure Mouse</string>
</property>
+ <property name="styleSheet">
+ <string notr="true">QPushButton {
+ min-width: 55px;
+}</string>
+ </property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="gridGroupBox">
@@ -20,81 +25,33 @@
<string>Mouse Buttons</string>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="0" column="4">
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Fixed</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="0" column="3">
- <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item row="3" column="5">
+ <layout class="QVBoxLayout" name="verticalLayout_6">
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
- <widget class="QLabel" name="label_3">
+ <widget class="QLabel" name="label_5">
<property name="text">
- <string>Right:</string>
+ <string>Forward:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
- <widget class="QPushButton" name="right_button">
+ <widget class="QPushButton" name="forward_button">
<property name="minimumSize">
<size>
- <width>75</width>
+ <width>57</width>
<height>0</height>
</size>
</property>
- <property name="text">
- <string/>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="0" column="0">
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Fixed</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="2" column="1">
- <layout class="QVBoxLayout" name="verticalLayout_3">
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <item>
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Middle:</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QPushButton" name="middle_button">
<property name="text">
<string/>
</property>
@@ -123,6 +80,12 @@
</item>
<item>
<widget class="QPushButton" name="back_button">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
<property name="text">
<string/>
</property>
@@ -147,7 +110,7 @@
<widget class="QPushButton" name="left_button">
<property name="minimumSize">
<size>
- <width>75</width>
+ <width>57</width>
<height>0</height>
</size>
</property>
@@ -158,21 +121,99 @@
</item>
</layout>
</item>
- <item row="3" column="3">
- <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item row="0" column="3">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
- <widget class="QLabel" name="label_5">
+ <widget class="QLabel" name="label_2">
<property name="text">
- <string>Forward:</string>
+ <string>Middle:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
- <widget class="QPushButton" name="forward_button">
+ <widget class="QPushButton" name="middle_button">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="6">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="0">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="5">
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Right:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QPushButton" name="right_button">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
+ </property>
<property name="text">
<string/>
</property>
@@ -180,6 +221,32 @@
</item>
</layout>
</item>
+ <item row="0" column="2">
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="4">
+ <spacer name="horizontalSpacer_5">
+ <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>
@@ -187,15 +254,39 @@
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QPushButton" name="buttonClearAll">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
+ </property>
<property name="text">
- <string>Clear All</string>
+ <string>Clear</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonRestoreDefaults">
+ <property name="minimumSize">
+ <size>
+ <width>57</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
+ </property>
<property name="text">
- <string>Restore Defaults</string>
+ <string>Defaults</string>
</property>
</widget>
</item>
@@ -206,21 +297,24 @@
</property>
<property name="sizeHint" stdset="0">
<size>
- <width>40</width>
+ <width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
</layout>
</item>
- <item>
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
- </property>
- </widget>
- </item>
</layout>
</widget>
<resources/>
diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp
index 478d5d3a1..793fd8975 100644
--- a/src/yuzu/configuration/configure_per_game_addons.cpp
+++ b/src/yuzu/configuration/configure_per_game_addons.cpp
@@ -79,8 +79,8 @@ void ConfigurePerGameAddons::ApplyConfiguration() {
std::sort(disabled_addons.begin(), disabled_addons.end());
std::sort(current.begin(), current.end());
if (disabled_addons != current) {
- FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP +
- "game_list" + DIR_SEP + fmt::format("{:016X}.pv.txt", title_id));
+ Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
+ "game_list" + DIR_SEP + fmt::format("{:016X}.pv.txt", title_id));
}
Settings::values.disabled_addons[title_id] = disabled_addons;
diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp
index f53423440..6334c4c50 100644
--- a/src/yuzu/configuration/configure_profile_manager.cpp
+++ b/src/yuzu/configuration/configure_profile_manager.cpp
@@ -34,7 +34,7 @@ constexpr std::array<u8, 107> backup_jpeg{
};
QString GetImagePath(Common::UUID uuid) {
- const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
+ const auto path = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
return QString::fromStdString(path);
}
@@ -282,7 +282,7 @@ void ConfigureProfileManager::SetUserImage() {
}
const auto raw_path = QString::fromStdString(
- FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010");
+ Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + "/system/save/8000000000000010");
const QFileInfo raw_info{raw_path};
if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) {
QMessageBox::warning(this, tr("Error deleting file"),
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index 0c4daf147..9ad43ed8f 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -90,13 +90,13 @@ void ConfigureSystem::SetConfiguration() {
&Settings::values.time_zone_index);
ConfigurationShared::SetPerGameSetting(ui->combo_sound, &Settings::values.sound_index);
- ConfigurationShared::SetHighlight(ui->label_language, "label_language",
+ ConfigurationShared::SetHighlight(ui->label_language,
!Settings::values.language_index.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->label_region, "label_region",
+ ConfigurationShared::SetHighlight(ui->label_region,
!Settings::values.region_index.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->label_timezone, "label_timezone",
+ ConfigurationShared::SetHighlight(ui->label_timezone,
!Settings::values.time_zone_index.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->label_sound, "label_sound",
+ ConfigurationShared::SetHighlight(ui->label_sound,
!Settings::values.sound_index.UsingGlobal());
}
}
@@ -224,22 +224,20 @@ void ConfigureSystem::SetupPerGameUI() {
}
ConfigurationShared::SetColoredComboBox(ui->combo_language, ui->label_language,
- "label_language",
Settings::values.language_index.GetValue(true));
- ConfigurationShared::SetColoredComboBox(ui->combo_region, ui->label_region, "label_region",
+ ConfigurationShared::SetColoredComboBox(ui->combo_region, ui->label_region,
Settings::values.region_index.GetValue(true));
ConfigurationShared::SetColoredComboBox(ui->combo_time_zone, ui->label_timezone,
- "label_timezone",
Settings::values.time_zone_index.GetValue(true));
- ConfigurationShared::SetColoredComboBox(ui->combo_sound, ui->label_sound, "label_sound",
+ ConfigurationShared::SetColoredComboBox(ui->combo_sound, ui->label_sound,
Settings::values.sound_index.GetValue(true));
ConfigurationShared::SetColoredTristate(
- ui->rng_seed_checkbox, "rng_seed_checkbox", Settings::values.rng_seed.UsingGlobal(),
+ ui->rng_seed_checkbox, Settings::values.rng_seed.UsingGlobal(),
Settings::values.rng_seed.GetValue().has_value(),
Settings::values.rng_seed.GetValue(true).has_value(), use_rng_seed);
ConfigurationShared::SetColoredTristate(
- ui->custom_rtc_checkbox, "custom_rtc_checkbox", Settings::values.custom_rtc.UsingGlobal(),
+ ui->custom_rtc_checkbox, Settings::values.custom_rtc.UsingGlobal(),
Settings::values.custom_rtc.GetValue().has_value(),
Settings::values.custom_rtc.GetValue(true).has_value(), use_custom_rtc);
}
diff --git a/src/yuzu/configuration/configure_touch_from_button.cpp b/src/yuzu/configuration/configure_touch_from_button.cpp
new file mode 100644
index 000000000..15557e4b8
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_from_button.cpp
@@ -0,0 +1,623 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QInputDialog>
+#include <QKeyEvent>
+#include <QMessageBox>
+#include <QMouseEvent>
+#include <QResizeEvent>
+#include <QStandardItemModel>
+#include <QTimer>
+#include "common/param_package.h"
+#include "core/frontend/framebuffer_layout.h"
+#include "core/settings.h"
+#include "input_common/main.h"
+#include "ui_configure_touch_from_button.h"
+#include "yuzu/configuration/configure_touch_from_button.h"
+#include "yuzu/configuration/configure_touch_widget.h"
+
+static QString GetKeyName(int key_code) {
+ switch (key_code) {
+ case Qt::Key_Shift:
+ return QObject::tr("Shift");
+ case Qt::Key_Control:
+ return QObject::tr("Ctrl");
+ case Qt::Key_Alt:
+ return QObject::tr("Alt");
+ case Qt::Key_Meta:
+ return QString{};
+ default:
+ return QKeySequence(key_code).toString();
+ }
+}
+
+static QString ButtonToText(const Common::ParamPackage& param) {
+ if (!param.Has("engine")) {
+ return QObject::tr("[not set]");
+ }
+
+ if (param.Get("engine", "") == "keyboard") {
+ return GetKeyName(param.Get("code", 0));
+ }
+
+ if (param.Get("engine", "") == "sdl") {
+ if (param.Has("hat")) {
+ const QString hat_str = QString::fromStdString(param.Get("hat", ""));
+ const QString direction_str = QString::fromStdString(param.Get("direction", ""));
+
+ return QObject::tr("Hat %1 %2").arg(hat_str, direction_str);
+ }
+
+ if (param.Has("axis")) {
+ const QString axis_str = QString::fromStdString(param.Get("axis", ""));
+ const QString direction_str = QString::fromStdString(param.Get("direction", ""));
+
+ return QObject::tr("Axis %1%2").arg(axis_str, direction_str);
+ }
+
+ if (param.Has("button")) {
+ const QString button_str = QString::fromStdString(param.Get("button", ""));
+
+ return QObject::tr("Button %1").arg(button_str);
+ }
+
+ return {};
+ }
+
+ return QObject::tr("[unknown]");
+}
+
+ConfigureTouchFromButton::ConfigureTouchFromButton(
+ QWidget* parent, const std::vector<Settings::TouchFromButtonMap>& touch_maps,
+ InputCommon::InputSubsystem* input_subsystem_, const int default_index)
+ : QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchFromButton>()),
+ touch_maps(touch_maps), input_subsystem{input_subsystem_}, selected_index(default_index),
+ timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
+ ui->setupUi(this);
+ binding_list_model = new QStandardItemModel(0, 3, this);
+ binding_list_model->setHorizontalHeaderLabels(
+ {tr("Button"), tr("X", "X axis"), tr("Y", "Y axis")});
+ ui->binding_list->setModel(binding_list_model);
+ ui->bottom_screen->SetCoordLabel(ui->coord_label);
+
+ SetConfiguration();
+ UpdateUiDisplay();
+ ConnectEvents();
+}
+
+ConfigureTouchFromButton::~ConfigureTouchFromButton() = default;
+
+void ConfigureTouchFromButton::showEvent(QShowEvent* ev) {
+ QWidget::showEvent(ev);
+
+ // width values are not valid in the constructor
+ const int w =
+ ui->binding_list->viewport()->contentsRect().width() / binding_list_model->columnCount();
+ if (w <= 0) {
+ return;
+ }
+ ui->binding_list->setColumnWidth(0, w);
+ ui->binding_list->setColumnWidth(1, w);
+ ui->binding_list->setColumnWidth(2, w);
+}
+
+void ConfigureTouchFromButton::SetConfiguration() {
+ for (const auto& touch_map : touch_maps) {
+ ui->mapping->addItem(QString::fromStdString(touch_map.name));
+ }
+
+ ui->mapping->setCurrentIndex(selected_index);
+}
+
+void ConfigureTouchFromButton::UpdateUiDisplay() {
+ ui->button_delete->setEnabled(touch_maps.size() > 1);
+ ui->button_delete_bind->setEnabled(false);
+
+ binding_list_model->removeRows(0, binding_list_model->rowCount());
+
+ for (const auto& button_str : touch_maps[selected_index].buttons) {
+ Common::ParamPackage package{button_str};
+ QStandardItem* button = new QStandardItem(ButtonToText(package));
+ button->setData(QString::fromStdString(button_str));
+ button->setEditable(false);
+ QStandardItem* xcoord = new QStandardItem(QString::number(package.Get("x", 0)));
+ QStandardItem* ycoord = new QStandardItem(QString::number(package.Get("y", 0)));
+ binding_list_model->appendRow({button, xcoord, ycoord});
+
+ const int dot = ui->bottom_screen->AddDot(package.Get("x", 0), package.Get("y", 0));
+ button->setData(dot, DataRoleDot);
+ }
+}
+
+void ConfigureTouchFromButton::ConnectEvents() {
+ connect(ui->mapping, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
+ SaveCurrentMapping();
+ selected_index = index;
+ UpdateUiDisplay();
+ });
+ connect(ui->button_new, &QPushButton::clicked, this, &ConfigureTouchFromButton::NewMapping);
+ connect(ui->button_delete, &QPushButton::clicked, this,
+ &ConfigureTouchFromButton::DeleteMapping);
+ connect(ui->button_rename, &QPushButton::clicked, this,
+ &ConfigureTouchFromButton::RenameMapping);
+ connect(ui->button_delete_bind, &QPushButton::clicked, this,
+ &ConfigureTouchFromButton::DeleteBinding);
+ connect(ui->binding_list, &QTreeView::doubleClicked, this,
+ &ConfigureTouchFromButton::EditBinding);
+ connect(ui->binding_list->selectionModel(), &QItemSelectionModel::selectionChanged, this,
+ &ConfigureTouchFromButton::OnBindingSelection);
+ connect(binding_list_model, &QStandardItemModel::itemChanged, this,
+ &ConfigureTouchFromButton::OnBindingChanged);
+ connect(ui->binding_list->model(), &QStandardItemModel::rowsAboutToBeRemoved, this,
+ &ConfigureTouchFromButton::OnBindingDeleted);
+ connect(ui->bottom_screen, &TouchScreenPreview::DotAdded, this,
+ &ConfigureTouchFromButton::NewBinding);
+ connect(ui->bottom_screen, &TouchScreenPreview::DotSelected, this,
+ &ConfigureTouchFromButton::SetActiveBinding);
+ connect(ui->bottom_screen, &TouchScreenPreview::DotMoved, this,
+ &ConfigureTouchFromButton::SetCoordinates);
+ connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
+ &ConfigureTouchFromButton::ApplyConfiguration);
+
+ connect(timeout_timer.get(), &QTimer::timeout, [this]() { SetPollingResult({}, true); });
+
+ connect(poll_timer.get(), &QTimer::timeout, [this]() {
+ Common::ParamPackage params;
+ for (auto& poller : device_pollers) {
+ params = poller->GetNextInput();
+ if (params.Has("engine")) {
+ SetPollingResult(params, false);
+ return;
+ }
+ }
+ });
+}
+
+void ConfigureTouchFromButton::SaveCurrentMapping() {
+ auto& map = touch_maps[selected_index];
+ map.buttons.clear();
+ for (int i = 0, rc = binding_list_model->rowCount(); i < rc; ++i) {
+ const auto bind_str = binding_list_model->index(i, 0)
+ .data(Qt::ItemDataRole::UserRole + 1)
+ .toString()
+ .toStdString();
+ if (bind_str.empty()) {
+ continue;
+ }
+ Common::ParamPackage params{bind_str};
+ if (!params.Has("engine")) {
+ continue;
+ }
+ params.Set("x", binding_list_model->index(i, 1).data().toInt());
+ params.Set("y", binding_list_model->index(i, 2).data().toInt());
+ map.buttons.emplace_back(params.Serialize());
+ }
+}
+
+void ConfigureTouchFromButton::NewMapping() {
+ const QString name =
+ QInputDialog::getText(this, tr("New Profile"), tr("Enter the name for the new profile."));
+ if (name.isEmpty()) {
+ return;
+ }
+ touch_maps.emplace_back(Settings::TouchFromButtonMap{name.toStdString(), {}});
+ ui->mapping->addItem(name);
+ ui->mapping->setCurrentIndex(ui->mapping->count() - 1);
+}
+
+void ConfigureTouchFromButton::DeleteMapping() {
+ const auto answer = QMessageBox::question(
+ this, tr("Delete Profile"), tr("Delete profile %1?").arg(ui->mapping->currentText()));
+ if (answer != QMessageBox::Yes) {
+ return;
+ }
+ const bool blocked = ui->mapping->blockSignals(true);
+ ui->mapping->removeItem(selected_index);
+ ui->mapping->blockSignals(blocked);
+ touch_maps.erase(touch_maps.begin() + selected_index);
+ selected_index = ui->mapping->currentIndex();
+ UpdateUiDisplay();
+}
+
+void ConfigureTouchFromButton::RenameMapping() {
+ const QString new_name = QInputDialog::getText(this, tr("Rename Profile"), tr("New name:"));
+ if (new_name.isEmpty()) {
+ return;
+ }
+ ui->mapping->setItemText(selected_index, new_name);
+ touch_maps[selected_index].name = new_name.toStdString();
+}
+
+void ConfigureTouchFromButton::GetButtonInput(const int row_index, const bool is_new) {
+ binding_list_model->item(row_index, 0)->setText(tr("[press key]"));
+
+ input_setter = [this, row_index, is_new](const Common::ParamPackage& params,
+ const bool cancel) {
+ auto* cell = binding_list_model->item(row_index, 0);
+ if (cancel) {
+ if (is_new) {
+ binding_list_model->removeRow(row_index);
+ } else {
+ cell->setText(
+ ButtonToText(Common::ParamPackage{cell->data().toString().toStdString()}));
+ }
+ } else {
+ cell->setText(ButtonToText(params));
+ cell->setData(QString::fromStdString(params.Serialize()));
+ }
+ };
+
+ device_pollers = input_subsystem->GetPollers(InputCommon::Polling::DeviceType::Button);
+
+ for (auto& poller : device_pollers) {
+ poller->Start();
+ }
+
+ grabKeyboard();
+ grabMouse();
+ qApp->setOverrideCursor(QCursor(Qt::CursorShape::ArrowCursor));
+ timeout_timer->start(5000); // Cancel after 5 seconds
+ poll_timer->start(200); // Check for new inputs every 200ms
+}
+
+void ConfigureTouchFromButton::NewBinding(const QPoint& pos) {
+ auto* button = new QStandardItem();
+ button->setEditable(false);
+ auto* x_coord = new QStandardItem(QString::number(pos.x()));
+ auto* y_coord = new QStandardItem(QString::number(pos.y()));
+
+ const int dot_id = ui->bottom_screen->AddDot(pos.x(), pos.y());
+ button->setData(dot_id, DataRoleDot);
+
+ binding_list_model->appendRow({button, x_coord, y_coord});
+ ui->binding_list->setFocus();
+ ui->binding_list->setCurrentIndex(button->index());
+
+ GetButtonInput(binding_list_model->rowCount() - 1, true);
+}
+
+void ConfigureTouchFromButton::EditBinding(const QModelIndex& qi) {
+ if (qi.row() >= 0 && qi.column() == 0) {
+ GetButtonInput(qi.row(), false);
+ }
+}
+
+void ConfigureTouchFromButton::DeleteBinding() {
+ const int row_index = ui->binding_list->currentIndex().row();
+ if (row_index < 0) {
+ return;
+ }
+ ui->bottom_screen->RemoveDot(binding_list_model->index(row_index, 0).data(DataRoleDot).toInt());
+ binding_list_model->removeRow(row_index);
+}
+
+void ConfigureTouchFromButton::OnBindingSelection(const QItemSelection& selected,
+ const QItemSelection& deselected) {
+ ui->button_delete_bind->setEnabled(!selected.isEmpty());
+ if (!selected.isEmpty()) {
+ const auto dot_data = selected.indexes().first().data(DataRoleDot);
+ if (dot_data.isValid()) {
+ ui->bottom_screen->HighlightDot(dot_data.toInt());
+ }
+ }
+ if (!deselected.isEmpty()) {
+ const auto dot_data = deselected.indexes().first().data(DataRoleDot);
+ if (dot_data.isValid()) {
+ ui->bottom_screen->HighlightDot(dot_data.toInt(), false);
+ }
+ }
+}
+
+void ConfigureTouchFromButton::OnBindingChanged(QStandardItem* item) {
+ if (item->column() == 0) {
+ return;
+ }
+
+ const bool blocked = binding_list_model->blockSignals(true);
+ item->setText(QString::number(
+ std::clamp(item->text().toInt(), 0,
+ static_cast<int>((item->column() == 1 ? Layout::ScreenUndocked::Width
+ : Layout::ScreenUndocked::Height) -
+ 1))));
+ binding_list_model->blockSignals(blocked);
+
+ const auto dot_data = binding_list_model->index(item->row(), 0).data(DataRoleDot);
+ if (dot_data.isValid()) {
+ ui->bottom_screen->MoveDot(dot_data.toInt(),
+ binding_list_model->item(item->row(), 1)->text().toInt(),
+ binding_list_model->item(item->row(), 2)->text().toInt());
+ }
+}
+
+void ConfigureTouchFromButton::OnBindingDeleted(const QModelIndex& parent, int first, int last) {
+ for (int i = first; i <= last; ++i) {
+ const auto ix = binding_list_model->index(i, 0);
+ if (!ix.isValid()) {
+ return;
+ }
+ const auto dot_data = ix.data(DataRoleDot);
+ if (dot_data.isValid()) {
+ ui->bottom_screen->RemoveDot(dot_data.toInt());
+ }
+ }
+}
+
+void ConfigureTouchFromButton::SetActiveBinding(const int dot_id) {
+ for (int i = 0; i < binding_list_model->rowCount(); ++i) {
+ if (binding_list_model->index(i, 0).data(DataRoleDot) == dot_id) {
+ ui->binding_list->setCurrentIndex(binding_list_model->index(i, 0));
+ ui->binding_list->setFocus();
+ return;
+ }
+ }
+}
+
+void ConfigureTouchFromButton::SetCoordinates(const int dot_id, const QPoint& pos) {
+ for (int i = 0; i < binding_list_model->rowCount(); ++i) {
+ if (binding_list_model->item(i, 0)->data(DataRoleDot) == dot_id) {
+ binding_list_model->item(i, 1)->setText(QString::number(pos.x()));
+ binding_list_model->item(i, 2)->setText(QString::number(pos.y()));
+ return;
+ }
+ }
+}
+
+void ConfigureTouchFromButton::SetPollingResult(const Common::ParamPackage& params,
+ const bool cancel) {
+ releaseKeyboard();
+ releaseMouse();
+ qApp->restoreOverrideCursor();
+ timeout_timer->stop();
+ poll_timer->stop();
+ for (auto& poller : device_pollers) {
+ poller->Stop();
+ }
+ if (input_setter) {
+ (*input_setter)(params, cancel);
+ input_setter.reset();
+ }
+}
+
+void ConfigureTouchFromButton::keyPressEvent(QKeyEvent* event) {
+ if (!input_setter && event->key() == Qt::Key_Delete) {
+ DeleteBinding();
+ return;
+ }
+
+ if (!input_setter) {
+ return QDialog::keyPressEvent(event);
+ }
+
+ if (event->key() != Qt::Key_Escape) {
+ SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
+ false);
+ } else {
+ SetPollingResult({}, true);
+ }
+}
+
+void ConfigureTouchFromButton::ApplyConfiguration() {
+ SaveCurrentMapping();
+ accept();
+}
+
+int ConfigureTouchFromButton::GetSelectedIndex() const {
+ return selected_index;
+}
+
+std::vector<Settings::TouchFromButtonMap> ConfigureTouchFromButton::GetMaps() const {
+ return touch_maps;
+}
+
+TouchScreenPreview::TouchScreenPreview(QWidget* parent) : QFrame(parent) {
+ setBackgroundRole(QPalette::ColorRole::Base);
+}
+
+TouchScreenPreview::~TouchScreenPreview() = default;
+
+void TouchScreenPreview::SetCoordLabel(QLabel* const label) {
+ coord_label = label;
+}
+
+int TouchScreenPreview::AddDot(const int device_x, const int device_y) {
+ QFont dot_font{QStringLiteral("monospace")};
+ dot_font.setStyleHint(QFont::Monospace);
+ dot_font.setPointSize(20);
+
+ auto* dot = new QLabel(this);
+ dot->setAttribute(Qt::WA_TranslucentBackground);
+ dot->setFont(dot_font);
+ dot->setText(QChar(0xD7)); // U+00D7 Multiplication Sign
+ dot->setAlignment(Qt::AlignmentFlag::AlignCenter);
+ dot->setProperty(PropId, ++max_dot_id);
+ dot->setProperty(PropX, device_x);
+ dot->setProperty(PropY, device_y);
+ dot->setCursor(Qt::CursorShape::PointingHandCursor);
+ dot->setMouseTracking(true);
+ dot->installEventFilter(this);
+ dot->show();
+ PositionDot(dot, device_x, device_y);
+ dots.emplace_back(max_dot_id, dot);
+ return max_dot_id;
+}
+
+void TouchScreenPreview::RemoveDot(const int id) {
+ const auto iter = std::find_if(dots.begin(), dots.end(),
+ [id](const auto& entry) { return entry.first == id; });
+ if (iter == dots.cend()) {
+ return;
+ }
+
+ iter->second->deleteLater();
+ dots.erase(iter);
+}
+
+void TouchScreenPreview::HighlightDot(const int id, const bool active) const {
+ for (const auto& dot : dots) {
+ if (dot.first == id) {
+ // use color property from the stylesheet, or fall back to the default palette
+ if (dot_highlight_color.isValid()) {
+ dot.second->setStyleSheet(
+ active ? QStringLiteral("color: %1").arg(dot_highlight_color.name())
+ : QString{});
+ } else {
+ dot.second->setForegroundRole(active ? QPalette::ColorRole::LinkVisited
+ : QPalette::ColorRole::NoRole);
+ }
+ if (active) {
+ dot.second->raise();
+ }
+ return;
+ }
+ }
+}
+
+void TouchScreenPreview::MoveDot(const int id, const int device_x, const int device_y) const {
+ const auto iter = std::find_if(dots.begin(), dots.end(),
+ [id](const auto& entry) { return entry.first == id; });
+ if (iter == dots.cend()) {
+ return;
+ }
+
+ iter->second->setProperty(PropX, device_x);
+ iter->second->setProperty(PropY, device_y);
+ PositionDot(iter->second, device_x, device_y);
+}
+
+void TouchScreenPreview::resizeEvent(QResizeEvent* event) {
+ if (ignore_resize) {
+ return;
+ }
+
+ const int target_width = std::min(width(), height() * 4 / 3);
+ const int target_height = std::min(height(), width() * 3 / 4);
+ if (target_width == width() && target_height == height()) {
+ return;
+ }
+ ignore_resize = true;
+ setGeometry((parentWidget()->contentsRect().width() - target_width) / 2, y(), target_width,
+ target_height);
+ ignore_resize = false;
+
+ if (event->oldSize().width() != target_width || event->oldSize().height() != target_height) {
+ for (const auto& dot : dots) {
+ PositionDot(dot.second);
+ }
+ }
+}
+
+void TouchScreenPreview::mouseMoveEvent(QMouseEvent* event) {
+ if (!coord_label) {
+ return;
+ }
+ const auto pos = MapToDeviceCoords(event->x(), event->y());
+ if (pos) {
+ coord_label->setText(QStringLiteral("X: %1, Y: %2").arg(pos->x()).arg(pos->y()));
+ } else {
+ coord_label->clear();
+ }
+}
+
+void TouchScreenPreview::leaveEvent(QEvent* event) {
+ if (coord_label) {
+ coord_label->clear();
+ }
+}
+
+void TouchScreenPreview::mousePressEvent(QMouseEvent* event) {
+ if (event->button() != Qt::MouseButton::LeftButton) {
+ return;
+ }
+ const auto pos = MapToDeviceCoords(event->x(), event->y());
+ if (pos) {
+ emit DotAdded(*pos);
+ }
+}
+
+bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) {
+ switch (event->type()) {
+ case QEvent::Type::MouseButtonPress: {
+ const auto mouse_event = static_cast<QMouseEvent*>(event);
+ if (mouse_event->button() != Qt::MouseButton::LeftButton) {
+ break;
+ }
+ emit DotSelected(obj->property(PropId).toInt());
+
+ drag_state.dot = qobject_cast<QLabel*>(obj);
+ drag_state.start_pos = mouse_event->globalPos();
+ return true;
+ }
+ case QEvent::Type::MouseMove: {
+ if (!drag_state.dot) {
+ break;
+ }
+ const auto mouse_event = static_cast<QMouseEvent*>(event);
+ if (!drag_state.active) {
+ drag_state.active =
+ (mouse_event->globalPos() - drag_state.start_pos).manhattanLength() >=
+ QApplication::startDragDistance();
+ if (!drag_state.active) {
+ break;
+ }
+ }
+ auto current_pos = mapFromGlobal(mouse_event->globalPos());
+ current_pos.setX(std::clamp(current_pos.x(), contentsMargins().left(),
+ contentsMargins().left() + contentsRect().width() - 1));
+ current_pos.setY(std::clamp(current_pos.y(), contentsMargins().top(),
+ contentsMargins().top() + contentsRect().height() - 1));
+ const auto device_coord = MapToDeviceCoords(current_pos.x(), current_pos.y());
+ if (device_coord) {
+ drag_state.dot->setProperty(PropX, device_coord->x());
+ drag_state.dot->setProperty(PropY, device_coord->y());
+ PositionDot(drag_state.dot, device_coord->x(), device_coord->y());
+ emit DotMoved(drag_state.dot->property(PropId).toInt(), *device_coord);
+ if (coord_label) {
+ coord_label->setText(
+ QStringLiteral("X: %1, Y: %2").arg(device_coord->x()).arg(device_coord->y()));
+ }
+ }
+ return true;
+ }
+ case QEvent::Type::MouseButtonRelease: {
+ drag_state.dot.clear();
+ drag_state.active = false;
+ return true;
+ }
+ default:
+ break;
+ }
+ return obj->eventFilter(obj, event);
+}
+
+std::optional<QPoint> TouchScreenPreview::MapToDeviceCoords(const int screen_x,
+ const int screen_y) const {
+ const float t_x = 0.5f + static_cast<float>(screen_x - contentsMargins().left()) *
+ (Layout::ScreenUndocked::Width - 1) / (contentsRect().width() - 1);
+ const float t_y = 0.5f + static_cast<float>(screen_y - contentsMargins().top()) *
+ (Layout::ScreenUndocked::Height - 1) /
+ (contentsRect().height() - 1);
+ if (t_x >= 0.5f && t_x < Layout::ScreenUndocked::Width && t_y >= 0.5f &&
+ t_y < Layout::ScreenUndocked::Height) {
+
+ return QPoint{static_cast<int>(t_x), static_cast<int>(t_y)};
+ }
+ return std::nullopt;
+}
+
+void TouchScreenPreview::PositionDot(QLabel* const dot, const int device_x,
+ const int device_y) const {
+ const float device_coord_x =
+ static_cast<float>(device_x >= 0 ? device_x : dot->property(PropX).toInt());
+ int x_coord = static_cast<int>(
+ device_coord_x * (contentsRect().width() - 1) / (Layout::ScreenUndocked::Width - 1) +
+ contentsMargins().left() - static_cast<float>(dot->width()) / 2 + 0.5f);
+
+ const float device_coord_y =
+ static_cast<float>(device_y >= 0 ? device_y : dot->property(PropY).toInt());
+ const int y_coord = static_cast<int>(
+ device_coord_y * (contentsRect().height() - 1) / (Layout::ScreenUndocked::Height - 1) +
+ contentsMargins().top() - static_cast<float>(dot->height()) / 2 + 0.5f);
+
+ dot->move(x_coord, y_coord);
+}
diff --git a/src/yuzu/configuration/configure_touch_from_button.h b/src/yuzu/configuration/configure_touch_from_button.h
new file mode 100644
index 000000000..d9513e3bc
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_from_button.h
@@ -0,0 +1,92 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <optional>
+#include <vector>
+#include <QDialog>
+
+class QItemSelection;
+class QModelIndex;
+class QStandardItemModel;
+class QStandardItem;
+class QTimer;
+
+namespace Common {
+class ParamPackage;
+}
+
+namespace InputCommon {
+class InputSubsystem;
+}
+
+namespace InputCommon::Polling {
+class DevicePoller;
+}
+
+namespace Settings {
+struct TouchFromButtonMap;
+}
+
+namespace Ui {
+class ConfigureTouchFromButton;
+}
+
+class ConfigureTouchFromButton : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit ConfigureTouchFromButton(QWidget* parent,
+ const std::vector<Settings::TouchFromButtonMap>& touch_maps,
+ InputCommon::InputSubsystem* input_subsystem_,
+ int default_index = 0);
+ ~ConfigureTouchFromButton() override;
+
+ int GetSelectedIndex() const;
+ std::vector<Settings::TouchFromButtonMap> GetMaps() const;
+
+public slots:
+ void ApplyConfiguration();
+ void NewBinding(const QPoint& pos);
+ void SetActiveBinding(int dot_id);
+ void SetCoordinates(int dot_id, const QPoint& pos);
+
+protected:
+ void showEvent(QShowEvent* ev) override;
+ void keyPressEvent(QKeyEvent* event) override;
+
+private slots:
+ void NewMapping();
+ void DeleteMapping();
+ void RenameMapping();
+ void EditBinding(const QModelIndex& qi);
+ void DeleteBinding();
+ void OnBindingSelection(const QItemSelection& selected, const QItemSelection& deselected);
+ void OnBindingChanged(QStandardItem* item);
+ void OnBindingDeleted(const QModelIndex& parent, int first, int last);
+
+private:
+ void SetConfiguration();
+ void UpdateUiDisplay();
+ void ConnectEvents();
+ void GetButtonInput(int row_index, bool is_new);
+ void SetPollingResult(const Common::ParamPackage& params, bool cancel);
+ void SaveCurrentMapping();
+
+ std::unique_ptr<Ui::ConfigureTouchFromButton> ui;
+ std::vector<Settings::TouchFromButtonMap> touch_maps;
+ QStandardItemModel* binding_list_model;
+ InputCommon::InputSubsystem* input_subsystem;
+ int selected_index;
+
+ std::unique_ptr<QTimer> timeout_timer;
+ std::unique_ptr<QTimer> poll_timer;
+ std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
+ std::optional<std::function<void(const Common::ParamPackage&, bool)>> input_setter;
+
+ static constexpr int DataRoleDot = Qt::ItemDataRole::UserRole + 2;
+};
diff --git a/src/yuzu/configuration/configure_touch_from_button.ui b/src/yuzu/configuration/configure_touch_from_button.ui
new file mode 100644
index 000000000..f581e27e0
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_from_button.ui
@@ -0,0 +1,231 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureTouchFromButton</class>
+ <widget class="QDialog" name="ConfigureTouchFromButton">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>500</width>
+ <height>500</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Configure Touchscreen Mappings</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Mapping:</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::PlainText</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="mapping">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="button_new">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>New</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="button_delete">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Delete</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="button_rename">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Rename</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::PlainText</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="button_delete_bind">
+ <property name="text">
+ <string>Delete Point</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QTreeView" name="binding_list">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ <property name="itemsExpandable">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="TouchScreenPreview" name="bottom_screen">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>160</width>
+ <height>120</height>
+ </size>
+ </property>
+ <property name="baseSize">
+ <size>
+ <width>320</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="cursor">
+ <cursorShape>CrossCursor</cursorShape>
+ </property>
+ <property name="mouseTracking">
+ <bool>true</bool>
+ </property>
+ <property name="autoFillBackground">
+ <bool>true</bool>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QLabel" name="coord_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::PlainText</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>TouchScreenPreview</class>
+ <extends>QFrame</extends>
+ <header>yuzu/configuration/configure_touch_widget.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ConfigureTouchFromButton</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>249</x>
+ <y>428</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>249</x>
+ <y>224</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/configuration/configure_touch_widget.h b/src/yuzu/configuration/configure_touch_widget.h
new file mode 100644
index 000000000..347b46583
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_widget.h
@@ -0,0 +1,62 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <optional>
+#include <utility>
+#include <vector>
+#include <QFrame>
+#include <QPointer>
+
+class QLabel;
+
+// Widget for representing touchscreen coordinates
+class TouchScreenPreview : public QFrame {
+ Q_OBJECT
+ Q_PROPERTY(QColor dotHighlightColor MEMBER dot_highlight_color)
+
+public:
+ explicit TouchScreenPreview(QWidget* parent);
+ ~TouchScreenPreview() override;
+
+ void SetCoordLabel(QLabel*);
+ int AddDot(int device_x, int device_y);
+ void RemoveDot(int id);
+ void HighlightDot(int id, bool active = true) const;
+ void MoveDot(int id, int device_x, int device_y) const;
+
+signals:
+ void DotAdded(const QPoint& pos);
+ void DotSelected(int dot_id);
+ void DotMoved(int dot_id, const QPoint& pos);
+
+protected:
+ void resizeEvent(QResizeEvent*) override;
+ void mouseMoveEvent(QMouseEvent*) override;
+ void leaveEvent(QEvent*) override;
+ void mousePressEvent(QMouseEvent*) override;
+ bool eventFilter(QObject*, QEvent*) override;
+
+private:
+ std::optional<QPoint> MapToDeviceCoords(int screen_x, int screen_y) const;
+ void PositionDot(QLabel* dot, int device_x = -1, int device_y = -1) const;
+
+ bool ignore_resize = false;
+ QPointer<QLabel> coord_label;
+
+ std::vector<std::pair<int, QLabel*>> dots;
+ int max_dot_id = 0;
+ QColor dot_highlight_color;
+ static constexpr char PropId[] = "dot_id";
+ static constexpr char PropX[] = "device_x";
+ static constexpr char PropY[] = "device_y";
+
+ struct DragState {
+ bool active = false;
+ QPointer<QLabel> dot;
+ QPoint start_pos;
+ };
+ DragState drag_state;
+};
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index 24b6c5b72..dbe3f78c8 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -4,9 +4,11 @@
#include <array>
#include <utility>
+#include <QFileDialog>
#include <QDirIterator>
#include "common/common_types.h"
+#include "common/file_util.h"
#include "core/settings.h"
#include "ui_configure_ui.h"
#include "yuzu/configuration/configure_ui.h"
@@ -52,9 +54,21 @@ ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::Configur
// Update text ComboBoxes after user interaction.
connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::activated),
- [=]() { ConfigureUi::UpdateSecondRowComboBox(); });
+ [this] { ConfigureUi::UpdateSecondRowComboBox(); });
connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::activated),
- [=]() { ConfigureUi::UpdateFirstRowComboBox(); });
+ [this] { ConfigureUi::UpdateFirstRowComboBox(); });
+
+ // Set screenshot path to user specification.
+ connect(ui->screenshot_path_button, &QToolButton::pressed, this, [this] {
+ const QString& filename =
+ QFileDialog::getExistingDirectory(this, tr("Select Screenshots Path..."),
+ QString::fromStdString(Common::FS::GetUserPath(
+ Common::FS::UserPath::ScreenshotsDir))) +
+ QDir::separator();
+ if (!filename.isEmpty()) {
+ ui->screenshot_path_edit->setText(filename);
+ }
+ });
}
ConfigureUi::~ConfigureUi() = default;
@@ -66,6 +80,10 @@ void ConfigureUi::ApplyConfiguration() {
UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt();
UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();
UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt();
+
+ UISettings::values.enable_screenshot_save_as = ui->enable_screenshot_save_as->isChecked();
+ Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir,
+ ui->screenshot_path_edit->text().toStdString());
Settings::Apply();
}
@@ -80,6 +98,10 @@ void ConfigureUi::SetConfiguration() {
ui->show_add_ons->setChecked(UISettings::values.show_add_ons);
ui->icon_size_combobox->setCurrentIndex(
ui->icon_size_combobox->findData(UISettings::values.icon_size));
+
+ ui->enable_screenshot_save_as->setChecked(UISettings::values.enable_screenshot_save_as);
+ ui->screenshot_path_edit->setText(
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir)));
}
void ConfigureUi::changeEvent(QEvent* event) {
diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui
index 0b81747d7..d895b799f 100644
--- a/src/yuzu/configuration/configure_ui.ui
+++ b/src/yuzu/configuration/configure_ui.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>300</width>
- <height>377</height>
+ <width>363</width>
+ <height>391</height>
</rect>
</property>
<property name="windowTitle">
@@ -128,6 +128,47 @@
</widget>
</item>
<item>
+ <widget class="QGroupBox" name="screenshots_GroupBox">
+ <property name="title">
+ <string>Screenshots</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QCheckBox" name="enable_screenshot_save_as">
+ <property name="text">
+ <string>Ask Where To Save Screenshots (Windows Only)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Screenshots Path: </string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="screenshot_path_edit"/>
+ </item>
+ <item>
+ <widget class="QToolButton" name="screenshot_path_button">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp
index 53049ffd6..0e26f765b 100644
--- a/src/yuzu/debugger/profiler.cpp
+++ b/src/yuzu/debugger/profiler.cpp
@@ -109,8 +109,7 @@ MicroProfileWidget::MicroProfileWidget(QWidget* parent) : QWidget(parent) {
MicroProfileSetDisplayMode(1); // Timers screen
MicroProfileInitUI();
- connect(&update_timer, &QTimer::timeout, this,
- static_cast<void (MicroProfileWidget::*)()>(&MicroProfileWidget::update));
+ connect(&update_timer, &QTimer::timeout, this, qOverload<>(&MicroProfileWidget::update));
}
void MicroProfileWidget::paintEvent(QPaintEvent* ev) {
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index ab7fc7a24..6a71d9644 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -406,7 +406,7 @@ bool GameList::isEmpty() const {
type == GameListItemType::SysNandDir)) {
item_model->invisibleRootItem()->removeRow(child->row());
i--;
- };
+ }
}
return !item_model->invisibleRootItem()->hasChildren();
}
@@ -474,28 +474,56 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, std::string path) {
QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
- QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location"));
+ QAction* open_mod_location = context_menu.addAction(tr("Open Mod Data Location"));
QAction* open_transferable_shader_cache =
context_menu.addAction(tr("Open Transferable Shader Cache"));
context_menu.addSeparator();
+ QMenu* remove_menu = context_menu.addMenu(tr("Remove"));
+ QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update"));
+ QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC"));
+ QAction* remove_shader_cache = remove_menu->addAction(tr("Remove Shader Cache"));
+ QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration"));
+ remove_menu->addSeparator();
+ QAction* remove_all_content = remove_menu->addAction(tr("Remove All Installed Contents"));
QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));
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"));
context_menu.addSeparator();
QAction* properties = context_menu.addAction(tr("Properties"));
- open_save_location->setEnabled(program_id != 0);
+ open_save_location->setVisible(program_id != 0);
+ open_mod_location->setVisible(program_id != 0);
+ open_transferable_shader_cache->setVisible(program_id != 0);
+ remove_update->setVisible(program_id != 0);
+ remove_dlc->setVisible(program_id != 0);
+ remove_shader_cache->setVisible(program_id != 0);
+ remove_all_content->setVisible(program_id != 0);
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0);
connect(open_save_location, &QAction::triggered, [this, program_id, path]() {
- emit OpenFolderRequested(GameListOpenTarget::SaveData, path);
+ emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData, path);
});
- connect(open_lfs_location, &QAction::triggered, [this, program_id, path]() {
- emit OpenFolderRequested(GameListOpenTarget::ModData, path);
+ connect(open_mod_location, &QAction::triggered, [this, program_id, path]() {
+ emit OpenFolderRequested(program_id, GameListOpenTarget::ModData, path);
});
connect(open_transferable_shader_cache, &QAction::triggered,
[this, program_id]() { emit OpenTransferableShaderCacheRequested(program_id); });
+ connect(remove_all_content, &QAction::triggered, [this, program_id]() {
+ emit RemoveInstalledEntryRequested(program_id, InstalledEntryType::Game);
+ });
+ connect(remove_update, &QAction::triggered, [this, program_id]() {
+ emit RemoveInstalledEntryRequested(program_id, InstalledEntryType::Update);
+ });
+ connect(remove_dlc, &QAction::triggered, [this, program_id]() {
+ emit RemoveInstalledEntryRequested(program_id, InstalledEntryType::AddOnContent);
+ });
+ connect(remove_shader_cache, &QAction::triggered, [this, program_id]() {
+ emit RemoveFileRequested(program_id, GameListRemoveTarget::ShaderCache);
+ });
+ connect(remove_custom_config, &QAction::triggered, [this, program_id]() {
+ emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration);
+ });
connect(dump_romfs, &QAction::triggered,
[this, program_id, path]() { emit DumpRomFSRequested(program_id, path); });
connect(copy_tid, &QAction::triggered,
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index a38cb2fc3..78e2ba169 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -39,6 +39,17 @@ enum class GameListOpenTarget {
ModData,
};
+enum class GameListRemoveTarget {
+ ShaderCache,
+ CustomConfiguration,
+};
+
+enum class InstalledEntryType {
+ Game,
+ Update,
+ AddOnContent,
+};
+
class GameList : public QWidget {
Q_OBJECT
@@ -73,8 +84,11 @@ public:
signals:
void GameChosen(QString game_path);
void ShouldCancelWorker();
- void OpenFolderRequested(GameListOpenTarget target, const std::string& game_path);
+ void OpenFolderRequested(u64 program_id, GameListOpenTarget target,
+ const std::string& game_path);
void OpenTransferableShaderCacheRequested(u64 program_id);
+ void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type);
+ void RemoveFileRequested(u64 program_id, GameListRemoveTarget target);
void DumpRomFSRequested(u64 program_id, const std::string& game_path);
void CopyTIDRequested(u64 program_id);
void NavigateToGamedbEntryRequested(u64 program_id,
@@ -117,8 +131,6 @@ private:
friend class GameListSearchField;
};
-Q_DECLARE_METATYPE(GameListOpenTarget);
-
class GameListPlaceholder : public QWidget {
Q_OBJECT
public:
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 0cd0054c8..92779a9c7 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -49,10 +49,10 @@ class GameListItem : public QStandardItem {
public:
// used to access type from item index
- static const int TypeRole = Qt::UserRole + 1;
- static const int SortRole = Qt::UserRole + 2;
+ static constexpr int TypeRole = Qt::UserRole + 1;
+ static constexpr int SortRole = Qt::UserRole + 2;
GameListItem() = default;
- GameListItem(const QString& string) : QStandardItem(string) {
+ explicit GameListItem(const QString& string) : QStandardItem(string) {
setData(string, SortRole);
}
};
@@ -65,10 +65,10 @@ public:
*/
class GameListItemPath : public GameListItem {
public:
- static const int TitleRole = SortRole + 1;
- static const int FullPathRole = SortRole + 2;
- static const int ProgramIdRole = SortRole + 3;
- static const int FileTypeRole = SortRole + 4;
+ static constexpr int TitleRole = SortRole + 1;
+ static constexpr int FullPathRole = SortRole + 2;
+ static constexpr int ProgramIdRole = SortRole + 3;
+ static constexpr int FileTypeRole = SortRole + 4;
GameListItemPath() = default;
GameListItemPath(const QString& game_path, const std::vector<u8>& picture_data,
@@ -110,18 +110,22 @@ public:
const auto& row1 = row_data.at(UISettings::values.row_1_text_id);
const int row2_id = UISettings::values.row_2_text_id;
- if (role == SortRole)
+ if (role == SortRole) {
return row1.toLower();
+ }
- if (row2_id == 4) // None
+ // None
+ if (row2_id == 4) {
return row1;
+ }
const auto& row2 = row_data.at(row2_id);
- if (row1 == row2)
+ if (row1 == row2) {
return row1;
+ }
- return QString(row1 + QStringLiteral("\n ") + row2);
+ return QStringLiteral("%1\n %2").arg(row1, row2);
}
return GameListItem::data(role);
@@ -131,7 +135,7 @@ public:
class GameListItemCompat : public GameListItem {
Q_DECLARE_TR_FUNCTIONS(GameListItemCompat)
public:
- static const int CompatNumberRole = SortRole;
+ static constexpr int CompatNumberRole = SortRole;
GameListItemCompat() = default;
explicit GameListItemCompat(const QString& compatibility) {
setData(type(), TypeRole);
@@ -181,7 +185,7 @@ public:
*/
class GameListItemSize : public GameListItem {
public:
- static const int SizeRole = SortRole;
+ static constexpr int SizeRole = SortRole;
GameListItemSize() = default;
explicit GameListItemSize(const qulonglong size_bytes) {
@@ -217,7 +221,7 @@ public:
class GameListDir : public GameListItem {
public:
- static const int GameDirRole = Qt::UserRole + 2;
+ static constexpr int GameDirRole = Qt::UserRole + 2;
explicit GameListDir(UISettings::GameDir& directory,
GameListItemType dir_type = GameListItemType::CustomDir)
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index 2018150db..e0ce45fd9 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -39,12 +39,12 @@ QString GetGameListCachedObject(const std::string& filename, const std::string&
return generator();
}
- const auto path = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
- DIR_SEP + filename + '.' + ext;
+ const auto path = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
+ "game_list" + DIR_SEP + filename + '.' + ext;
- FileUtil::CreateFullPath(path);
+ Common::FS::CreateFullPath(path);
- if (!FileUtil::Exists(path)) {
+ if (!Common::FS::Exists(path)) {
const auto str = generator();
QFile file{QString::fromStdString(path)};
@@ -70,14 +70,14 @@ std::pair<std::vector<u8>, std::string> GetGameListCachedObject(
return generator();
}
- const auto path1 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
- DIR_SEP + filename + ".jpeg";
- const auto path2 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
- DIR_SEP + filename + ".appname.txt";
+ const auto path1 = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
+ "game_list" + DIR_SEP + filename + ".jpeg";
+ const auto path2 = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
+ "game_list" + DIR_SEP + filename + ".appname.txt";
- FileUtil::CreateFullPath(path1);
+ Common::FS::CreateFullPath(path1);
- if (!FileUtil::Exists(path1) || !FileUtil::Exists(path2)) {
+ if (!Common::FS::Exists(path1) || !Common::FS::Exists(path2)) {
const auto [icon, nacp] = generator();
QFile file1{QString::fromStdString(path1)};
@@ -208,7 +208,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
file_type_string, program_id),
new GameListItemCompat(compatibility),
new GameListItem(file_type_string),
- new GameListItemSize(FileUtil::GetSize(path)),
+ new GameListItemSize(Common::FS::GetSize(path)),
};
if (UISettings::values.show_add_ons) {
@@ -289,7 +289,7 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
}
const std::string physical_name = directory + DIR_SEP + virtual_name;
- const bool is_dir = FileUtil::IsDirectory(physical_name);
+ const bool is_dir = Common::FS::IsDirectory(physical_name);
if (!is_dir &&
(HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read);
@@ -345,11 +345,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
return true;
};
- FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback);
+ Common::FS::ForeachDirectoryEntry(nullptr, dir_path, callback);
}
void GameListWorker::run() {
stop_processing = false;
+ provider->ClearAllEntries();
for (UISettings::GameDir& game_dir : game_dirs) {
if (game_dir.path == QStringLiteral("SDMC")) {
@@ -368,13 +369,12 @@ void GameListWorker::run() {
watch_list.append(game_dir.path);
auto* const game_list_dir = new GameListDir(game_dir);
emit DirEntryReady(game_list_dir);
- provider->ClearAllEntries();
- ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), 2,
- game_list_dir);
+ ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(),
+ game_dir.deep_scan ? 256 : 0, game_list_dir);
ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(),
game_dir.deep_scan ? 256 : 0, game_list_dir);
}
- };
+ }
emit Finished(watch_list);
}
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 31a635176..68ad43a80 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -11,6 +11,7 @@
#endif
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
+#include "applets/controller.h"
#include "applets/error.h"
#include "applets/profile_select.h"
#include "applets/software_keyboard.h"
@@ -19,7 +20,9 @@
#include "configuration/configure_per_game.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
+#include "core/frontend/applets/controller.h"
#include "core/frontend/applets/general_frontend.h"
+#include "core/frontend/applets/software_keyboard.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"
@@ -84,7 +87,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/file_sys/romfs.h"
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/submission_package.h"
-#include "core/frontend/applets/software_keyboard.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/filesystem/filesystem.h"
@@ -94,6 +96,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/perf_stats.h"
#include "core/settings.h"
#include "core/telemetry_session.h"
+#include "input_common/main.h"
#include "video_core/gpu.h"
#include "video_core/shader_notify.h"
#include "yuzu/about_dialog.h"
@@ -177,8 +180,8 @@ static void InitializeLogging() {
log_filter.ParseFilterString(Settings::values.log_filter);
Log::SetGlobalFilter(log_filter);
- const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
- FileUtil::CreateFullPath(log_dir);
+ const std::string& log_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir);
+ Common::FS::CreateFullPath(log_dir);
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
#ifdef _WIN32
Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
@@ -186,9 +189,9 @@ static void InitializeLogging() {
}
GMainWindow::GMainWindow()
- : config(new Config()), emu_thread(nullptr),
- vfs(std::make_shared<FileSys::RealVfsFilesystem>()),
- provider(std::make_unique<FileSys::ManualContentProvider>()) {
+ : input_subsystem{std::make_shared<InputCommon::InputSubsystem>()},
+ config{std::make_unique<Config>()}, vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
+ provider{std::make_unique<FileSys::ManualContentProvider>()} {
InitializeLogging();
LoadTranslation();
@@ -282,6 +285,23 @@ GMainWindow::~GMainWindow() {
delete render_window;
}
+void GMainWindow::ControllerSelectorReconfigureControllers(
+ const Core::Frontend::ControllerParameters& parameters) {
+ QtControllerSelectorDialog dialog(this, parameters, input_subsystem.get());
+ dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
+ Qt::WindowSystemMenuHint);
+ dialog.setWindowModality(Qt::WindowModal);
+ dialog.exec();
+
+ emit ControllerSelectorReconfigureFinished();
+
+ // Don't forget to apply settings.
+ Settings::Apply();
+ config->Save();
+
+ UpdateStatusButtons();
+}
+
void GMainWindow::ProfileSelectorSelectProfile() {
const Service::Account::ProfileManager manager;
int index = 0;
@@ -290,10 +310,12 @@ void GMainWindow::ProfileSelectorSelectProfile() {
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
dialog.setWindowModality(Qt::WindowModal);
+
if (dialog.exec() == QDialog::Rejected) {
emit ProfileSelectorFinishedSelection(std::nullopt);
return;
}
+
index = dialog.GetIndex();
}
@@ -473,7 +495,7 @@ void GMainWindow::InitializeWidgets() {
#ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING
ui.action_Report_Compatibility->setVisible(true);
#endif
- render_window = new GRenderWindow(this, emu_thread.get());
+ render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem);
render_window->hide();
game_list = new GameList(vfs, provider.get(), this);
@@ -583,7 +605,7 @@ void GMainWindow::InitializeWidgets() {
renderer_status_button->setObjectName(QStringLiteral("RendererStatusBarButton"));
renderer_status_button->setCheckable(true);
renderer_status_button->setFocusPolicy(Qt::NoFocus);
- connect(renderer_status_button, &QPushButton::toggled, [=](bool checked) {
+ connect(renderer_status_button, &QPushButton::toggled, [this](bool checked) {
renderer_status_button->setText(checked ? tr("VULKAN") : tr("OPENGL"));
});
renderer_status_button->toggle();
@@ -595,7 +617,7 @@ void GMainWindow::InitializeWidgets() {
#else
renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() ==
Settings::RendererBackend::Vulkan);
- connect(renderer_status_button, &QPushButton::clicked, [=] {
+ connect(renderer_status_button, &QPushButton::clicked, [this] {
if (emulation_running) {
return;
}
@@ -847,6 +869,9 @@ void GMainWindow::ConnectWidgetEvents() {
connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder);
connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this,
&GMainWindow::OnTransferableShaderCacheOpenFile);
+ connect(game_list, &GameList::RemoveInstalledEntryRequested, this,
+ &GMainWindow::OnGameListRemoveInstalledEntry);
+ connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile);
connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
@@ -891,6 +916,8 @@ void GMainWindow::ConnectMenuEvents() {
connect(ui.action_Open_FAQ, &QAction::triggered, this, &GMainWindow::OnOpenFAQ);
connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); });
connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure);
+ connect(ui.action_Configure_Current_Game, &QAction::triggered, this,
+ &GMainWindow::OnConfigurePerGame);
// View
connect(ui.action_Single_Window_Mode, &QAction::triggered, this,
@@ -960,13 +987,14 @@ bool GMainWindow::LoadROM(const QString& filename) {
system.SetFilesystem(vfs);
system.SetAppletFrontendSet({
- nullptr, // Parental Controls
- std::make_unique<QtErrorDisplay>(*this), //
- nullptr, // Photo Viewer
- std::make_unique<QtProfileSelector>(*this), //
- std::make_unique<QtSoftwareKeyboard>(*this), //
- std::make_unique<QtWebBrowser>(*this), //
- nullptr, // E-Commerce
+ std::make_unique<QtControllerSelector>(*this), // Controller Selector
+ nullptr, // E-Commerce
+ std::make_unique<QtErrorDisplay>(*this), // Error Display
+ nullptr, // Parental Controls
+ nullptr, // Photo Viewer
+ std::make_unique<QtProfileSelector>(*this), // Profile Selector
+ std::make_unique<QtSoftwareKeyboard>(*this), // Software Keyboard
+ std::make_unique<QtWebBrowser>(*this), // Web Browser
});
system.RegisterHostThread();
@@ -1036,7 +1064,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
}
game_path = filename;
- system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "Qt");
+ system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "Qt");
return true;
}
@@ -1118,7 +1146,7 @@ void GMainWindow::BootGame(const QString& filename) {
title_name = metadata.first->GetApplicationName();
}
if (res != Loader::ResultStatus::Success || title_name.empty()) {
- title_name = FileUtil::GetFilename(filename.toStdString());
+ title_name = Common::FS::GetFilename(filename.toStdString());
}
LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version);
UpdateWindowTitle(title_name, title_version);
@@ -1164,6 +1192,7 @@ void GMainWindow::ShutdownGame() {
ui.action_Pause->setEnabled(false);
ui.action_Stop->setEnabled(false);
ui.action_Restart->setEnabled(false);
+ ui.action_Configure_Current_Game->setEnabled(false);
ui.action_Report_Compatibility->setEnabled(false);
ui.action_Load_Amiibo->setEnabled(false);
ui.action_Capture_Screenshot->setEnabled(false);
@@ -1236,28 +1265,36 @@ void GMainWindow::OnGameListLoadFile(QString game_path) {
BootGame(game_path);
}
-void GMainWindow::OnGameListOpenFolder(GameListOpenTarget target, const std::string& game_path) {
+void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target,
+ const std::string& game_path) {
std::string path;
QString open_target;
- const auto v_file = Core::GetGameFileFromPath(vfs, game_path);
- const auto loader = Loader::GetLoader(v_file);
- FileSys::NACP control{};
- u64 program_id{};
+ const auto [user_save_size, device_save_size] = [this, &program_id, &game_path] {
+ FileSys::PatchManager pm{program_id};
+ const auto control = pm.GetControlMetadata().first;
+ if (control != nullptr) {
+ return std::make_pair(control->GetDefaultNormalSaveSize(),
+ control->GetDeviceSaveDataSize());
+ } else {
+ const auto file = Core::GetGameFileFromPath(vfs, game_path);
+ const auto loader = Loader::GetLoader(file);
- loader->ReadControlData(control);
- loader->ReadProgramId(program_id);
+ FileSys::NACP nacp{};
+ loader->ReadControlData(nacp);
+ return std::make_pair(nacp.GetDefaultNormalSaveSize(), nacp.GetDeviceSaveDataSize());
+ }
+ }();
- const bool has_user_save{control.GetDefaultNormalSaveSize() > 0};
- const bool has_device_save{control.GetDeviceSaveDataSize() > 0};
+ const bool has_user_save{user_save_size > 0};
+ const bool has_device_save{device_save_size > 0};
ASSERT_MSG(has_user_save != has_device_save, "Game uses both user and device savedata?");
switch (target) {
case GameListOpenTarget::SaveData: {
open_target = tr("Save Data");
- const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
- ASSERT(program_id != 0);
+ const std::string nand_dir = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir);
if (has_user_save) {
// User save data
@@ -1292,16 +1329,16 @@ void GMainWindow::OnGameListOpenFolder(GameListOpenTarget target, const std::str
FileSys::SaveDataType::SaveData, program_id, {}, 0);
}
- if (!FileUtil::Exists(path)) {
- FileUtil::CreateFullPath(path);
- FileUtil::CreateDir(path);
+ if (!Common::FS::Exists(path)) {
+ Common::FS::CreateFullPath(path);
+ Common::FS::CreateDir(path);
}
break;
}
case GameListOpenTarget::ModData: {
open_target = tr("Mod Data");
- const auto load_dir = FileUtil::GetUserPath(FileUtil::UserPath::LoadDir);
+ const auto load_dir = Common::FS::GetUserPath(Common::FS::UserPath::LoadDir);
path = fmt::format("{}{:016X}", load_dir, program_id);
break;
}
@@ -1322,14 +1359,12 @@ void GMainWindow::OnGameListOpenFolder(GameListOpenTarget target, const std::str
}
void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) {
- ASSERT(program_id != 0);
-
const QString shader_dir =
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir));
- const QString tranferable_shader_cache_folder_path =
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir));
+ const QString transferable_shader_cache_folder_path =
shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable");
const QString transferable_shader_cache_file_path =
- tranferable_shader_cache_folder_path + QDir::separator() +
+ transferable_shader_cache_folder_path + QDir::separator() +
QString::fromStdString(fmt::format("{:016X}.bin", program_id));
if (!QFile::exists(transferable_shader_cache_file_path)) {
@@ -1350,7 +1385,7 @@ void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) {
param << QDir::toNativeSeparators(transferable_shader_cache_file_path);
QProcess::startDetached(explorer, param);
#else
- QDesktopServices::openUrl(QUrl::fromLocalFile(tranferable_shader_cache_folder_path));
+ QDesktopServices::openUrl(QUrl::fromLocalFile(transferable_shader_cache_folder_path));
#endif
}
@@ -1394,6 +1429,174 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src
return true;
}
+void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type) {
+ const QString entry_type = [this, type] {
+ switch (type) {
+ case InstalledEntryType::Game:
+ return tr("Contents");
+ case InstalledEntryType::Update:
+ return tr("Update");
+ case InstalledEntryType::AddOnContent:
+ return tr("DLC");
+ default:
+ return QString{};
+ }
+ }();
+
+ if (QMessageBox::question(
+ this, tr("Remove Entry"), tr("Remove Installed Game %1?").arg(entry_type),
+ QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) {
+ return;
+ }
+
+ switch (type) {
+ case InstalledEntryType::Game:
+ RemoveBaseContent(program_id, entry_type);
+ [[fallthrough]];
+ case InstalledEntryType::Update:
+ RemoveUpdateContent(program_id, entry_type);
+ if (type != InstalledEntryType::Game) {
+ break;
+ }
+ [[fallthrough]];
+ case InstalledEntryType::AddOnContent:
+ RemoveAddOnContent(program_id, entry_type);
+ break;
+ }
+ Common::FS::DeleteDirRecursively(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) +
+ DIR_SEP + "game_list");
+ game_list->PopulateAsync(UISettings::values.game_dirs);
+}
+
+void GMainWindow::RemoveBaseContent(u64 program_id, const QString& entry_type) {
+ const auto& fs_controller = Core::System::GetInstance().GetFileSystemController();
+ const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(program_id) ||
+ fs_controller.GetSDMCContents()->RemoveExistingEntry(program_id);
+
+ if (res) {
+ QMessageBox::information(this, tr("Successfully Removed"),
+ tr("Successfully removed the installed base game."));
+ } else {
+ QMessageBox::warning(
+ this, tr("Error Removing %1").arg(entry_type),
+ tr("The base game is not installed in the NAND and cannot be removed."));
+ }
+}
+
+void GMainWindow::RemoveUpdateContent(u64 program_id, const QString& entry_type) {
+ const auto update_id = program_id | 0x800;
+ const auto& fs_controller = Core::System::GetInstance().GetFileSystemController();
+ const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) ||
+ fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id);
+
+ if (res) {
+ QMessageBox::information(this, tr("Successfully Removed"),
+ tr("Successfully removed the installed update."));
+ } else {
+ QMessageBox::warning(this, tr("Error Removing %1").arg(entry_type),
+ tr("There is no update installed for this title."));
+ }
+}
+
+void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type) {
+ u32 count{};
+ const auto& fs_controller = Core::System::GetInstance().GetFileSystemController();
+ const auto dlc_entries = Core::System::GetInstance().GetContentProvider().ListEntriesFilter(
+ FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
+
+ for (const auto& entry : dlc_entries) {
+ if ((entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id) {
+ const auto res =
+ fs_controller.GetUserNANDContents()->RemoveExistingEntry(entry.title_id) ||
+ fs_controller.GetSDMCContents()->RemoveExistingEntry(entry.title_id);
+ if (res) {
+ ++count;
+ }
+ }
+ }
+
+ if (count == 0) {
+ QMessageBox::warning(this, tr("Error Removing %1").arg(entry_type),
+ tr("There are no DLC installed for this title."));
+ return;
+ }
+
+ QMessageBox::information(this, tr("Successfully Removed"),
+ tr("Successfully removed %1 installed DLC.").arg(count));
+}
+
+void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target) {
+ const QString question = [this, target] {
+ switch (target) {
+ case GameListRemoveTarget::ShaderCache:
+ return tr("Delete Transferable Shader Cache?");
+ case GameListRemoveTarget::CustomConfiguration:
+ return tr("Remove Custom Game Configuration?");
+ default:
+ return QString{};
+ }
+ }();
+
+ if (QMessageBox::question(this, tr("Remove File"), question, QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No) != QMessageBox::Yes) {
+ return;
+ }
+
+ switch (target) {
+ case GameListRemoveTarget::ShaderCache:
+ RemoveTransferableShaderCache(program_id);
+ break;
+ case GameListRemoveTarget::CustomConfiguration:
+ RemoveCustomConfiguration(program_id);
+ break;
+ }
+}
+
+void GMainWindow::RemoveTransferableShaderCache(u64 program_id) {
+ const QString shader_dir =
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir));
+ const QString transferable_shader_cache_folder_path =
+ shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable");
+ const QString transferable_shader_cache_file_path =
+ transferable_shader_cache_folder_path + QDir::separator() +
+ QString::fromStdString(fmt::format("{:016X}.bin", program_id));
+
+ if (!QFile::exists(transferable_shader_cache_file_path)) {
+ QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"),
+ tr("A shader cache for this title does not exist."));
+ return;
+ }
+
+ if (QFile::remove(transferable_shader_cache_file_path)) {
+ QMessageBox::information(this, tr("Successfully Removed"),
+ tr("Successfully removed the transferable shader cache."));
+ } else {
+ QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"),
+ tr("Failed to remove the transferable shader cache."));
+ }
+}
+
+void GMainWindow::RemoveCustomConfiguration(u64 program_id) {
+ const QString config_dir =
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir));
+ const QString custom_config_file_path =
+ config_dir + QString::fromStdString(fmt::format("{:016X}.ini", program_id));
+
+ if (!QFile::exists(custom_config_file_path)) {
+ QMessageBox::warning(this, tr("Error Removing Custom Configuration"),
+ tr("A custom configuration for this title does not exist."));
+ return;
+ }
+
+ if (QFile::remove(custom_config_file_path)) {
+ QMessageBox::information(this, tr("Successfully Removed"),
+ tr("Successfully removed the custom game configuration."));
+ } else {
+ QMessageBox::warning(this, tr("Error Removing Custom Configuration"),
+ tr("Failed to remove the custom game configuration."));
+ }
+}
+
void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) {
const auto failed = [this] {
QMessageBox::warning(this, tr("RomFS Extraction Failed!"),
@@ -1422,7 +1625,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
}
const auto path = fmt::format(
- "{}{:016X}/romfs", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), *romfs_title_id);
+ "{}{:016X}/romfs", Common::FS::GetUserPath(Common::FS::UserPath::DumpDir), *romfs_title_id);
FileSys::VirtualFile romfs;
@@ -1502,13 +1705,13 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
QString path;
if (directory == QStringLiteral("SDMC")) {
- path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
+ path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir) +
"Nintendo/Contents/registered");
} else if (directory == QStringLiteral("UserNAND")) {
- path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
+ path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
"user/Contents/registered");
} else if (directory == QStringLiteral("SysNAND")) {
- path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
+ path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
"system/Contents/registered");
} else {
path = directory;
@@ -1522,8 +1725,10 @@ void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
void GMainWindow::OnGameListAddDirectory() {
const QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
- if (dir_path.isEmpty())
+ if (dir_path.isEmpty()) {
return;
+ }
+
UISettings::GameDir game_dir{dir_path, false, true};
if (!UISettings::values.game_dirs.contains(game_dir)) {
UISettings::values.game_dirs.append(game_dir);
@@ -1550,26 +1755,7 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
return;
}
- ConfigurePerGame dialog(this, title_id);
- dialog.LoadFromFile(v_file);
- auto result = dialog.exec();
- if (result == QDialog::Accepted) {
- dialog.ApplyConfiguration();
-
- const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
- if (reload) {
- game_list->PopulateAsync(UISettings::values.game_dirs);
- }
-
- // Do not cause the global config to write local settings into the config file
- Settings::RestoreGlobalState();
-
- if (!Core::System::GetInstance().IsPoweredOn()) {
- config->Save();
- }
- } else {
- Settings::RestoreGlobalState();
- }
+ OpenPerGameConfiguration(title_id, file);
}
void GMainWindow::OnMenuLoadFile() {
@@ -1655,7 +1841,7 @@ void GMainWindow::OnMenuInstallToNAND() {
ui.action_Install_File_NAND->setEnabled(false);
- install_progress = new QProgressDialog(QStringLiteral(""), tr("Cancel"), 0, total_size, this);
+ install_progress = new QProgressDialog(QString{}, tr("Cancel"), 0, total_size, this);
install_progress->setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint &
~Qt::WindowMaximizeButtonHint);
install_progress->setAttribute(Qt::WA_DeleteOnClose, true);
@@ -1705,18 +1891,18 @@ void GMainWindow::OnMenuInstallToNAND() {
install_progress->close();
const QString install_results =
- (new_files.isEmpty() ? QStringLiteral("")
+ (new_files.isEmpty() ? QString{}
: tr("%n file(s) were newly installed\n", "", new_files.size())) +
(overwritten_files.isEmpty()
- ? QStringLiteral("")
+ ? QString{}
: tr("%n file(s) were overwritten\n", "", overwritten_files.size())) +
- (failed_files.isEmpty() ? QStringLiteral("")
+ (failed_files.isEmpty() ? QString{}
: tr("%n file(s) failed to install\n", "", failed_files.size()));
QMessageBox::information(this, tr("Install Results"), install_results);
+ Common::FS::DeleteDirRecursively(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) +
+ DIR_SEP + "game_list");
game_list->PopulateAsync(UISettings::values.game_dirs);
- FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP +
- "game_list");
ui.action_Install_File_NAND->setEnabled(true);
}
@@ -1883,6 +2069,7 @@ void GMainWindow::OnStartGame() {
emu_thread->SetRunning(true);
+ qRegisterMetaType<Core::Frontend::ControllerParameters>("Core::Frontend::ControllerParameters");
qRegisterMetaType<Core::Frontend::SoftwareKeyboardParameters>(
"Core::Frontend::SoftwareKeyboardParameters");
qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus");
@@ -1898,6 +2085,7 @@ void GMainWindow::OnStartGame() {
ui.action_Pause->setEnabled(true);
ui.action_Stop->setEnabled(true);
ui.action_Restart->setEnabled(true);
+ ui.action_Configure_Current_Game->setEnabled(true);
ui.action_Report_Compatibility->setEnabled(true);
discord_rpc->Update();
@@ -2049,7 +2237,7 @@ void GMainWindow::OnConfigure() {
const auto old_theme = UISettings::values.theme;
const bool old_discord_presence = UISettings::values.enable_discord_presence;
- ConfigureDialog configure_dialog(this, hotkey_registry);
+ ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get());
connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this,
&GMainWindow::OnLanguageChanged);
@@ -2087,6 +2275,36 @@ void GMainWindow::OnConfigure() {
UpdateStatusButtons();
}
+void GMainWindow::OnConfigurePerGame() {
+ const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
+ OpenPerGameConfiguration(title_id, game_path.toStdString());
+}
+
+void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file_name) {
+ const auto v_file = Core::GetGameFileFromPath(vfs, file_name);
+
+ ConfigurePerGame dialog(this, title_id);
+ dialog.LoadFromFile(v_file);
+ auto result = dialog.exec();
+ if (result == QDialog::Accepted) {
+ dialog.ApplyConfiguration();
+
+ const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
+ if (reload) {
+ game_list->PopulateAsync(UISettings::values.game_dirs);
+ }
+
+ // Do not cause the global config to write local settings into the config file
+ Settings::RestoreGlobalState();
+
+ if (!Core::System::GetInstance().IsPoweredOn()) {
+ config->Save();
+ }
+ } else {
+ Settings::RestoreGlobalState();
+ }
+}
+
void GMainWindow::OnLoadAmiibo() {
const QString extensions{QStringLiteral("*.bin")};
const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions);
@@ -2134,7 +2352,7 @@ void GMainWindow::LoadAmiibo(const QString& filename) {
void GMainWindow::OnOpenYuzuFolder() {
QDesktopServices::openUrl(QUrl::fromLocalFile(
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::UserDir))));
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::UserDir))));
}
void GMainWindow::OnAbout() {
@@ -2153,17 +2371,28 @@ void GMainWindow::OnToggleFilterBar() {
void GMainWindow::OnCaptureScreenshot() {
OnPauseGame();
- QFileDialog png_dialog(this, tr("Capture Screenshot"), UISettings::values.screenshot_path,
- tr("PNG Image (*.png)"));
- png_dialog.setAcceptMode(QFileDialog::AcceptSave);
- png_dialog.setDefaultSuffix(QStringLiteral("png"));
- if (png_dialog.exec()) {
- const QString path = png_dialog.selectedFiles().first();
- if (!path.isEmpty()) {
- UISettings::values.screenshot_path = QFileInfo(path).path();
- render_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor, path);
+
+ const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
+ const auto screenshot_path =
+ QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir));
+ const auto date =
+ QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd_hh-mm-ss-zzz"));
+ QString filename = QStringLiteral("%1%2_%3.png")
+ .arg(screenshot_path)
+ .arg(title_id, 16, 16, QLatin1Char{'0'})
+ .arg(date);
+
+#ifdef _WIN32
+ if (UISettings::values.enable_screenshot_save_as) {
+ filename = QFileDialog::getSaveFileName(this, tr("Capture Screenshot"), filename,
+ tr("PNG Image (*.png)"));
+ if (filename.isEmpty()) {
+ OnStartGame();
+ return;
}
}
+#endif
+ render_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor, filename);
OnStartGame();
}
@@ -2202,8 +2431,7 @@ void GMainWindow::UpdateStatusBar() {
if (shaders_building != 0) {
shader_building_label->setText(
- tr("Building: %1 shader").arg(shaders_building) +
- (shaders_building != 1 ? QString::fromStdString("s") : QString::fromStdString("")));
+ tr("Building: %n shader(s)", "", static_cast<int>(shaders_building)));
shader_building_label->setVisible(true);
} else {
shader_building_label->setVisible(false);
@@ -2349,18 +2577,18 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
if (res == QMessageBox::Cancel)
return;
- FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) +
- "prod.keys_autogenerated");
- FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) +
- "console.keys_autogenerated");
- FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) +
- "title.keys_autogenerated");
+ Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) +
+ "prod.keys_autogenerated");
+ Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) +
+ "console.keys_autogenerated");
+ Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) +
+ "title.keys_autogenerated");
}
Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
if (keys.BaseDeriveNecessary()) {
Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory(
- FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), FileSys::Mode::Read)};
+ Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir), FileSys::Mode::Read)};
const auto function = [this, &keys, &pdm] {
keys.PopulateFromPartitionData(pdm);
@@ -2692,7 +2920,7 @@ int main(int argc, char* argv[]) {
// If you start a bundle (binary) on OSX without the Terminal, the working directory is "/".
// But since we require the working directory to be the executable path for the location of
// the user folder in the Qt Frontend, we need to cd into that working directory
- const std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
+ const std::string bin_path = Common::FS::GetBundleDirectory() + DIR_SEP + "..";
chdir(bin_path.c_str());
#endif
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index db573d606..afcfa68a9 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -32,18 +32,29 @@ class QPushButton;
class QProgressDialog;
class WaitTreeWidget;
enum class GameListOpenTarget;
+enum class GameListRemoveTarget;
+enum class InstalledEntryType;
class GameListPlaceholder;
namespace Core::Frontend {
+struct ControllerParameters;
struct SoftwareKeyboardParameters;
} // namespace Core::Frontend
+namespace DiscordRPC {
+class DiscordInterface;
+}
+
namespace FileSys {
class ContentProvider;
class ManualContentProvider;
class VfsFilesystem;
} // namespace FileSys
+namespace InputCommon {
+class InputSubsystem;
+}
+
enum class EmulatedDirectoryTarget {
NAND,
SDMC,
@@ -60,10 +71,6 @@ enum class ReinitializeKeyBehavior {
Warning,
};
-namespace DiscordRPC {
-class DiscordInterface;
-}
-
class GMainWindow : public QMainWindow {
Q_OBJECT
@@ -84,8 +91,6 @@ public:
GMainWindow();
~GMainWindow() override;
- std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
-
bool DropAction(QDropEvent* event);
void AcceptDropEvent(QDropEvent* event);
@@ -112,9 +117,12 @@ signals:
void UpdateInstallProgress();
+ void ControllerSelectorReconfigureFinished();
+
void ErrorDisplayFinished();
void ProfileSelectorFinishedSelection(std::optional<Common::UUID> uuid);
+
void SoftwareKeyboardFinishedText(std::optional<std::u16string> text);
void SoftwareKeyboardFinishedCheckDialog();
@@ -123,6 +131,8 @@ signals:
public slots:
void OnLoadComplete();
+ void ControllerSelectorReconfigureControllers(
+ const Core::Frontend::ControllerParameters& parameters);
void ErrorDisplayDisplayError(QString body);
void ProfileSelectorSelectProfile();
void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);
@@ -196,8 +206,11 @@ private slots:
void OnOpenFAQ();
/// Called whenever a user selects a game in the game list widget.
void OnGameListLoadFile(QString game_path);
- void OnGameListOpenFolder(GameListOpenTarget target, const std::string& game_path);
+ void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target,
+ const std::string& game_path);
void OnTransferableShaderCacheOpenFile(u64 program_id);
+ void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type);
+ void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target);
void OnGameListDumpRomFS(u64 program_id, const std::string& game_path);
void OnGameListCopyTID(u64 program_id);
void OnGameListNavigateToGamedbEntry(u64 program_id,
@@ -212,6 +225,7 @@ private slots:
void OnMenuInstallToNAND();
void OnMenuRecentFile();
void OnConfigure();
+ void OnConfigurePerGame();
void OnLoadAmiibo();
void OnOpenYuzuFolder();
void OnAbout();
@@ -229,6 +243,11 @@ private slots:
void OnLanguageChanged(const QString& locale);
private:
+ void RemoveBaseContent(u64 program_id, const QString& entry_type);
+ void RemoveUpdateContent(u64 program_id, const QString& entry_type);
+ void RemoveAddOnContent(u64 program_id, const QString& entry_type);
+ void RemoveTransferableShaderCache(u64 program_id);
+ void RemoveCustomConfiguration(u64 program_id);
std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
InstallResult InstallNSPXCI(const QString& filename);
InstallResult InstallNCA(const QString& filename);
@@ -240,9 +259,13 @@ private:
void ShowMouseCursor();
void OpenURL(const QUrl& url);
void LoadTranslation();
+ void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
Ui::MainWindow ui;
+ std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
+ std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
+
GRenderWindow* render_window;
GameList* game_list;
LoadingScreen* loading_screen;
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index c3a1d715e..2f3792247 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -81,6 +81,7 @@
<addaction name="action_Restart"/>
<addaction name="separator"/>
<addaction name="action_Configure"/>
+ <addaction name="action_Configure_Current_Game"/>
</widget>
<widget class="QMenu" name="menu_View">
<property name="title">
@@ -287,6 +288,14 @@
<string>Capture Screenshot</string>
</property>
</action>
+ <action name="action_Configure_Current_Game">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Configure Current Game...</string>
+ </property>
+ </action>
</widget>
<resources/>
<connections/>
diff --git a/src/yuzu/uisettings.cpp b/src/yuzu/uisettings.cpp
index a51175f36..37499fc85 100644
--- a/src/yuzu/uisettings.cpp
+++ b/src/yuzu/uisettings.cpp
@@ -16,4 +16,5 @@ const Themes themes{{
}};
Values values = {};
+
} // namespace UISettings
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index ac7b9aef6..ce3945485 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -29,14 +29,14 @@ extern const Themes themes;
struct GameDir {
QString path;
- bool deep_scan;
- bool expanded;
+ bool deep_scan = false;
+ bool expanded = false;
bool operator==(const GameDir& rhs) const {
return path == rhs.path;
- };
+ }
bool operator!=(const GameDir& rhs) const {
return !operator==(rhs);
- };
+ }
};
struct Values {
@@ -66,11 +66,11 @@ struct Values {
// Discord RPC
bool enable_discord_presence;
+ bool enable_screenshot_save_as;
u16 screenshot_resolution_factor;
QString roms_path;
QString symbols_path;
- QString screenshot_path;
QString game_dir_deprecated;
bool game_dir_deprecated_deepscan;
QVector<UISettings::GameDir> game_dirs;
@@ -87,9 +87,6 @@ struct Values {
// logging
bool show_console;
- // Controllers
- int profile_index;
-
// Game List
bool show_add_ons;
uint32_t icon_size;
@@ -100,6 +97,7 @@ struct Values {
};
extern Values values;
+
} // namespace UISettings
Q_DECLARE_METATYPE(UISettings::GameDir*);
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index c2a2982fb..e9f1c6500 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -16,9 +16,11 @@
#include "yuzu_cmd/config.h"
#include "yuzu_cmd/default_ini.h"
+namespace FS = Common::FS;
+
Config::Config() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
- sdl2_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "sdl2-config.ini";
+ sdl2_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + "sdl2-config.ini";
sdl2_config = std::make_unique<INIReader>(sdl2_config_loc);
Reload();
@@ -31,8 +33,8 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) {
if (sdl2_config->ParseError() < 0) {
if (retry) {
LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location);
- FileUtil::CreateFullPath(location);
- FileUtil::WriteStringToFile(true, location, default_contents);
+ FS::CreateFullPath(location);
+ FS::WriteStringToFile(true, location, default_contents);
sdl2_config = std::make_unique<INIReader>(location); // Reopen file
return LoadINI(default_contents, false);
@@ -286,6 +288,8 @@ void Config::ReadValues() {
Settings::values.debug_pad_analogs[i] = default_param;
}
+ Settings::values.vibration_enabled =
+ sdl2_config->GetBoolean("ControlsGeneral", "vibration_enabled", true);
Settings::values.touchscreen.enabled =
sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true);
Settings::values.touchscreen.device =
@@ -315,21 +319,21 @@ void Config::ReadValues() {
// Data Storage
Settings::values.use_virtual_sd =
sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
- FileUtil::GetUserPath(FileUtil::UserPath::NANDDir,
- sdl2_config->Get("Data Storage", "nand_directory",
- FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
- FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir,
- sdl2_config->Get("Data Storage", "sdmc_directory",
- FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
- FileUtil::GetUserPath(FileUtil::UserPath::LoadDir,
- sdl2_config->Get("Data Storage", "load_directory",
- FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)));
- FileUtil::GetUserPath(FileUtil::UserPath::DumpDir,
- sdl2_config->Get("Data Storage", "dump_directory",
- FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)));
- FileUtil::GetUserPath(FileUtil::UserPath::CacheDir,
- sdl2_config->Get("Data Storage", "cache_directory",
- FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)));
+ FS::GetUserPath(
+ FS::UserPath::NANDDir,
+ sdl2_config->Get("Data Storage", "nand_directory", FS::GetUserPath(FS::UserPath::NANDDir)));
+ FS::GetUserPath(
+ FS::UserPath::SDMCDir,
+ sdl2_config->Get("Data Storage", "sdmc_directory", FS::GetUserPath(FS::UserPath::SDMCDir)));
+ FS::GetUserPath(
+ FS::UserPath::LoadDir,
+ sdl2_config->Get("Data Storage", "load_directory", FS::GetUserPath(FS::UserPath::LoadDir)));
+ FS::GetUserPath(
+ FS::UserPath::DumpDir,
+ sdl2_config->Get("Data Storage", "dump_directory", FS::GetUserPath(FS::UserPath::DumpDir)));
+ FS::GetUserPath(FS::UserPath::CacheDir,
+ sdl2_config->Get("Data Storage", "cache_directory",
+ FS::GetUserPath(FS::UserPath::CacheDir)));
Settings::values.gamecard_inserted =
sdl2_config->GetBoolean("Data Storage", "gamecard_inserted", false);
Settings::values.gamecard_current_game =
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index e5e684206..a804d5185 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -13,23 +13,25 @@
#include "input_common/sdl/sdl.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
-EmuWindow_SDL2::EmuWindow_SDL2(Core::System& system, bool fullscreen) : system{system} {
+EmuWindow_SDL2::EmuWindow_SDL2(Core::System& system, bool fullscreen,
+ InputCommon::InputSubsystem* input_subsystem_)
+ : system{system}, input_subsystem{input_subsystem_} {
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) {
LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting...");
exit(1);
}
- InputCommon::Init();
+ input_subsystem->Initialize();
SDL_SetMainReady();
}
EmuWindow_SDL2::~EmuWindow_SDL2() {
- InputCommon::Shutdown();
+ input_subsystem->Shutdown();
SDL_Quit();
}
void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
- InputCommon::GetMotionEmu()->Tilt(x, y);
+ input_subsystem->GetMotionEmu()->Tilt(x, y);
}
void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
@@ -41,9 +43,9 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
}
} else if (button == SDL_BUTTON_RIGHT) {
if (state == SDL_PRESSED) {
- InputCommon::GetMotionEmu()->BeginTilt(x, y);
+ input_subsystem->GetMotionEmu()->BeginTilt(x, y);
} else {
- InputCommon::GetMotionEmu()->EndTilt();
+ input_subsystem->GetMotionEmu()->EndTilt();
}
}
}
@@ -79,9 +81,9 @@ void EmuWindow_SDL2::OnFingerUp() {
void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
if (state == SDL_PRESSED) {
- InputCommon::GetKeyboard()->PressKey(key);
+ input_subsystem->GetKeyboard()->PressKey(key);
} else if (state == SDL_RELEASED) {
- InputCommon::GetKeyboard()->ReleaseKey(key);
+ input_subsystem->GetKeyboard()->ReleaseKey(key);
}
}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
index fffac4252..82750ffec 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
@@ -14,9 +14,14 @@ namespace Core {
class System;
}
+namespace InputCommon {
+class InputSubsystem;
+}
+
class EmuWindow_SDL2 : public Core::Frontend::EmuWindow {
public:
- explicit EmuWindow_SDL2(Core::System& system, bool fullscreen);
+ explicit EmuWindow_SDL2(Core::System& system, bool fullscreen,
+ InputCommon::InputSubsystem* input_subsystem);
~EmuWindow_SDL2();
/// Polls window events
@@ -76,4 +81,7 @@ protected:
/// Keeps track of how often to update the title bar during gameplay
u32 last_time = 0;
+
+ /// Input subsystem to use with this window.
+ InputCommon::InputSubsystem* input_subsystem;
};
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
index e78025737..881b67a76 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
@@ -87,8 +87,9 @@ bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
return unsupported_ext.empty();
}
-EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system, bool fullscreen)
- : EmuWindow_SDL2{system, fullscreen} {
+EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system, bool fullscreen,
+ InputCommon::InputSubsystem* input_subsystem)
+ : EmuWindow_SDL2{system, fullscreen, input_subsystem} {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
index 48bb41683..732a64edd 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
@@ -8,9 +8,14 @@
#include "core/frontend/emu_window.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
+namespace InputCommon {
+class InputSubsystem;
+}
+
class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 {
public:
- explicit EmuWindow_SDL2_GL(Core::System& system, bool fullscreen);
+ explicit EmuWindow_SDL2_GL(Core::System& system, bool fullscreen,
+ InputCommon::InputSubsystem* input_subsystem);
~EmuWindow_SDL2_GL();
void Present() override;
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
index cb8e68a39..53491f86e 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
@@ -19,8 +19,9 @@
#include <SDL.h>
#include <SDL_syswm.h>
-EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen)
- : EmuWindow_SDL2{system, fullscreen} {
+EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen,
+ InputCommon::InputSubsystem* input_subsystem)
+ : EmuWindow_SDL2{system, fullscreen, input_subsystem} {
const std::string window_title = fmt::format("yuzu {} | {}-{} (Vulkan)", Common::g_build_name,
Common::g_scm_branch, Common::g_scm_desc);
render_window =
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
index 77a6ca72b..f99704d4c 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
@@ -13,9 +13,14 @@ namespace Core {
class System;
}
+namespace InputCommon {
+class InputSubsystem;
+}
+
class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 {
public:
- explicit EmuWindow_SDL2_VK(Core::System& system, bool fullscreen);
+ explicit EmuWindow_SDL2_VK(Core::System& system, bool fullscreen,
+ InputCommon::InputSubsystem* input_subsystem);
~EmuWindow_SDL2_VK();
void Present() override;
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 512b060a7..e960b5413 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -23,12 +23,15 @@
#include "common/telemetry.h"
#include "core/core.h"
#include "core/crypto/key_manager.h"
+#include "core/file_sys/registered_cache.h"
#include "core/file_sys/vfs_real.h"
#include "core/gdbstub/gdbstub.h"
+#include "core/hle/kernel/process.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#include "core/settings.h"
#include "core/telemetry_session.h"
+#include "input_common/main.h"
#include "video_core/renderer_base.h"
#include "yuzu_cmd/config.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
@@ -37,8 +40,6 @@
#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h"
#endif
-#include "core/file_sys/registered_cache.h"
-
#ifdef _WIN32
// windows.h needs to be included before shellapi.h
#include <windows.h>
@@ -82,8 +83,8 @@ static void InitializeLogging() {
Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
- const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
- FileUtil::CreateFullPath(log_dir);
+ const std::string& log_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir);
+ Common::FS::CreateFullPath(log_dir);
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
#ifdef _WIN32
Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
@@ -179,15 +180,16 @@ int main(int argc, char** argv) {
Settings::Apply();
Core::System& system{Core::System::GetInstance()};
+ InputCommon::InputSubsystem input_subsystem;
std::unique_ptr<EmuWindow_SDL2> emu_window;
switch (Settings::values.renderer_backend.GetValue()) {
case Settings::RendererBackend::OpenGL:
- emu_window = std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen);
+ emu_window = std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen, &input_subsystem);
break;
case Settings::RendererBackend::Vulkan:
#ifdef HAS_VULKAN
- emu_window = std::make_unique<EmuWindow_SDL2_VK>(system, fullscreen);
+ emu_window = std::make_unique<EmuWindow_SDL2_VK>(system, fullscreen, &input_subsystem);
break;
#else
LOG_CRITICAL(Frontend, "Vulkan backend has not been compiled!");
@@ -229,12 +231,14 @@ int main(int argc, char** argv) {
}
}
- system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "SDL");
+ system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "SDL");
// Core is loaded, start the GPU (makes the GPU contexts current to this thread)
system.GPU().Start();
- system.Renderer().Rasterizer().LoadDiskResources();
+ system.Renderer().Rasterizer().LoadDiskResources(
+ system.CurrentProcess()->GetTitleID(), false,
+ [](VideoCore::LoadCallbackStage, size_t value, size_t total) {});
std::thread render_thread([&emu_window] { emu_window->Present(); });
system.Run();
diff --git a/src/yuzu_tester/config.cpp b/src/yuzu_tester/config.cpp
index acb22885e..aaf59129a 100644
--- a/src/yuzu_tester/config.cpp
+++ b/src/yuzu_tester/config.cpp
@@ -15,10 +15,11 @@
#include "yuzu_tester/config.h"
#include "yuzu_tester/default_ini.h"
+namespace FS = Common::FS;
+
Config::Config() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
- sdl2_config_loc =
- FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "sdl2-tester-config.ini";
+ sdl2_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + "sdl2-tester-config.ini";
sdl2_config = std::make_unique<INIReader>(sdl2_config_loc);
Reload();
@@ -31,8 +32,8 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) {
if (sdl2_config->ParseError() < 0) {
if (retry) {
LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location);
- FileUtil::CreateFullPath(location);
- FileUtil::WriteStringToFile(true, default_contents, location);
+ FS::CreateFullPath(location);
+ FS::WriteStringToFile(true, default_contents, location);
sdl2_config = std::make_unique<INIReader>(location); // Reopen file
return LoadINI(default_contents, false);
@@ -74,6 +75,7 @@ void Config::ReadValues() {
Settings::values.debug_pad_analogs[i] = "";
}
+ Settings::values.vibration_enabled = true;
Settings::values.touchscreen.enabled = "";
Settings::values.touchscreen.device = "";
Settings::values.touchscreen.finger = 0;
@@ -87,12 +89,12 @@ void Config::ReadValues() {
// Data Storage
Settings::values.use_virtual_sd =
sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
- FileUtil::GetUserPath(FileUtil::UserPath::NANDDir,
- sdl2_config->Get("Data Storage", "nand_directory",
- FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
- FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir,
- sdl2_config->Get("Data Storage", "sdmc_directory",
- FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
+ FS::GetUserPath(Common::FS::UserPath::NANDDir,
+ sdl2_config->Get("Data Storage", "nand_directory",
+ Common::FS::GetUserPath(Common::FS::UserPath::NANDDir)));
+ FS::GetUserPath(Common::FS::UserPath::SDMCDir,
+ sdl2_config->Get("Data Storage", "sdmc_directory",
+ Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)));
// System
Settings::values.current_user = std::clamp<int>(
diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
index 8584f6671..78f75fb38 100644
--- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
+++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
@@ -13,7 +13,6 @@
#include <glad/glad.h>
-#include "common/assert.h"
#include "common/logging/log.h"
#include "common/scm_rev.h"
#include "core/settings.h"
@@ -53,7 +52,7 @@ EmuWindow_SDL2_Hide::EmuWindow_SDL2_Hide() {
exit(1);
}
- InputCommon::Init();
+ input_subsystem->Initialize();
SDL_SetMainReady();
@@ -105,7 +104,7 @@ EmuWindow_SDL2_Hide::EmuWindow_SDL2_Hide() {
}
EmuWindow_SDL2_Hide::~EmuWindow_SDL2_Hide() {
- InputCommon::Shutdown();
+ input_subsystem->Shutdown();
SDL_GL_DeleteContext(gl_context);
SDL_Quit();
}
diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
index c13a82df2..a553b4b95 100644
--- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
+++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
@@ -8,6 +8,10 @@
struct SDL_Window;
+namespace InputCommon {
+class InputSubsystem;
+}
+
class EmuWindow_SDL2_Hide : public Core::Frontend::EmuWindow {
public:
explicit EmuWindow_SDL2_Hide();
@@ -25,6 +29,8 @@ private:
/// Whether the GPU and driver supports the OpenGL extension required
bool SupportsRequiredGLExtensions();
+ std::unique_ptr<InputCommon::InputSubsystem> input_subsystem;
+
/// Internal SDL2 render window
SDL_Window* render_window;
diff --git a/src/yuzu_tester/yuzu.cpp b/src/yuzu_tester/yuzu.cpp
index 083667baf..5798ce43a 100644
--- a/src/yuzu_tester/yuzu.cpp
+++ b/src/yuzu_tester/yuzu.cpp
@@ -79,8 +79,8 @@ static void InitializeLogging(bool console) {
if (console)
Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
- const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
- FileUtil::CreateFullPath(log_dir);
+ const std::string& log_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir);
+ Common::FS::CreateFullPath(log_dir);
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
#ifdef _WIN32
Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
@@ -251,10 +251,10 @@ int main(int argc, char** argv) {
Service::Yuzu::InstallInterfaces(system.ServiceManager(), datastring, callback);
- system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "SDLHideTester");
+ system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend",
+ "SDLHideTester");
system.GPU().Start();
- system.Renderer().Rasterizer().LoadDiskResources();
system.Run();
while (!finished) {