summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/CMakeLists.txt121
-rw-r--r--src/common/bit_set.h2
-rw-r--r--src/common/chunk_file.h5
-rw-r--r--src/common/color.h2
-rw-r--r--src/common/file_util.cpp17
-rw-r--r--src/common/file_util.h4
-rw-r--r--src/common/logging/backend.cpp28
-rw-r--r--src/common/logging/backend.h2
-rw-r--r--src/common/logging/filter.cpp2
-rw-r--r--src/common/logging/filter.h2
-rw-r--r--src/common/logging/log.h28
-rw-r--r--src/common/logging/text_formatter.cpp2
-rw-r--r--src/common/logging/text_formatter.h2
-rw-r--r--src/common/memory_util.cpp7
-rw-r--r--src/common/platform.h34
-rw-r--r--src/common/quaternion.h2
-rw-r--r--src/common/scm_rev.h2
-rw-r--r--src/common/scope_exit.h2
-rw-r--r--src/common/string_util.cpp4
-rw-r--r--src/common/string_util.h2
-rw-r--r--src/common/telemetry.h4
-rw-r--r--src/common/thread_queue_list.h2
-rw-r--r--src/common/x64/cpu_detect.cpp2
-rw-r--r--src/common/x64/xbyak_abi.h69
-rw-r--r--src/core/CMakeLists.txt439
-rw-r--r--src/core/arm/arm_interface.h35
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.cpp146
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.h9
-rw-r--r--src/core/arm/unicorn/arm_unicorn.cpp16
-rw-r--r--src/core/arm/unicorn/arm_unicorn.h5
-rw-r--r--src/core/core.cpp58
-rw-r--r--src/core/core.h45
-rw-r--r--src/core/core_timing.cpp4
-rw-r--r--src/core/file_sys/directory.h55
-rw-r--r--src/core/file_sys/directory_backend.h58
-rw-r--r--src/core/file_sys/disk_archive.cpp99
-rw-r--r--src/core/file_sys/disk_archive.h68
-rw-r--r--src/core/file_sys/disk_filesystem.cpp228
-rw-r--r--src/core/file_sys/disk_filesystem.h85
-rw-r--r--src/core/file_sys/errors.h25
-rw-r--r--src/core/file_sys/filesystem.cpp (renamed from src/core/file_sys/archive_backend.cpp)6
-rw-r--r--src/core/file_sys/filesystem.h (renamed from src/core/file_sys/archive_backend.h)84
-rw-r--r--src/core/file_sys/ivfc_archive.cpp110
-rw-r--r--src/core/file_sys/path_parser.h2
-rw-r--r--src/core/file_sys/program_metadata.cpp114
-rw-r--r--src/core/file_sys/program_metadata.h154
-rw-r--r--src/core/file_sys/romfs_factory.cpp38
-rw-r--r--src/core/file_sys/romfs_factory.h35
-rw-r--r--src/core/file_sys/romfs_filesystem.cpp113
-rw-r--r--src/core/file_sys/romfs_filesystem.h (renamed from src/core/file_sys/ivfc_archive.h)46
-rw-r--r--src/core/file_sys/savedata_archive.cpp330
-rw-r--r--src/core/file_sys/savedata_archive.h43
-rw-r--r--src/core/file_sys/savedata_factory.cpp57
-rw-r--r--src/core/file_sys/savedata_factory.h33
-rw-r--r--src/core/file_sys/sdmc_factory.cpp40
-rw-r--r--src/core/file_sys/sdmc_factory.h31
-rw-r--r--src/core/file_sys/storage.h (renamed from src/core/file_sys/file_backend.h)27
-rw-r--r--src/core/file_sys/title_metadata.cpp163
-rw-r--r--src/core/file_sys/title_metadata.h125
-rw-r--r--src/core/gdbstub/gdbstub.cpp296
-rw-r--r--src/core/gdbstub/gdbstub.h2
-rw-r--r--src/core/hle/config_mem.cpp2
-rw-r--r--src/core/hle/config_mem.h2
-rw-r--r--src/core/hle/ipc.h15
-rw-r--r--src/core/hle/ipc_helpers.h134
-rw-r--r--src/core/hle/kernel/address_arbiter.cpp91
-rw-r--r--src/core/hle/kernel/address_arbiter.h60
-rw-r--r--src/core/hle/kernel/client_port.cpp2
-rw-r--r--src/core/hle/kernel/client_port.h2
-rw-r--r--src/core/hle/kernel/client_session.cpp2
-rw-r--r--src/core/hle/kernel/client_session.h8
-rw-r--r--src/core/hle/kernel/condition_variable.cpp5
-rw-r--r--src/core/hle/kernel/condition_variable.h6
-rw-r--r--src/core/hle/kernel/domain.cpp44
-rw-r--r--src/core/hle/kernel/domain.h45
-rw-r--r--src/core/hle/kernel/event.cpp2
-rw-r--r--src/core/hle/kernel/event.h2
-rw-r--r--src/core/hle/kernel/handle_table.cpp15
-rw-r--r--src/core/hle/kernel/handle_table.h9
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp208
-rw-r--r--src/core/hle/kernel/hle_ipc.h82
-rw-r--r--src/core/hle/kernel/kernel.cpp1
-rw-r--r--src/core/hle/kernel/kernel.h20
-rw-r--r--src/core/hle/kernel/mutex.cpp1
-rw-r--r--src/core/hle/kernel/object_address_table.cpp4
-rw-r--r--src/core/hle/kernel/process.cpp27
-rw-r--r--src/core/hle/kernel/process.h8
-rw-r--r--src/core/hle/kernel/resource_limit.cpp2
-rw-r--r--src/core/hle/kernel/resource_limit.h2
-rw-r--r--src/core/hle/kernel/scheduler.cpp135
-rw-r--r--src/core/hle/kernel/scheduler.h73
-rw-r--r--src/core/hle/kernel/server_port.cpp2
-rw-r--r--src/core/hle/kernel/server_port.h2
-rw-r--r--src/core/hle/kernel/server_session.cpp98
-rw-r--r--src/core/hle/kernel/server_session.h34
-rw-r--r--src/core/hle/kernel/session.cpp2
-rw-r--r--src/core/hle/kernel/session.h2
-rw-r--r--src/core/hle/kernel/shared_memory.cpp36
-rw-r--r--src/core/hle/kernel/shared_memory.h14
-rw-r--r--src/core/hle/kernel/svc.cpp161
-rw-r--r--src/core/hle/kernel/svc.h4
-rw-r--r--src/core/hle/kernel/svc_wrap.h29
-rw-r--r--src/core/hle/kernel/sync_object.h35
-rw-r--r--src/core/hle/kernel/thread.cpp236
-rw-r--r--src/core/hle/kernel/thread.h30
-rw-r--r--src/core/hle/kernel/timer.cpp2
-rw-r--r--src/core/hle/kernel/timer.h2
-rw-r--r--src/core/hle/kernel/vm_manager.cpp55
-rw-r--r--src/core/hle/kernel/vm_manager.h41
-rw-r--r--src/core/hle/kernel/wait_object.cpp3
-rw-r--r--src/core/hle/result.h9
-rw-r--r--src/core/hle/service/acc/acc_u0.cpp102
-rw-r--r--src/core/hle/service/acc/acc_u0.h18
-rw-r--r--src/core/hle/service/am/am.cpp483
-rw-r--r--src/core/hle/service/am/am.h115
-rw-r--r--src/core/hle/service/am/applet_ae.cpp112
-rw-r--r--src/core/hle/service/am/applet_ae.h30
-rw-r--r--src/core/hle/service/am/applet_oe.cpp296
-rw-r--r--src/core/hle/service/am/applet_oe.h9
-rw-r--r--src/core/hle/service/aoc/aoc_u.cpp32
-rw-r--r--src/core/hle/service/aoc/aoc_u.h4
-rw-r--r--src/core/hle/service/apm/apm.cpp54
-rw-r--r--src/core/hle/service/apm/apm.h9
-rw-r--r--src/core/hle/service/apm/interface.cpp66
-rw-r--r--src/core/hle/service/apm/interface.h27
-rw-r--r--src/core/hle/service/audio/audin_u.cpp41
-rw-r--r--src/core/hle/service/audio/audin_u.h23
-rw-r--r--src/core/hle/service/audio/audio.cpp8
-rw-r--r--src/core/hle/service/audio/audout_u.cpp182
-rw-r--r--src/core/hle/service/audio/audout_u.h20
-rw-r--r--src/core/hle/service/audio/audrec_u.cpp38
-rw-r--r--src/core/hle/service/audio/audrec_u.h23
-rw-r--r--src/core/hle/service/audio/audren_u.cpp260
-rw-r--r--src/core/hle/service/audio/audren_u.h28
-rw-r--r--src/core/hle/service/audio/codecctl.cpp33
-rw-r--r--src/core/hle/service/audio/codecctl.h23
-rw-r--r--src/core/hle/service/fatal/fatal.cpp38
-rw-r--r--src/core/hle/service/fatal/fatal.h29
-rw-r--r--src/core/hle/service/fatal/fatal_p.cpp14
-rw-r--r--src/core/hle/service/fatal/fatal_p.h18
-rw-r--r--src/core/hle/service/fatal/fatal_u.cpp19
-rw-r--r--src/core/hle/service/fatal/fatal_u.h18
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp79
-rw-r--r--src/core/hle/service/filesystem/filesystem.h59
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp464
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.h37
-rw-r--r--src/core/hle/service/friend/friend.cpp28
-rw-r--r--src/core/hle/service/friend/friend.h29
-rw-r--r--src/core/hle/service/friend/friend_a.cpp19
-rw-r--r--src/core/hle/service/friend/friend_a.h18
-rw-r--r--src/core/hle/service/hid/hid.cpp237
-rw-r--r--src/core/hle/service/lm/lm.cpp95
-rw-r--r--src/core/hle/service/lm/lm.h3
-rw-r--r--src/core/hle/service/nfp/nfp.cpp28
-rw-r--r--src/core/hle/service/nfp/nfp.h28
-rw-r--r--src/core/hle/service/nfp/nfp_user.cpp19
-rw-r--r--src/core/hle/service/nfp/nfp_user.h18
-rw-r--r--src/core/hle/service/nifm/nifm.cpp213
-rw-r--r--src/core/hle/service/nifm/nifm.h29
-rw-r--r--src/core/hle/service/nifm/nifm_a.cpp19
-rw-r--r--src/core/hle/service/nifm/nifm_a.h18
-rw-r--r--src/core/hle/service/nifm/nifm_s.cpp19
-rw-r--r--src/core/hle/service/nifm/nifm_s.h18
-rw-r--r--src/core/hle/service/nifm/nifm_u.cpp19
-rw-r--r--src/core/hle/service/nifm/nifm_u.h18
-rw-r--r--src/core/hle/service/ns/ns.cpp16
-rw-r--r--src/core/hle/service/ns/ns.h16
-rw-r--r--src/core/hle/service/ns/pl_u.cpp122
-rw-r--r--src/core/hle/service/ns/pl_u.h34
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdevice.h12
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp17
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.h6
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp104
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h84
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp64
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.h60
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp127
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h130
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp147
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.h144
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.cpp56
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.h28
-rw-r--r--src/core/hle/service/nvdrv/interface.cpp75
-rw-r--r--src/core/hle/service/nvdrv/interface.h9
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.cpp28
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.h9
-rw-r--r--src/core/hle/service/nvdrv/nvmemp.cpp31
-rw-r--r--src/core/hle/service/nvdrv/nvmemp.h23
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue.cpp115
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue.h102
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp166
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.h84
-rw-r--r--src/core/hle/service/pctl/pctl_a.cpp4
-rw-r--r--src/core/hle/service/service.cpp37
-rw-r--r--src/core/hle/service/service.h4
-rw-r--r--src/core/hle/service/set/set.cpp44
-rw-r--r--src/core/hle/service/set/set.h22
-rw-r--r--src/core/hle/service/set/set_cal.cpp40
-rw-r--r--src/core/hle/service/set/set_cal.h19
-rw-r--r--src/core/hle/service/set/set_fd.cpp25
-rw-r--r--src/core/hle/service/set/set_fd.h19
-rw-r--r--src/core/hle/service/set/set_sys.cpp167
-rw-r--r--src/core/hle/service/set/set_sys.h22
-rw-r--r--src/core/hle/service/set/settings.cpp22
-rw-r--r--src/core/hle/service/set/settings.h16
-rw-r--r--src/core/hle/service/sm/controller.cpp35
-rw-r--r--src/core/hle/service/sm/controller.h1
-rw-r--r--src/core/hle/service/sm/sm.cpp10
-rw-r--r--src/core/hle/service/sockets/bsd.cpp90
-rw-r--r--src/core/hle/service/sockets/bsd.h31
-rw-r--r--src/core/hle/service/sockets/nsd.cpp34
-rw-r--r--src/core/hle/service/sockets/nsd.h20
-rw-r--r--src/core/hle/service/sockets/sfdnsres.cpp38
-rw-r--r--src/core/hle/service/sockets/sfdnsres.h23
-rw-r--r--src/core/hle/service/sockets/sockets.cpp22
-rw-r--r--src/core/hle/service/sockets/sockets.h16
-rw-r--r--src/core/hle/service/spl/csrng.cpp18
-rw-r--r--src/core/hle/service/spl/csrng.h18
-rw-r--r--src/core/hle/service/spl/module.cpp42
-rw-r--r--src/core/hle/service/spl/module.h29
-rw-r--r--src/core/hle/service/spl/spl.cpp41
-rw-r--r--src/core/hle/service/spl/spl.h18
-rw-r--r--src/core/hle/service/ssl/ssl.cpp17
-rw-r--r--src/core/hle/service/ssl/ssl.h22
-rw-r--r--src/core/hle/service/time/time.cpp152
-rw-r--r--src/core/hle/service/time/time.h54
-rw-r--r--src/core/hle/service/time/time_s.cpp48
-rw-r--r--src/core/hle/service/time/time_s.h11
-rw-r--r--src/core/hle/service/time/time_u.cpp22
-rw-r--r--src/core/hle/service/time/time_u.h18
-rw-r--r--src/core/hle/service/vi/vi.cpp727
-rw-r--r--src/core/hle/service/vi/vi.h149
-rw-r--r--src/core/hle/service/vi/vi_m.cpp14
-rw-r--r--src/core/hle/service/vi/vi_m.h13
-rw-r--r--src/core/hle/service/vi/vi_s.cpp20
-rw-r--r--src/core/hle/service/vi/vi_s.h18
-rw-r--r--src/core/hle/service/vi/vi_u.cpp20
-rw-r--r--src/core/hle/service/vi/vi_u.h18
-rw-r--r--src/core/hle/shared_page.cpp2
-rw-r--r--src/core/hle/shared_page.h2
-rw-r--r--src/core/hw/hw.cpp2
-rw-r--r--src/core/hw/hw.h2
-rw-r--r--src/core/hw/lcd.cpp2
-rw-r--r--src/core/hw/lcd.h2
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp183
-rw-r--r--src/core/loader/deconstructed_rom_directory.h48
-rw-r--r--src/core/loader/elf.cpp15
-rw-r--r--src/core/loader/elf.h8
-rw-r--r--src/core/loader/linker.cpp2
-rw-r--r--src/core/loader/loader.cpp22
-rw-r--r--src/core/loader/loader.h9
-rw-r--r--src/core/loader/nro.cpp49
-rw-r--r--src/core/loader/nro.h10
-rw-r--r--src/core/loader/nso.cpp48
-rw-r--r--src/core/loader/nso.h14
-rw-r--r--src/core/memory.cpp362
-rw-r--r--src/core/memory.h130
-rw-r--r--src/core/memory_hook.h46
-rw-r--r--src/core/memory_setup.h10
-rw-r--r--src/core/mmio.h38
-rw-r--r--src/core/settings.h13
-rw-r--r--src/core/telemetry_session.cpp15
-rw-r--r--src/core/tracer/citrace.h2
-rw-r--r--src/core/tracer/recorder.cpp2
-rw-r--r--src/core/tracer/recorder.h8
-rw-r--r--src/input_common/CMakeLists.txt31
-rw-r--r--src/input_common/main.cpp3
-rw-r--r--src/input_common/sdl/sdl.cpp2
-rw-r--r--src/tests/CMakeLists.txt26
-rw-r--r--src/tests/common/param_package.cpp4
-rw-r--r--src/tests/core/arm/arm_test_common.cpp36
-rw-r--r--src/tests/core/arm/arm_test_common.h22
-rw-r--r--src/video_core/CMakeLists.txt65
-rw-r--r--src/video_core/command_processor.cpp149
-rw-r--r--src/video_core/command_processor.h37
-rw-r--r--src/video_core/debug_utils/debug_utils.cpp64
-rw-r--r--src/video_core/debug_utils/debug_utils.h163
-rw-r--r--src/video_core/engines/fermi_2d.cpp13
-rw-r--r--src/video_core/engines/fermi_2d.h22
-rw-r--r--src/video_core/engines/maxwell_3d.cpp303
-rw-r--r--src/video_core/engines/maxwell_3d.h590
-rw-r--r--src/video_core/engines/maxwell_compute.cpp13
-rw-r--r--src/video_core/engines/maxwell_compute.h22
-rw-r--r--src/video_core/gpu.cpp25
-rw-r--r--src/video_core/gpu.h107
-rw-r--r--src/video_core/macro_interpreter.cpp257
-rw-r--r--src/video_core/macro_interpreter.h164
-rw-r--r--src/video_core/memory_manager.cpp110
-rw-r--r--src/video_core/memory_manager.h49
-rw-r--r--src/video_core/rasterizer_interface.h63
-rw-r--r--src/video_core/renderer_base.cpp7
-rw-r--r--src/video_core/renderer_base.h39
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp578
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h179
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp1432
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.h369
-rw-r--r--src/video_core/renderer_opengl/gl_resource_manager.h85
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp58
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.h27
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.cpp20
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.h66
-rw-r--r--src/video_core/renderer_opengl/gl_shader_util.cpp156
-rw-r--r--src/video_core/renderer_opengl/gl_shader_util.h8
-rw-r--r--src/video_core/renderer_opengl/gl_state.cpp125
-rw-r--r--src/video_core/renderer_opengl/gl_state.h35
-rw-r--r--src/video_core/renderer_opengl/gl_stream_buffer.cpp182
-rw-r--r--src/video_core/renderer_opengl/gl_stream_buffer.h34
-rw-r--r--src/video_core/renderer_opengl/maxwell_to_gl.h50
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp251
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h16
-rw-r--r--src/video_core/textures/decoders.cpp105
-rw-r--r--src/video_core/textures/decoders.h26
-rw-r--r--src/video_core/textures/texture.h137
-rw-r--r--src/video_core/utils.h114
-rw-r--r--src/video_core/video_core.cpp4
-rw-r--r--src/video_core/video_core.h4
-rw-r--r--src/yuzu/CMakeLists.txt136
-rw-r--r--src/yuzu/about_dialog.cpp2
-rw-r--r--src/yuzu/bootmanager.cpp21
-rw-r--r--src/yuzu/bootmanager.h10
-rw-r--r--src/yuzu/configuration/config.cpp27
-rw-r--r--src/yuzu/configuration/configure.ui2
-rw-r--r--src/yuzu/configuration/configure_general.cpp18
-rw-r--r--src/yuzu/configuration/configure_general.ui104
-rw-r--r--src/yuzu/configuration/configure_input.cpp19
-rw-r--r--src/yuzu/configuration/configure_input.ui132
-rw-r--r--src/yuzu/configuration/configure_system.cpp16
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp27
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoint_observer.h33
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoints.cpp212
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoints.h46
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoints_p.h36
-rw-r--r--src/yuzu/debugger/graphics/graphics_surface.cpp451
-rw-r--r--src/yuzu/debugger/graphics/graphics_surface.h97
-rw-r--r--src/yuzu/debugger/profiler.cpp5
-rw-r--r--src/yuzu/debugger/wait_tree.cpp11
-rw-r--r--src/yuzu/debugger/wait_tree.h2
-rw-r--r--src/yuzu/game_list.cpp9
-rw-r--r--src/yuzu/game_list.h2
-rw-r--r--src/yuzu/hotkeys.cpp1
-rw-r--r--src/yuzu/main.cpp98
-rw-r--r--src/yuzu/main.h26
-rw-r--r--src/yuzu/ui_settings.h6
-rw-r--r--src/yuzu/util/spinbox.cpp2
-rw-r--r--src/yuzu_cmd/CMakeLists.txt25
-rw-r--r--src/yuzu_cmd/config.cpp18
-rw-r--r--src/yuzu_cmd/default_ini.h16
-rw-r--r--src/yuzu_cmd/yuzu.rc2
348 files changed, 17452 insertions, 4775 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 26cf9480b..581c46b52 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -17,85 +17,78 @@ if ($ENV{CI})
string(SUBSTRING ${WORD} 1 -1 REMAINDER)
string(TOUPPER ${FIRST_LETTER} FIRST_LETTER)
# this leaves a trailing space on the last word, but we actually want that
- # because of how its styled in the title bar.
+ # because of how it's styled in the title bar.
set(REPO_NAME "${REPO_NAME}${FIRST_LETTER}${REMAINDER} ")
endforeach()
endif()
endif()
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp" @ONLY)
-set(SRCS
- break_points.cpp
- file_util.cpp
- hash.cpp
- logging/filter.cpp
- logging/text_formatter.cpp
- logging/backend.cpp
- memory_util.cpp
- microprofile.cpp
- misc.cpp
- param_package.cpp
- scm_rev.cpp
- string_util.cpp
- telemetry.cpp
- thread.cpp
- timer.cpp
- )
-
-set(HEADERS
- alignment.h
- assert.h
- bit_field.h
- bit_set.h
- break_points.h
- chunk_file.h
- code_block.h
- color.h
- common_funcs.h
- common_paths.h
- common_types.h
- file_util.h
- hash.h
- linear_disk_cache.h
- logging/text_formatter.h
- logging/filter.h
- logging/log.h
- logging/backend.h
- math_util.h
- memory_util.h
- microprofile.h
- microprofileui.h
- param_package.h
- platform.h
- quaternion.h
- scm_rev.h
- scope_exit.h
- string_util.h
- swap.h
- synchronized_wrapper.h
- telemetry.h
- thread.h
- thread_queue_list.h
- threadsafe_queue.h
- timer.h
- vector_math.h
- )
+add_library(common STATIC
+ alignment.h
+ assert.h
+ bit_field.h
+ bit_set.h
+ break_points.cpp
+ break_points.h
+ chunk_file.h
+ code_block.h
+ color.h
+ common_funcs.h
+ common_paths.h
+ common_types.h
+ file_util.cpp
+ file_util.h
+ hash.cpp
+ hash.h
+ linear_disk_cache.h
+ logging/backend.cpp
+ logging/backend.h
+ logging/filter.cpp
+ logging/filter.h
+ logging/log.h
+ logging/text_formatter.cpp
+ logging/text_formatter.h
+ math_util.h
+ memory_util.cpp
+ memory_util.h
+ microprofile.cpp
+ microprofile.h
+ microprofileui.h
+ misc.cpp
+ param_package.cpp
+ param_package.h
+ quaternion.h
+ scm_rev.cpp
+ scm_rev.h
+ scope_exit.h
+ string_util.cpp
+ string_util.h
+ swap.h
+ synchronized_wrapper.h
+ telemetry.cpp
+ telemetry.h
+ thread.cpp
+ thread.h
+ thread_queue_list.h
+ threadsafe_queue.h
+ timer.cpp
+ timer.h
+ vector_math.h
+)
if(ARCHITECTURE_x86_64)
- set(SRCS ${SRCS}
+ target_sources(common
+ PRIVATE
x64/cpu_detect.cpp
- )
-
- set(HEADERS ${HEADERS}
x64/cpu_detect.h
x64/xbyak_abi.h
x64/xbyak_util.h
- )
+ )
endif()
-create_directory_groups(${SRCS} ${HEADERS})
+create_target_directory_groups(common)
-add_library(common STATIC ${SRCS} ${HEADERS})
target_link_libraries(common PUBLIC Boost::boost microprofile)
if (ARCHITECTURE_x86_64)
target_link_libraries(common PRIVATE xbyak)
diff --git a/src/common/bit_set.h b/src/common/bit_set.h
index 9c2e6b28c..84e3cbe58 100644
--- a/src/common/bit_set.h
+++ b/src/common/bit_set.h
@@ -236,7 +236,7 @@ public:
IntTy m_val;
};
-} // Common
+} // namespace Common
typedef Common::BitSet<u8> BitSet8;
typedef Common::BitSet<u16> BitSet16;
diff --git a/src/common/chunk_file.h b/src/common/chunk_file.h
index 5145a3657..972ef9039 100644
--- a/src/common/chunk_file.h
+++ b/src/common/chunk_file.h
@@ -607,8 +607,9 @@ public:
u32 cookie = arbitraryNumber;
Do(cookie);
if (mode == PointerWrap::MODE_READ && cookie != arbitraryNumber) {
- LOG_ERROR(Common, "After \"%s\", found %d (0x%X) instead of save marker %d (0x%X). "
- "Aborting savestate load...",
+ LOG_ERROR(Common,
+ "After \"%s\", found %d (0x%X) instead of save marker %d (0x%X). "
+ "Aborting savestate load...",
prevName, cookie, cookie, arbitraryNumber, arbitraryNumber);
SetError(ERROR_FAILURE);
}
diff --git a/src/common/color.h b/src/common/color.h
index 4ebd4f3d0..24a445dac 100644
--- a/src/common/color.h
+++ b/src/common/color.h
@@ -256,4 +256,4 @@ inline void EncodeX24S8(u8 stencil, u8* bytes) {
bytes[3] = stencil;
}
-} // namespace
+} // namespace Color
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 5ab036b34..4e1d702f7 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -873,20 +873,19 @@ bool IOFile::Flush() {
}
bool IOFile::Resize(u64 size) {
- if (!IsOpen() ||
- 0 !=
+ if (!IsOpen() || 0 !=
#ifdef _WIN32
- // ector: _chsize sucks, not 64-bit safe
- // F|RES: changed to _chsize_s. i think it is 64-bit safe
- _chsize_s(_fileno(m_file), size)
+ // ector: _chsize sucks, not 64-bit safe
+ // F|RES: changed to _chsize_s. i think it is 64-bit safe
+ _chsize_s(_fileno(m_file), size)
#else
- // TODO: handle 64bit and growing
- ftruncate(fileno(m_file), size)
+ // TODO: handle 64bit and growing
+ ftruncate(fileno(m_file), size)
#endif
- )
+ )
m_good = false;
return m_good;
}
-} // namespace
+} // namespace FileUtil
diff --git a/src/common/file_util.h b/src/common/file_util.h
index 94adfcd7e..143f099eb 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -121,7 +121,7 @@ void CopyDir(const std::string& source_path, const std::string& dest_path);
// Set the current directory to given directory
bool SetCurrentDir(const std::string& directory);
-// Returns a pointer to a string with a Citra data dir in the user's home
+// Returns a pointer to a string with a yuzu data dir in the user's home
// directory. To be used in "multi-user" mode (that is, installed).
const std::string& GetUserPath(const unsigned int DirIDX, const std::string& newPath = "");
@@ -253,7 +253,7 @@ private:
bool m_good = true;
};
-} // namespace
+} // namespace FileUtil
// To deal with Windows being dumb at unicode:
template <typename T>
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 13f915a01..a763f4abf 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -32,17 +32,33 @@ namespace Log {
CLS(Kernel) \
SUB(Kernel, SVC) \
CLS(Service) \
- SUB(Service, SM) \
+ SUB(Service, ACC) \
+ SUB(Service, Audio) \
+ SUB(Service, AM) \
+ SUB(Service, AOC) \
+ SUB(Service, APM) \
+ SUB(Service, Fatal) \
+ SUB(Service, Friend) \
SUB(Service, FS) \
- SUB(Service, GSP) \
- SUB(Service, CFG) \
- SUB(Service, DSP) \
SUB(Service, HID) \
+ SUB(Service, LM) \
+ SUB(Service, NFP) \
+ SUB(Service, NIFM) \
+ SUB(Service, NS) \
+ SUB(Service, NVDRV) \
+ SUB(Service, PCTL) \
+ SUB(Service, SET) \
+ SUB(Service, SM) \
+ SUB(Service, SPL) \
+ SUB(Service, SSL) \
+ SUB(Service, Time) \
+ SUB(Service, VI) \
CLS(HW) \
SUB(HW, Memory) \
SUB(HW, LCD) \
SUB(HW, GPU) \
SUB(HW, AES) \
+ CLS(IPC) \
CLS(Frontend) \
CLS(Render) \
SUB(Render, Software) \
@@ -91,8 +107,8 @@ const char* GetLevelName(Level log_level) {
Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
const char* function, const char* format, va_list args) {
- using std::chrono::steady_clock;
using std::chrono::duration_cast;
+ using std::chrono::steady_clock;
static steady_clock::time_point time_origin = steady_clock::now();
@@ -131,4 +147,4 @@ void LogMessage(Class log_class, Level log_level, const char* filename, unsigned
PrintColoredMessage(entry);
}
-}
+} // namespace Log
diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h
index c4fe2acbf..70744e3e5 100644
--- a/src/common/logging/backend.h
+++ b/src/common/logging/backend.h
@@ -47,4 +47,4 @@ Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsign
const char* function, const char* format, va_list args);
void SetFilter(Filter* filter);
-}
+} // namespace Log
diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp
index 12e5bb45d..733247b51 100644
--- a/src/common/logging/filter.cpp
+++ b/src/common/logging/filter.cpp
@@ -94,4 +94,4 @@ bool Filter::ParseFilterRule(const std::string::const_iterator begin,
bool Filter::CheckMessage(Class log_class, Level level) const {
return static_cast<u8>(level) >= static_cast<u8>(class_levels[static_cast<size_t>(log_class)]);
}
-}
+} // namespace Log
diff --git a/src/common/logging/filter.h b/src/common/logging/filter.h
index b51df61de..16fa72642 100644
--- a/src/common/logging/filter.h
+++ b/src/common/logging/filter.h
@@ -50,4 +50,4 @@ public:
private:
std::array<Level, (size_t)Class::Count> class_levels;
};
-}
+} // namespace Log
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 35b5af3cb..7f079b20f 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -49,17 +49,33 @@ enum class Class : ClassType {
Kernel_SVC, ///< Kernel system calls
Service, ///< HLE implementation of system services. Each major service
/// should have its own subclass.
- Service_SM, ///< The SRV (Service Directory) implementation
- Service_FS, ///< The FS (Filesystem) service implementation
- Service_GSP, ///< The GSP (GPU control) service
- Service_CFG, ///< The CFG (Configuration) service
- Service_DSP, ///< The DSP (DSP control) service
+ Service_ACC, ///< The ACC (Accounts) service
+ Service_AM, ///< The AM (Applet manager) service
+ Service_AOC, ///< The AOC (AddOn Content) service
+ Service_APM, ///< The APM (Performance) service
+ Service_Audio, ///< The Audio (Audio control) service
+ Service_Fatal, ///< The Fatal service
+ Service_Friend, ///< The friend service
+ Service_FS, ///< The FS (Filesystem) service
Service_HID, ///< The HID (Human interface device) service
+ Service_LM, ///< The LM (Logger) service
+ Service_NFP, ///< The NFP service
+ Service_NIFM, ///< The NIFM (Network interface) service
+ Service_NS, ///< The NS services
+ Service_NVDRV, ///< The NVDRV (Nvidia driver) service
+ Service_PCTL, ///< The PCTL (Parental control) service
+ Service_SET, ///< The SET (Settings) service
+ Service_SM, ///< The SM (Service manager) service
+ Service_SPL, ///< The SPL service
+ Service_SSL, ///< The SSL service
+ Service_Time, ///< The time service
+ Service_VI, ///< The VI (Video interface) service
HW, ///< Low-level hardware emulation
HW_Memory, ///< Memory-map and address translation
HW_LCD, ///< LCD register emulation
HW_GPU, ///< GPU control emulation
HW_AES, ///< AES engine emulation
+ IPC, ///< IPC interface
Frontend, ///< Emulator UI
Render, ///< Emulator video output and hardware acceleration
Render_Software, ///< Software renderer backend
@@ -70,7 +86,7 @@ enum class Class : ClassType {
Loader, ///< ROM loader
Input, ///< Input emulation
Network, ///< Network emulation
- WebService, ///< Interface to Citra Web Services
+ WebService, ///< Interface to yuzu Web Services
Count ///< Total number of logging classes
};
diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp
index f71e748d1..e7e46c76b 100644
--- a/src/common/logging/text_formatter.cpp
+++ b/src/common/logging/text_formatter.cpp
@@ -129,4 +129,4 @@ void PrintColoredMessage(const Entry& entry) {
#undef ESC
#endif
}
-}
+} // namespace Log
diff --git a/src/common/logging/text_formatter.h b/src/common/logging/text_formatter.h
index 749268310..66e86d9ec 100644
--- a/src/common/logging/text_formatter.h
+++ b/src/common/logging/text_formatter.h
@@ -28,4 +28,4 @@ void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len);
void PrintMessage(const Entry& entry);
/// Prints the same message as `PrintMessage`, but colored acoording to the severity level.
void PrintColoredMessage(const Entry& entry);
-}
+} // namespace Log
diff --git a/src/common/memory_util.cpp b/src/common/memory_util.cpp
index c19729b21..759ad02ca 100644
--- a/src/common/memory_util.cpp
+++ b/src/common/memory_util.cpp
@@ -40,11 +40,12 @@ void* AllocateExecutableMemory(size_t size, bool low) {
if (low && (!map_hint))
map_hint = (char*)round_page(512 * 1024 * 1024); /* 0.5 GB rounded up to the next page */
#endif
- void* ptr = mmap(map_hint, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE
+ void* ptr = mmap(map_hint, size, PROT_READ | PROT_WRITE | PROT_EXEC,
+ MAP_ANON | MAP_PRIVATE
#if defined(ARCHITECTURE_X64) && defined(MAP_32BIT)
- | (low ? MAP_32BIT : 0)
+ | (low ? MAP_32BIT : 0)
#endif
- ,
+ ,
-1, 0);
#endif /* defined(_WIN32) */
diff --git a/src/common/platform.h b/src/common/platform.h
deleted file mode 100644
index c62fb7c8f..000000000
--- a/src/common/platform.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * Copyright (C) 2005-2012 Gekko Emulator
- *
- * @file platform.h
- * @author ShizZy <shizzy247@gmail.com>
- * @date 2012-02-11
- * @brief Platform detection macros for portable compilation
- *
- * @section LICENSE
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details at
- * http://www.gnu.org/copyleft/gpl.html
- *
- * Official project repository can be found at:
- * http://code.google.com/p/gekko-gc-emu/
- */
-
-#pragma once
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// Platform detection
-
-#if defined(ARCHITECTURE_x86_64) || defined(__aarch64__)
-#define EMU_ARCH_BITS 64
-#elif defined(__i386) || defined(_M_IX86) || defined(__arm__) || defined(_M_ARM)
-#define EMU_ARCH_BITS 32
-#endif
diff --git a/src/common/quaternion.h b/src/common/quaternion.h
index 77f626bcb..ea39298c1 100644
--- a/src/common/quaternion.h
+++ b/src/common/quaternion.h
@@ -46,4 +46,4 @@ inline Quaternion<float> MakeQuaternion(const Math::Vec3<float>& axis, float ang
return {axis * std::sin(angle / 2), std::cos(angle / 2)};
}
-} // namspace Math
+} // namespace Math
diff --git a/src/common/scm_rev.h b/src/common/scm_rev.h
index 18aaa1735..db0f4a947 100644
--- a/src/common/scm_rev.h
+++ b/src/common/scm_rev.h
@@ -12,4 +12,4 @@ extern const char g_scm_desc[];
extern const char g_build_name[];
extern const char g_build_date[];
-} // namespace
+} // namespace Common
diff --git a/src/common/scope_exit.h b/src/common/scope_exit.h
index 072ab285d..baf1f1c9e 100644
--- a/src/common/scope_exit.h
+++ b/src/common/scope_exit.h
@@ -22,7 +22,7 @@ template <typename Func>
ScopeExitHelper<Func> ScopeExit(Func&& func) {
return ScopeExitHelper<Func>(std::move(func));
}
-}
+} // namespace detail
/**
* This macro allows you to conveniently specify a block of code that will run on scope exit. Handy
diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp
index 6959915fa..e9a2a6b00 100644
--- a/src/common/string_util.cpp
+++ b/src/common/string_util.cpp
@@ -202,7 +202,7 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
#ifdef _WIN32
":"
#endif
- );
+ );
if (std::string::npos == dir_end)
dir_end = 0;
else
@@ -462,4 +462,4 @@ std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, size_t max_l
return std::string(buffer, len);
}
-}
+} // namespace Common
diff --git a/src/common/string_util.h b/src/common/string_util.h
index 259360aec..ceb8df48e 100644
--- a/src/common/string_util.h
+++ b/src/common/string_util.h
@@ -134,4 +134,4 @@ bool ComparePartialString(InIt begin, InIt end, const char* other) {
* NUL-terminated then the string ends at max_len characters.
*/
std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, size_t max_len);
-}
+} // namespace Common
diff --git a/src/common/telemetry.h b/src/common/telemetry.h
index dd6bbd759..3694c76f2 100644
--- a/src/common/telemetry.h
+++ b/src/common/telemetry.h
@@ -53,10 +53,10 @@ template <typename T>
class Field : public FieldInterface {
public:
Field(FieldType type, std::string name, const T& value)
- : type(type), name(std::move(name)), value(value) {}
+ : name(std::move(name)), type(type), value(value) {}
Field(FieldType type, std::string name, T&& value)
- : type(type), name(std::move(name)), value(std::move(value)) {}
+ : name(std::move(name)), type(type), value(std::move(value)) {}
Field(const Field& other) : Field(other.type, other.name, other.value) {}
diff --git a/src/common/thread_queue_list.h b/src/common/thread_queue_list.h
index edd0e4a3f..38a450d69 100644
--- a/src/common/thread_queue_list.h
+++ b/src/common/thread_queue_list.h
@@ -158,4 +158,4 @@ private:
std::array<Queue, NUM_QUEUES> queues;
};
-} // namespace
+} // namespace Common
diff --git a/src/common/x64/cpu_detect.cpp b/src/common/x64/cpu_detect.cpp
index 62f17fbb5..2dfcd39c8 100644
--- a/src/common/x64/cpu_detect.cpp
+++ b/src/common/x64/cpu_detect.cpp
@@ -54,7 +54,7 @@ static CPUCaps Detect() {
caps.num_cores = std::thread::hardware_concurrency();
// Assumes the CPU supports the CPUID instruction. Those that don't would likely not support
- // Citra at all anyway
+ // yuzu at all anyway
int cpu_id[4];
memset(caps.brand_string, 0, sizeof(caps.brand_string));
diff --git a/src/common/x64/xbyak_abi.h b/src/common/x64/xbyak_abi.h
index 6090d93e1..fd3fbdd4b 100644
--- a/src/common/x64/xbyak_abi.h
+++ b/src/common/x64/xbyak_abi.h
@@ -60,20 +60,41 @@ const Xbyak::Reg ABI_PARAM4 = Xbyak::util::r9;
const BitSet32 ABI_ALL_CALLER_SAVED = BuildRegSet({
// GPRs
- Xbyak::util::rcx, Xbyak::util::rdx, Xbyak::util::r8, Xbyak::util::r9, Xbyak::util::r10,
+ Xbyak::util::rcx,
+ Xbyak::util::rdx,
+ Xbyak::util::r8,
+ Xbyak::util::r9,
+ Xbyak::util::r10,
Xbyak::util::r11,
// XMMs
- Xbyak::util::xmm0, Xbyak::util::xmm1, Xbyak::util::xmm2, Xbyak::util::xmm3, Xbyak::util::xmm4,
+ Xbyak::util::xmm0,
+ Xbyak::util::xmm1,
+ Xbyak::util::xmm2,
+ Xbyak::util::xmm3,
+ Xbyak::util::xmm4,
Xbyak::util::xmm5,
});
const BitSet32 ABI_ALL_CALLEE_SAVED = BuildRegSet({
// GPRs
- Xbyak::util::rbx, Xbyak::util::rsi, Xbyak::util::rdi, Xbyak::util::rbp, Xbyak::util::r12,
- Xbyak::util::r13, Xbyak::util::r14, Xbyak::util::r15,
+ Xbyak::util::rbx,
+ Xbyak::util::rsi,
+ Xbyak::util::rdi,
+ Xbyak::util::rbp,
+ Xbyak::util::r12,
+ Xbyak::util::r13,
+ Xbyak::util::r14,
+ Xbyak::util::r15,
// XMMs
- Xbyak::util::xmm6, Xbyak::util::xmm7, Xbyak::util::xmm8, Xbyak::util::xmm9, Xbyak::util::xmm10,
- Xbyak::util::xmm11, Xbyak::util::xmm12, Xbyak::util::xmm13, Xbyak::util::xmm14,
+ Xbyak::util::xmm6,
+ Xbyak::util::xmm7,
+ Xbyak::util::xmm8,
+ Xbyak::util::xmm9,
+ Xbyak::util::xmm10,
+ Xbyak::util::xmm11,
+ Xbyak::util::xmm12,
+ Xbyak::util::xmm13,
+ Xbyak::util::xmm14,
Xbyak::util::xmm15,
});
@@ -90,18 +111,40 @@ const Xbyak::Reg ABI_PARAM4 = Xbyak::util::rcx;
const BitSet32 ABI_ALL_CALLER_SAVED = BuildRegSet({
// GPRs
- Xbyak::util::rcx, Xbyak::util::rdx, Xbyak::util::rdi, Xbyak::util::rsi, Xbyak::util::r8,
- Xbyak::util::r9, Xbyak::util::r10, Xbyak::util::r11,
+ Xbyak::util::rcx,
+ Xbyak::util::rdx,
+ Xbyak::util::rdi,
+ Xbyak::util::rsi,
+ Xbyak::util::r8,
+ Xbyak::util::r9,
+ Xbyak::util::r10,
+ Xbyak::util::r11,
// XMMs
- Xbyak::util::xmm0, Xbyak::util::xmm1, Xbyak::util::xmm2, Xbyak::util::xmm3, Xbyak::util::xmm4,
- Xbyak::util::xmm5, Xbyak::util::xmm6, Xbyak::util::xmm7, Xbyak::util::xmm8, Xbyak::util::xmm9,
- Xbyak::util::xmm10, Xbyak::util::xmm11, Xbyak::util::xmm12, Xbyak::util::xmm13,
- Xbyak::util::xmm14, Xbyak::util::xmm15,
+ Xbyak::util::xmm0,
+ Xbyak::util::xmm1,
+ Xbyak::util::xmm2,
+ Xbyak::util::xmm3,
+ Xbyak::util::xmm4,
+ Xbyak::util::xmm5,
+ Xbyak::util::xmm6,
+ Xbyak::util::xmm7,
+ Xbyak::util::xmm8,
+ Xbyak::util::xmm9,
+ Xbyak::util::xmm10,
+ Xbyak::util::xmm11,
+ Xbyak::util::xmm12,
+ Xbyak::util::xmm13,
+ Xbyak::util::xmm14,
+ Xbyak::util::xmm15,
});
const BitSet32 ABI_ALL_CALLEE_SAVED = BuildRegSet({
// GPRs
- Xbyak::util::rbx, Xbyak::util::rbp, Xbyak::util::r12, Xbyak::util::r13, Xbyak::util::r14,
+ Xbyak::util::rbx,
+ Xbyak::util::rbp,
+ Xbyak::util::r12,
+ Xbyak::util::r13,
+ Xbyak::util::r14,
Xbyak::util::r15,
});
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 20e41038b..6f8104516 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -1,175 +1,270 @@
-set(SRCS
- arm/dynarmic/arm_dynarmic.cpp
- arm/unicorn/arm_unicorn.cpp
- core.cpp
- core_timing.cpp
- file_sys/archive_backend.cpp
- file_sys/disk_archive.cpp
- file_sys/ivfc_archive.cpp
- file_sys/path_parser.cpp
- file_sys/savedata_archive.cpp
- file_sys/title_metadata.cpp
- frontend/emu_window.cpp
- frontend/framebuffer_layout.cpp
- gdbstub/gdbstub.cpp
- hle/config_mem.cpp
- hle/kernel/address_arbiter.cpp
- hle/kernel/client_port.cpp
- hle/kernel/client_session.cpp
- hle/kernel/condition_variable.cpp
- hle/kernel/domain.cpp
- hle/kernel/event.cpp
- hle/kernel/handle_table.cpp
- hle/kernel/hle_ipc.cpp
- hle/kernel/kernel.cpp
- hle/kernel/memory.cpp
- hle/kernel/mutex.cpp
- hle/kernel/object_address_table.cpp
- hle/kernel/process.cpp
- hle/kernel/resource_limit.cpp
- hle/kernel/server_port.cpp
- hle/kernel/server_session.cpp
- hle/kernel/shared_memory.cpp
- hle/kernel/svc.cpp
- hle/kernel/thread.cpp
- hle/kernel/timer.cpp
- hle/kernel/vm_manager.cpp
- hle/kernel/wait_object.cpp
- hle/lock.cpp
- hle/romfs.cpp
- hle/service/acc/acc.cpp
- hle/service/acc/acc_u0.cpp
- hle/service/am/am.cpp
- hle/service/am/applet_oe.cpp
- hle/service/aoc/aoc_u.cpp
- hle/service/apm/apm.cpp
- hle/service/audio/audio.cpp
- hle/service/audio/audout_u.cpp
- hle/service/hid/hid.cpp
- hle/service/lm/lm.cpp
- hle/service/nvdrv/devices/nvdisp_disp0.cpp
- hle/service/nvdrv/devices/nvhost_as_gpu.cpp
- hle/service/nvdrv/devices/nvmap.cpp
- hle/service/nvdrv/interface.cpp
- hle/service/nvdrv/nvdrv.cpp
- hle/service/pctl/pctl.cpp
- hle/service/pctl/pctl_a.cpp
- hle/service/service.cpp
- hle/service/sm/controller.cpp
- hle/service/sm/sm.cpp
- hle/service/time/time.cpp
- hle/service/time/time_s.cpp
- hle/service/vi/vi.cpp
- hle/service/vi/vi_m.cpp
- hle/shared_page.cpp
- hw/hw.cpp
- hw/lcd.cpp
- loader/elf.cpp
- loader/linker.cpp
- loader/loader.cpp
- loader/nro.cpp
- loader/nso.cpp
- tracer/recorder.cpp
- memory.cpp
- perf_stats.cpp
- settings.cpp
- telemetry_session.cpp
- )
+add_library(core STATIC
+ arm/arm_interface.h
+ arm/unicorn/arm_unicorn.cpp
+ arm/unicorn/arm_unicorn.h
+ core.cpp
+ core.h
+ core_timing.cpp
+ core_timing.h
+ file_sys/directory.h
+ file_sys/disk_filesystem.cpp
+ file_sys/disk_filesystem.h
+ file_sys/errors.h
+ file_sys/filesystem.cpp
+ file_sys/filesystem.h
+ file_sys/path_parser.cpp
+ file_sys/path_parser.h
+ file_sys/program_metadata.cpp
+ file_sys/program_metadata.h
+ file_sys/romfs_factory.cpp
+ file_sys/romfs_factory.h
+ file_sys/romfs_filesystem.cpp
+ file_sys/romfs_filesystem.h
+ file_sys/savedata_factory.cpp
+ file_sys/savedata_factory.h
+ file_sys/sdmc_factory.cpp
+ file_sys/sdmc_factory.h
+ file_sys/storage.h
+ frontend/emu_window.cpp
+ frontend/emu_window.h
+ frontend/framebuffer_layout.cpp
+ frontend/framebuffer_layout.h
+ frontend/input.h
+ gdbstub/gdbstub.cpp
+ gdbstub/gdbstub.h
+ hle/config_mem.cpp
+ hle/config_mem.h
+ hle/ipc.h
+ hle/ipc_helpers.h
+ hle/kernel/client_port.cpp
+ hle/kernel/client_port.h
+ hle/kernel/client_session.cpp
+ hle/kernel/client_session.h
+ hle/kernel/condition_variable.cpp
+ hle/kernel/condition_variable.h
+ hle/kernel/errors.h
+ hle/kernel/event.cpp
+ hle/kernel/event.h
+ hle/kernel/handle_table.cpp
+ hle/kernel/handle_table.h
+ hle/kernel/hle_ipc.cpp
+ hle/kernel/hle_ipc.h
+ hle/kernel/kernel.cpp
+ hle/kernel/kernel.h
+ hle/kernel/memory.cpp
+ hle/kernel/memory.h
+ hle/kernel/mutex.cpp
+ hle/kernel/mutex.h
+ hle/kernel/object_address_table.cpp
+ hle/kernel/object_address_table.h
+ hle/kernel/process.cpp
+ hle/kernel/process.h
+ hle/kernel/resource_limit.cpp
+ hle/kernel/resource_limit.h
+ hle/kernel/scheduler.cpp
+ hle/kernel/scheduler.h
+ hle/kernel/server_port.cpp
+ hle/kernel/server_port.h
+ hle/kernel/server_session.cpp
+ hle/kernel/server_session.h
+ hle/kernel/session.h
+ hle/kernel/shared_memory.cpp
+ hle/kernel/shared_memory.h
+ hle/kernel/svc.cpp
+ hle/kernel/svc.h
+ hle/kernel/svc_wrap.h
+ hle/kernel/thread.cpp
+ hle/kernel/thread.h
+ hle/kernel/timer.cpp
+ hle/kernel/timer.h
+ hle/kernel/vm_manager.cpp
+ hle/kernel/vm_manager.h
+ hle/kernel/wait_object.cpp
+ hle/kernel/wait_object.h
+ hle/lock.cpp
+ hle/lock.h
+ hle/result.h
+ hle/romfs.cpp
+ hle/romfs.h
+ hle/service/acc/acc.cpp
+ hle/service/acc/acc.h
+ hle/service/acc/acc_u0.cpp
+ hle/service/acc/acc_u0.h
+ hle/service/am/am.cpp
+ hle/service/am/am.h
+ hle/service/am/applet_ae.cpp
+ hle/service/am/applet_ae.h
+ hle/service/am/applet_oe.cpp
+ hle/service/am/applet_oe.h
+ hle/service/aoc/aoc_u.cpp
+ hle/service/aoc/aoc_u.h
+ hle/service/apm/apm.cpp
+ hle/service/apm/apm.h
+ hle/service/apm/interface.cpp
+ hle/service/apm/interface.h
+ hle/service/audio/audio.cpp
+ hle/service/audio/audio.h
+ hle/service/audio/audin_u.cpp
+ hle/service/audio/audin_u.h
+ hle/service/audio/audout_u.cpp
+ hle/service/audio/audout_u.h
+ hle/service/audio/audrec_u.cpp
+ hle/service/audio/audrec_u.h
+ hle/service/audio/audren_u.cpp
+ hle/service/audio/audren_u.h
+ hle/service/audio/audren_u.cpp
+ hle/service/audio/audren_u.h
+ hle/service/audio/codecctl.cpp
+ hle/service/audio/codecctl.h
+ hle/service/fatal/fatal.cpp
+ hle/service/fatal/fatal.h
+ hle/service/fatal/fatal_p.cpp
+ hle/service/fatal/fatal_p.h
+ hle/service/fatal/fatal_u.cpp
+ hle/service/fatal/fatal_u.h
+ hle/service/filesystem/filesystem.cpp
+ hle/service/filesystem/filesystem.h
+ hle/service/filesystem/fsp_srv.cpp
+ hle/service/filesystem/fsp_srv.h
+ hle/service/friend/friend.cpp
+ hle/service/friend/friend.h
+ hle/service/friend/friend_a.cpp
+ hle/service/friend/friend_a.h
+ hle/service/hid/hid.cpp
+ hle/service/hid/hid.h
+ hle/service/lm/lm.cpp
+ hle/service/lm/lm.h
+ hle/service/nifm/nifm.cpp
+ hle/service/nifm/nifm.h
+ hle/service/nifm/nifm_a.cpp
+ hle/service/nifm/nifm_a.h
+ hle/service/nifm/nifm_s.cpp
+ hle/service/nifm/nifm_s.h
+ hle/service/nifm/nifm_u.cpp
+ hle/service/nifm/nifm_u.h
+ hle/service/nfp/nfp.cpp
+ hle/service/nfp/nfp.h
+ hle/service/nfp/nfp_user.cpp
+ hle/service/nfp/nfp_user.h
+ hle/service/ns/ns.cpp
+ hle/service/ns/ns.h
+ hle/service/ns/pl_u.cpp
+ hle/service/ns/pl_u.h
+ hle/service/nvdrv/devices/nvdevice.h
+ hle/service/nvdrv/devices/nvdisp_disp0.cpp
+ hle/service/nvdrv/devices/nvdisp_disp0.h
+ hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+ hle/service/nvdrv/devices/nvhost_as_gpu.h
+ hle/service/nvdrv/devices/nvhost_ctrl.cpp
+ hle/service/nvdrv/devices/nvhost_ctrl.h
+ hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
+ hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
+ hle/service/nvdrv/devices/nvhost_gpu.cpp
+ hle/service/nvdrv/devices/nvhost_gpu.h
+ hle/service/nvdrv/devices/nvmap.cpp
+ hle/service/nvdrv/devices/nvmap.h
+ hle/service/nvdrv/interface.cpp
+ hle/service/nvdrv/interface.h
+ hle/service/nvdrv/nvdrv.cpp
+ hle/service/nvdrv/nvdrv.h
+ hle/service/nvdrv/nvmemp.cpp
+ hle/service/nvdrv/nvmemp.h
+ hle/service/nvflinger/buffer_queue.cpp
+ hle/service/nvflinger/buffer_queue.h
+ hle/service/nvflinger/nvflinger.cpp
+ hle/service/nvflinger/nvflinger.h
+ hle/service/pctl/pctl.cpp
+ hle/service/pctl/pctl.h
+ hle/service/pctl/pctl_a.cpp
+ hle/service/pctl/pctl_a.h
+ hle/service/service.cpp
+ hle/service/service.h
+ hle/service/set/set.cpp
+ hle/service/set/set.h
+ hle/service/set/set_cal.cpp
+ hle/service/set/set_cal.h
+ hle/service/set/set_fd.cpp
+ hle/service/set/set_fd.h
+ hle/service/set/set_sys.cpp
+ hle/service/set/set_sys.h
+ hle/service/set/settings.cpp
+ hle/service/set/settings.h
+ hle/service/sm/controller.cpp
+ hle/service/sm/controller.h
+ hle/service/sm/sm.cpp
+ hle/service/sm/sm.h
+ hle/service/sockets/bsd.cpp
+ hle/service/sockets/bsd.h
+ hle/service/sockets/nsd.cpp
+ hle/service/sockets/nsd.h
+ hle/service/sockets/sfdnsres.cpp
+ hle/service/sockets/sfdnsres.h
+ hle/service/sockets/sockets.cpp
+ hle/service/sockets/sockets.h
+ hle/service/spl/csrng.cpp
+ hle/service/spl/csrng.h
+ hle/service/spl/module.cpp
+ hle/service/spl/module.h
+ hle/service/spl/spl.cpp
+ hle/service/spl/spl.h
+ hle/service/ssl/ssl.cpp
+ hle/service/ssl/ssl.h
+ hle/service/time/time.cpp
+ hle/service/time/time.h
+ hle/service/time/time_s.cpp
+ hle/service/time/time_s.h
+ hle/service/time/time_u.cpp
+ hle/service/time/time_u.h
+ hle/service/vi/vi.cpp
+ hle/service/vi/vi.h
+ hle/service/vi/vi_m.cpp
+ hle/service/vi/vi_m.h
+ hle/service/vi/vi_s.cpp
+ hle/service/vi/vi_s.h
+ hle/service/vi/vi_u.cpp
+ hle/service/vi/vi_u.h
+ hle/shared_page.cpp
+ hle/shared_page.h
+ hw/hw.cpp
+ hw/hw.h
+ hw/lcd.cpp
+ hw/lcd.h
+ loader/deconstructed_rom_directory.cpp
+ loader/deconstructed_rom_directory.h
+ loader/elf.cpp
+ loader/elf.h
+ loader/linker.cpp
+ loader/linker.h
+ loader/loader.cpp
+ loader/loader.h
+ loader/nro.cpp
+ loader/nro.h
+ loader/nso.cpp
+ loader/nso.h
+ memory.cpp
+ memory.h
+ memory_hook.h
+ memory_setup.h
+ perf_stats.cpp
+ perf_stats.h
+ settings.cpp
+ settings.h
+ telemetry_session.cpp
+ telemetry_session.h
+ tracer/citrace.h
+ tracer/recorder.cpp
+ tracer/recorder.h
+)
-set(HEADERS
- arm/arm_interface.h
- arm/dynarmic/arm_dynarmic.h
- arm/unicorn/arm_unicorn.h
- core.h
- core_timing.h
- file_sys/archive_backend.h
- file_sys/directory_backend.h
- file_sys/disk_archive.h
- file_sys/errors.h
- file_sys/file_backend.h
- file_sys/ivfc_archive.h
- file_sys/path_parser.h
- file_sys/savedata_archive.h
- frontend/emu_window.h
- frontend/framebuffer_layout.h
- frontend/input.h
- gdbstub/gdbstub.h
- hle/config_mem.h
- hle/ipc.h
- hle/ipc_helpers.h
- hle/kernel/address_arbiter.h
- hle/kernel/client_port.h
- hle/kernel/client_session.h
- hle/kernel/condition_variable.h
- hle/kernel/domain.h
- hle/kernel/errors.h
- hle/kernel/event.h
- hle/kernel/handle_table.h
- hle/kernel/hle_ipc.h
- hle/kernel/kernel.h
- hle/kernel/memory.h
- hle/kernel/mutex.h
- hle/kernel/object_address_table.h
- hle/kernel/process.h
- hle/kernel/resource_limit.h
- hle/kernel/server_port.h
- hle/kernel/server_session.h
- hle/kernel/session.h
- hle/kernel/shared_memory.h
- hle/kernel/sync_object.h
- hle/kernel/svc.h
- hle/kernel/svc_wrap.h
- hle/kernel/thread.h
- hle/kernel/timer.h
- hle/kernel/vm_manager.h
- hle/kernel/wait_object.h
- hle/lock.h
- hle/result.h
- hle/romfs.h
- hle/service/acc/acc.h
- hle/service/acc/acc_u0.h
- hle/service/am/am.h
- hle/service/am/applet_oe.h
- hle/service/aoc/aoc_u.h
- hle/service/apm/apm.h
- hle/service/audio/audio.h
- hle/service/audio/audout_u.h
- hle/service/hid/hid.h
- hle/service/lm/lm.h
- hle/service/nvdrv/devices/nvdevice.h
- hle/service/nvdrv/devices/nvdisp_disp0.h
- hle/service/nvdrv/devices/nvhost_as_gpu.h
- hle/service/nvdrv/devices/nvmap.h
- hle/service/nvdrv/interface.h
- hle/service/nvdrv/nvdrv.h
- hle/service/pctl/pctl.h
- hle/service/pctl/pctl_a.h
- hle/service/service.h
- hle/service/sm/controller.h
- hle/service/sm/sm.h
- hle/service/time/time.h
- hle/service/time/time_s.h
- hle/service/vi/vi.h
- hle/service/vi/vi_m.h
- hle/shared_page.h
- hw/hw.h
- hw/lcd.h
- loader/elf.h
- loader/linker.h
- loader/loader.h
- loader/nro.h
- loader/nso.h
- tracer/recorder.h
- tracer/citrace.h
- memory.h
- memory_setup.h
- mmio.h
- perf_stats.h
- settings.h
- telemetry_session.h
- )
+create_target_directory_groups(core)
-create_directory_groups(${SRCS} ${HEADERS})
-add_library(core STATIC ${SRCS} ${HEADERS})
-target_link_libraries(core PUBLIC common PRIVATE dynarmic video_core)
+target_link_libraries(core PUBLIC common PRIVATE video_core)
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static unicorn)
+
+if (ARCHITECTURE_x86_64)
+ target_sources(core PRIVATE
+ arm/dynarmic/arm_dynarmic.cpp
+ arm/dynarmic/arm_dynarmic.h
+ )
+ target_link_libraries(core PRIVATE dynarmic)
+endif()
diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h
index 5ae60214e..32ff3c345 100644
--- a/src/core/arm/arm_interface.h
+++ b/src/core/arm/arm_interface.h
@@ -25,22 +25,18 @@ public:
VAddr tls_address;
};
- /**
- * Runs the CPU for the given number of instructions
- * @param num_instructions Number of instructions to run
- */
- void Run(int num_instructions) {
- ExecuteInstructions(num_instructions);
- this->num_instructions += num_instructions;
- }
+ /// Runs the CPU until an event happens
+ virtual void Run() = 0;
/// Step CPU by one instruction
- void Step() {
- Run(1);
- }
+ virtual void Step() = 0;
+ /// Maps a backing memory region for the CPU
virtual void MapBackingMemory(VAddr address, size_t size, u8* memory,
- Kernel::VMAPermission perms) {}
+ Kernel::VMAPermission perms) = 0;
+
+ /// Unmaps a region of memory that was previously mapped using MapBackingMemory
+ virtual void UnmapMemory(VAddr address, size_t size) = 0;
/// Clear all instruction cache
virtual void ClearInstructionCache() = 0;
@@ -122,19 +118,4 @@ public:
/// Prepare core for thread reschedule (if needed to correctly handle state)
virtual void PrepareReschedule() = 0;
-
- /// Getter for num_instructions
- u64 GetNumInstructions() const {
- return num_instructions;
- }
-
-protected:
- /**
- * Executes the given number of instructions
- * @param num_instructions Number of instructions to executes
- */
- virtual void ExecuteInstructions(int num_instructions) = 0;
-
-private:
- u64 num_instructions = 0; ///< Number of instructions executed
};
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp
index 2ad48dcc7..6afad0e0c 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -6,11 +6,16 @@
#include <memory>
#include <dynarmic/A64/a64.h>
#include <dynarmic/A64/config.h>
+#include "common/logging/log.h"
#include "core/arm/dynarmic/arm_dynarmic.h"
+#include "core/core.h"
#include "core/core_timing.h"
+#include "core/hle/kernel/memory.h"
#include "core/hle/kernel/svc.h"
#include "core/memory.h"
+using Vector = Dynarmic::A64::Vector;
+
class ARM_Dynarmic_Callbacks : public Dynarmic::A64::UserCallbacks {
public:
explicit ARM_Dynarmic_Callbacks(ARM_Dynarmic& parent) : parent(parent) {}
@@ -28,6 +33,9 @@ public:
u64 MemoryRead64(u64 vaddr) override {
return Memory::Read64(vaddr);
}
+ Vector MemoryRead128(u64 vaddr) override {
+ return {Memory::Read64(vaddr), Memory::Read64(vaddr + 8)};
+ }
void MemoryWrite8(u64 vaddr, u8 value) override {
Memory::Write8(vaddr, value);
@@ -41,49 +49,91 @@ public:
void MemoryWrite64(u64 vaddr, u64 value) override {
Memory::Write64(vaddr, value);
}
+ void MemoryWrite128(u64 vaddr, Vector value) override {
+ Memory::Write64(vaddr, value[0]);
+ Memory::Write64(vaddr + 8, value[1]);
+ }
void InterpreterFallback(u64 pc, size_t num_instructions) override {
+ LOG_INFO(Core_ARM, "Unicorn fallback @ 0x%" PRIx64 " for %zu instructions (instr = %08x)",
+ pc, num_instructions, MemoryReadCode(pc));
+
ARM_Interface::ThreadContext ctx;
parent.SaveContext(ctx);
parent.inner_unicorn.LoadContext(ctx);
- parent.inner_unicorn.ExecuteInstructions(num_instructions);
+ parent.inner_unicorn.ExecuteInstructions(static_cast<int>(num_instructions));
parent.inner_unicorn.SaveContext(ctx);
parent.LoadContext(ctx);
num_interpreted_instructions += num_instructions;
}
- void ExceptionRaised(u64 pc, Dynarmic::A64::Exception /*exception*/) override {
- ASSERT_MSG(false, "ExceptionRaised(%" PRIx64 ")", pc);
+ void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override {
+ switch (exception) {
+ case Dynarmic::A64::Exception::WaitForInterrupt:
+ case Dynarmic::A64::Exception::WaitForEvent:
+ case Dynarmic::A64::Exception::SendEvent:
+ case Dynarmic::A64::Exception::SendEventLocal:
+ case Dynarmic::A64::Exception::Yield:
+ return;
+ default:
+ ASSERT_MSG(false, "ExceptionRaised(exception = %zu, pc = %" PRIx64 ")",
+ static_cast<size_t>(exception), pc);
+ }
}
void CallSVC(u32 swi) override {
- printf("svc %x\n", swi);
Kernel::CallSVC(swi);
}
void AddTicks(u64 ticks) override {
- if (ticks > ticks_remaining) {
- ticks_remaining = 0;
- return;
- }
- ticks -= ticks_remaining;
+ CoreTiming::AddTicks(ticks - num_interpreted_instructions);
+ num_interpreted_instructions = 0;
}
u64 GetTicksRemaining() override {
- return ticks_remaining;
+ return std::max(CoreTiming::GetDowncount(), 0);
+ }
+ u64 GetCNTPCT() override {
+ return CoreTiming::GetTicks();
}
ARM_Dynarmic& parent;
- size_t ticks_remaining = 0;
size_t num_interpreted_instructions = 0;
- u64 tpidrr0_el0 = 0;
+ u64 tpidrro_el0 = 0;
+ u64 tpidr_el0 = 0;
};
+std::unique_ptr<Dynarmic::A64::Jit> MakeJit(const std::unique_ptr<ARM_Dynarmic_Callbacks>& cb) {
+ const auto page_table = Core::CurrentProcess()->vm_manager.page_table.pointers.data();
+
+ Dynarmic::A64::UserConfig config;
+ config.callbacks = cb.get();
+ config.tpidrro_el0 = &cb->tpidrro_el0;
+ config.tpidr_el0 = &cb->tpidr_el0;
+ config.dczid_el0 = 4;
+ config.ctr_el0 = 0x8444c004;
+ config.page_table = reinterpret_cast<void**>(page_table);
+ config.page_table_address_space_bits = Memory::ADDRESS_SPACE_BITS;
+ config.silently_mirror_page_table = false;
+
+ return std::make_unique<Dynarmic::A64::Jit>(config);
+}
+
+void ARM_Dynarmic::Run() {
+ ASSERT(Memory::GetCurrentPageTable() == current_page_table);
+
+ jit->Run();
+}
+
+void ARM_Dynarmic::Step() {
+ cb->InterpreterFallback(jit->GetPC(), 1);
+}
+
ARM_Dynarmic::ARM_Dynarmic()
- : cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)),
- jit(Dynarmic::A64::UserConfig{cb.get()}) {
+ : cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), jit(MakeJit(cb)) {
ARM_Interface::ThreadContext ctx;
inner_unicorn.SaveContext(ctx);
LoadContext(ctx);
+ PageTableChanged();
}
ARM_Dynarmic::~ARM_Dynarmic() = default;
@@ -93,28 +143,32 @@ void ARM_Dynarmic::MapBackingMemory(u64 address, size_t size, u8* memory,
inner_unicorn.MapBackingMemory(address, size, memory, perms);
}
+void ARM_Dynarmic::UnmapMemory(u64 address, size_t size) {
+ inner_unicorn.UnmapMemory(address, size);
+}
+
void ARM_Dynarmic::SetPC(u64 pc) {
- jit.SetPC(pc);
+ jit->SetPC(pc);
}
u64 ARM_Dynarmic::GetPC() const {
- return jit.GetPC();
+ return jit->GetPC();
}
u64 ARM_Dynarmic::GetReg(int index) const {
- return jit.GetRegister(index);
+ return jit->GetRegister(index);
}
void ARM_Dynarmic::SetReg(int index, u64 value) {
- jit.SetRegister(index, value);
+ jit->SetRegister(index, value);
}
u128 ARM_Dynarmic::GetExtReg(int index) const {
- return jit.GetVector(index);
+ return jit->GetVector(index);
}
void ARM_Dynarmic::SetExtReg(int index, u128 value) {
- jit.SetVector(index, value);
+ jit->SetVector(index, value);
}
u32 ARM_Dynarmic::GetVFPReg(int /*index*/) const {
@@ -127,58 +181,52 @@ void ARM_Dynarmic::SetVFPReg(int /*index*/, u32 /*value*/) {
}
u32 ARM_Dynarmic::GetCPSR() const {
- return jit.GetPstate();
+ return jit->GetPstate();
}
void ARM_Dynarmic::SetCPSR(u32 cpsr) {
- jit.SetPstate(cpsr);
+ jit->SetPstate(cpsr);
}
u64 ARM_Dynarmic::GetTlsAddress() const {
- return cb->tpidrr0_el0;
+ return cb->tpidrro_el0;
}
void ARM_Dynarmic::SetTlsAddress(u64 address) {
- cb->tpidrr0_el0 = address;
-}
-
-void ARM_Dynarmic::ExecuteInstructions(int num_instructions) {
- cb->ticks_remaining = num_instructions;
- jit.Run();
- CoreTiming::AddTicks(num_instructions - cb->num_interpreted_instructions);
- cb->num_interpreted_instructions = 0;
+ cb->tpidrro_el0 = address;
}
void ARM_Dynarmic::SaveContext(ARM_Interface::ThreadContext& ctx) {
- ctx.cpu_registers = jit.GetRegisters();
- ctx.sp = jit.GetSP();
- ctx.pc = jit.GetPC();
- ctx.cpsr = jit.GetPstate();
- ctx.fpu_registers = jit.GetVectors();
- ctx.fpscr = jit.GetFpcr();
- ctx.tls_address = cb->tpidrr0_el0;
+ ctx.cpu_registers = jit->GetRegisters();
+ ctx.sp = jit->GetSP();
+ ctx.pc = jit->GetPC();
+ ctx.cpsr = jit->GetPstate();
+ ctx.fpu_registers = jit->GetVectors();
+ ctx.fpscr = jit->GetFpcr();
+ ctx.tls_address = cb->tpidrro_el0;
}
void ARM_Dynarmic::LoadContext(const ARM_Interface::ThreadContext& ctx) {
- jit.SetRegisters(ctx.cpu_registers);
- jit.SetSP(ctx.sp);
- jit.SetPC(ctx.pc);
- jit.SetPstate(ctx.cpsr);
- jit.SetVectors(ctx.fpu_registers);
- jit.SetFpcr(ctx.fpscr);
- cb->tpidrr0_el0 = ctx.tls_address;
+ jit->SetRegisters(ctx.cpu_registers);
+ jit->SetSP(ctx.sp);
+ jit->SetPC(ctx.pc);
+ jit->SetPstate(static_cast<u32>(ctx.cpsr));
+ jit->SetVectors(ctx.fpu_registers);
+ jit->SetFpcr(static_cast<u32>(ctx.fpscr));
+ cb->tpidrro_el0 = ctx.tls_address;
}
void ARM_Dynarmic::PrepareReschedule() {
- if (jit.IsExecuting()) {
- jit.HaltExecution();
+ if (jit->IsExecuting()) {
+ jit->HaltExecution();
}
}
void ARM_Dynarmic::ClearInstructionCache() {
- jit.ClearCache();
+ jit->ClearCache();
}
void ARM_Dynarmic::PageTableChanged() {
- UNIMPLEMENTED();
+ jit = MakeJit(cb);
+ current_page_table = Memory::GetCurrentPageTable();
}
diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h
index 43dc56d74..128669d01 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.h
+++ b/src/core/arm/dynarmic/arm_dynarmic.h
@@ -19,7 +19,7 @@ public:
void MapBackingMemory(VAddr address, size_t size, u8* memory,
Kernel::VMAPermission perms) override;
-
+ void UnmapMemory(u64 address, size_t size) override;
void SetPC(u64 pc) override;
u64 GetPC() const override;
u64 GetReg(int index) const override;
@@ -29,6 +29,8 @@ public:
u32 GetVFPReg(int index) const override;
void SetVFPReg(int index, u32 value) override;
u32 GetCPSR() const override;
+ void Run() override;
+ void Step() override;
void SetCPSR(u32 cpsr) override;
VAddr GetTlsAddress() const override;
void SetTlsAddress(VAddr address) override;
@@ -37,7 +39,6 @@ public:
void LoadContext(const ThreadContext& ctx) override;
void PrepareReschedule() override;
- void ExecuteInstructions(int num_instructions) override;
void ClearInstructionCache() override;
void PageTableChanged() override;
@@ -45,6 +46,8 @@ public:
private:
friend class ARM_Dynarmic_Callbacks;
std::unique_ptr<ARM_Dynarmic_Callbacks> cb;
- Dynarmic::A64::Jit jit;
+ std::unique_ptr<Dynarmic::A64::Jit> jit;
ARM_Unicorn inner_unicorn;
+
+ Memory::PageTable* current_page_table = nullptr;
};
diff --git a/src/core/arm/unicorn/arm_unicorn.cpp b/src/core/arm/unicorn/arm_unicorn.cpp
index fd64eab39..b0cdc2403 100644
--- a/src/core/arm/unicorn/arm_unicorn.cpp
+++ b/src/core/arm/unicorn/arm_unicorn.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <unicorn/arm64.h>
#include "common/assert.h"
#include "common/microprofile.h"
@@ -52,7 +53,8 @@ static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int si
void* user_data) {
ARM_Interface::ThreadContext ctx{};
Core::CPU().SaveContext(ctx);
- ASSERT_MSG(false, "Attempted to read from unmapped memory: 0x%llx", addr);
+ ASSERT_MSG(false, "Attempted to read from unmapped memory: 0x%lx, pc=0x%lx, lr=0x%lx", addr,
+ ctx.pc, ctx.cpu_registers[30]);
return {};
}
@@ -76,6 +78,10 @@ void ARM_Unicorn::MapBackingMemory(VAddr address, size_t size, u8* memory,
CHECKED(uc_mem_map_ptr(uc, address, size, static_cast<u32>(perms), memory));
}
+void ARM_Unicorn::UnmapMemory(VAddr address, size_t size) {
+ CHECKED(uc_mem_unmap(uc, address, size));
+}
+
void ARM_Unicorn::SetPC(u64 pc) {
CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &pc));
}
@@ -148,6 +154,14 @@ void ARM_Unicorn::SetTlsAddress(VAddr base) {
CHECKED(uc_reg_write(uc, UC_ARM64_REG_TPIDRRO_EL0, &base));
}
+void ARM_Unicorn::Run() {
+ ExecuteInstructions(std::max(CoreTiming::GetDowncount(), 0));
+}
+
+void ARM_Unicorn::Step() {
+ ExecuteInstructions(1);
+}
+
MICROPROFILE_DEFINE(ARM_Jit, "ARM JIT", "ARM JIT", MP_RGB(255, 64, 64));
void ARM_Unicorn::ExecuteInstructions(int num_instructions) {
diff --git a/src/core/arm/unicorn/arm_unicorn.h b/src/core/arm/unicorn/arm_unicorn.h
index c9a561dec..b99b58e4c 100644
--- a/src/core/arm/unicorn/arm_unicorn.h
+++ b/src/core/arm/unicorn/arm_unicorn.h
@@ -14,6 +14,7 @@ public:
~ARM_Unicorn();
void MapBackingMemory(VAddr address, size_t size, u8* memory,
Kernel::VMAPermission perms) override;
+ void UnmapMemory(VAddr address, size_t size) override;
void SetPC(u64 pc) override;
u64 GetPC() const override;
u64 GetReg(int index) const override;
@@ -29,7 +30,9 @@ public:
void SaveContext(ThreadContext& ctx) override;
void LoadContext(const ThreadContext& ctx) override;
void PrepareReschedule() override;
- void ExecuteInstructions(int num_instructions) override;
+ void ExecuteInstructions(int num_instructions);
+ void Run() override;
+ void Step() override;
void ClearInstructionCache() override;
void PageTableChanged() override{};
diff --git a/src/core/core.cpp b/src/core/core.cpp
index ae1eb2430..11654d4da 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -5,7 +5,9 @@
#include <memory>
#include <utility>
#include "common/logging/log.h"
+#ifdef ARCHITECTURE_x86_64
#include "core/arm/dynarmic/arm_dynarmic.h"
+#endif
#include "core/arm/unicorn/arm_unicorn.h"
#include "core/core.h"
#include "core/core_timing.h"
@@ -24,7 +26,7 @@ namespace Core {
/*static*/ System System::s_instance;
-System::ResultStatus System::RunLoop(int tight_loop) {
+System::ResultStatus System::RunLoop(bool tight_loop) {
status = ResultStatus::Success;
if (!cpu_core) {
return ResultStatus::ErrorNotInitialized;
@@ -38,7 +40,7 @@ System::ResultStatus System::RunLoop(int tight_loop) {
if (GDBStub::GetCpuHaltFlag()) {
if (GDBStub::GetCpuStepFlag()) {
GDBStub::SetCpuStepFlag(false);
- tight_loop = 1;
+ tight_loop = false;
} else {
return ResultStatus::Success;
}
@@ -54,7 +56,11 @@ System::ResultStatus System::RunLoop(int tight_loop) {
PrepareReschedule();
} else {
CoreTiming::Advance();
- cpu_core->Run(tight_loop);
+ if (tight_loop) {
+ cpu_core->Run();
+ } else {
+ cpu_core->Step();
+ }
}
HW::Update();
@@ -64,7 +70,7 @@ System::ResultStatus System::RunLoop(int tight_loop) {
}
System::ResultStatus System::SingleStep() {
- return RunLoop(1);
+ return RunLoop(false);
}
System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& filepath) {
@@ -80,7 +86,6 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file
if (system_mode.second != Loader::ResultStatus::Success) {
LOG_CRITICAL(Core, "Failed to determine system mode (Error %i)!",
static_cast<int>(system_mode.second));
- System::Shutdown();
switch (system_mode.second) {
case Loader::ResultStatus::ErrorEncrypted:
@@ -94,14 +99,15 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file
ResultStatus init_result{Init(emu_window, system_mode.first.get())};
if (init_result != ResultStatus::Success) {
- LOG_CRITICAL(Core, "Failed to initialize system (Error %i)!", init_result);
+ LOG_CRITICAL(Core, "Failed to initialize system (Error %i)!",
+ static_cast<int>(init_result));
System::Shutdown();
return init_result;
}
- const Loader::ResultStatus load_result{app_loader->Load(Kernel::g_current_process)};
+ const Loader::ResultStatus load_result{app_loader->Load(current_process)};
if (Loader::ResultStatus::Success != load_result) {
- LOG_CRITICAL(Core, "Failed to load ROM (Error %i)!", load_result);
+ LOG_CRITICAL(Core, "Failed to load ROM (Error %i)!", static_cast<int>(load_result));
System::Shutdown();
switch (load_result) {
@@ -132,27 +138,34 @@ void System::Reschedule() {
}
reschedule_pending = false;
- Kernel::Reschedule();
+ Core::System::GetInstance().Scheduler().Reschedule();
}
System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
LOG_DEBUG(HW_Memory, "initialized OK");
- switch (Settings::values.cpu_core) {
- case Settings::CpuCore::Unicorn:
- cpu_core = std::make_unique<ARM_Unicorn>();
- break;
- case Settings::CpuCore::Dynarmic:
- default:
- cpu_core = std::make_unique<ARM_Dynarmic>();
- break;
+ CoreTiming::Init();
+
+ current_process = Kernel::Process::Create("main");
+
+ if (Settings::values.use_cpu_jit) {
+#ifdef ARCHITECTURE_x86_64
+ cpu_core = std::make_shared<ARM_Dynarmic>();
+#else
+ cpu_core = std::make_shared<ARM_Unicorn>();
+ LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
+#endif
+ } else {
+ cpu_core = std::make_shared<ARM_Unicorn>();
}
+ gpu_core = std::make_unique<Tegra::GPU>();
+
telemetry_session = std::make_unique<Core::TelemetrySession>();
- CoreTiming::Init();
HW::Init();
Kernel::Init(system_mode);
+ scheduler = std::make_unique<Kernel::Scheduler>(cpu_core.get());
Service::Init();
GDBStub::Init();
@@ -180,15 +193,18 @@ void System::Shutdown() {
perf_results.frametime * 1000.0);
// Shutdown emulation session
- GDBStub::Shutdown();
VideoCore::Shutdown();
+ GDBStub::Shutdown();
Service::Shutdown();
+ scheduler = nullptr;
Kernel::Shutdown();
HW::Shutdown();
- CoreTiming::Shutdown();
+ telemetry_session = nullptr;
+ gpu_core = nullptr;
cpu_core = nullptr;
+ CoreTiming::Shutdown();
+
app_loader = nullptr;
- telemetry_session = nullptr;
LOG_DEBUG(Core, "Shutdown OK");
}
diff --git a/src/core/core.h b/src/core/core.h
index a9a035a1b..ade456cfc 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -7,10 +7,14 @@
#include <memory>
#include <string>
#include "common/common_types.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/scheduler.h"
#include "core/loader/loader.h"
#include "core/memory.h"
#include "core/perf_stats.h"
#include "core/telemetry_session.h"
+#include "video_core/debug_utils/debug_utils.h"
+#include "video_core/gpu.h"
class EmuWindow;
class ARM_Interface;
@@ -50,14 +54,14 @@ public:
* is not required to do a full dispatch with each instruction. NOTE: the number of instructions
* requested is not guaranteed to run, as this will be interrupted preemptively if a hardware
* update is requested (e.g. on a thread switch).
- * @param tight_loop Number of instructions to execute.
- * @return Result status, indicating whethor or not the operation succeeded.
+ * @param tight_loop If false, the CPU single-steps.
+ * @return Result status, indicating whether or not the operation succeeded.
*/
- ResultStatus RunLoop(int tight_loop = 100000);
+ ResultStatus RunLoop(bool tight_loop = true);
/**
* Step the CPU one instruction
- * @return Result status, indicating whethor or not the operation succeeded.
+ * @return Result status, indicating whether or not the operation succeeded.
*/
ResultStatus SingleStep();
@@ -102,6 +106,18 @@ public:
return *cpu_core;
}
+ Tegra::GPU& GPU() {
+ return *gpu_core;
+ }
+
+ Kernel::Scheduler& Scheduler() {
+ return *scheduler;
+ }
+
+ Kernel::SharedPtr<Kernel::Process>& CurrentProcess() {
+ return current_process;
+ }
+
PerfStats perf_stats;
FrameLimiter frame_limiter;
@@ -120,6 +136,14 @@ public:
return *app_loader;
}
+ void SetGPUDebugContext(std::shared_ptr<Tegra::DebugContext> context) {
+ debug_context = std::move(context);
+ }
+
+ std::shared_ptr<Tegra::DebugContext> GetGPUDebugContext() const {
+ return debug_context;
+ }
+
private:
/**
* Initialize the emulated system.
@@ -135,8 +159,13 @@ private:
/// AppLoader used to load the current executing application
std::unique_ptr<Loader::AppLoader> app_loader;
- ///< ARM11 CPU core
- std::unique_ptr<ARM_Interface> cpu_core;
+ std::shared_ptr<ARM_Interface> cpu_core;
+ std::unique_ptr<Kernel::Scheduler> scheduler;
+ std::unique_ptr<Tegra::GPU> gpu_core;
+
+ std::shared_ptr<Tegra::DebugContext> debug_context;
+
+ Kernel::SharedPtr<Kernel::Process> current_process;
/// When true, signals that a reschedule should happen
bool reschedule_pending{};
@@ -158,4 +187,8 @@ inline TelemetrySession& Telemetry() {
return System::GetInstance().TelemetrySession();
}
+inline Kernel::SharedPtr<Kernel::Process>& CurrentProcess() {
+ return System::GetInstance().CurrentProcess();
+}
+
} // namespace Core
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index a0656f0a8..9e1bf2d0e 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -122,7 +122,7 @@ u64 GetTicks() {
}
void AddTicks(u64 ticks) {
- downcount -= ticks;
+ downcount -= static_cast<int>(ticks);
}
u64 GetIdleTicks() {
@@ -208,7 +208,7 @@ void Advance() {
Event evt = std::move(event_queue.front());
std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<Event>());
event_queue.pop_back();
- evt.type->callback(evt.userdata, global_timer - evt.time);
+ evt.type->callback(evt.userdata, static_cast<int>(global_timer - evt.time));
}
is_global_timer_sane = false;
diff --git a/src/core/file_sys/directory.h b/src/core/file_sys/directory.h
new file mode 100644
index 000000000..c7639795e
--- /dev/null
+++ b/src/core/file_sys/directory.h
@@ -0,0 +1,55 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "core/file_sys/filesystem.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// FileSys namespace
+
+namespace FileSys {
+
+// Structure of a directory entry, from
+// http://switchbrew.org/index.php?title=Filesystem_services#DirectoryEntry
+const size_t FILENAME_LENGTH = 0x300;
+struct Entry {
+ char filename[FILENAME_LENGTH];
+ INSERT_PADDING_BYTES(4);
+ EntryType type;
+ INSERT_PADDING_BYTES(3);
+ u64 file_size;
+};
+static_assert(sizeof(Entry) == 0x310, "Directory Entry struct isn't exactly 0x310 bytes long!");
+static_assert(offsetof(Entry, type) == 0x304, "Wrong offset for type in Entry.");
+static_assert(offsetof(Entry, file_size) == 0x308, "Wrong offset for file_size in Entry.");
+
+class DirectoryBackend : NonCopyable {
+public:
+ DirectoryBackend() {}
+ virtual ~DirectoryBackend() {}
+
+ /**
+ * List files contained in the directory
+ * @param count Number of entries to return at once in entries
+ * @param entries Buffer to read data into
+ * @return Number of entries listed
+ */
+ virtual u64 Read(const u64 count, Entry* entries) = 0;
+
+ /// Returns the number of entries still left to read.
+ virtual u64 GetEntryCount() const = 0;
+
+ /**
+ * Close the directory
+ * @return true if the directory closed correctly
+ */
+ virtual bool Close() const = 0;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/directory_backend.h b/src/core/file_sys/directory_backend.h
deleted file mode 100644
index 0c93f2074..000000000
--- a/src/core/file_sys/directory_backend.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <cstddef>
-#include "common/common_types.h"
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// FileSys namespace
-
-namespace FileSys {
-
-// Structure of a directory entry, from http://3dbrew.org/wiki/FSDir:Read#Entry_format
-const size_t FILENAME_LENGTH = 0x20C / 2;
-struct Entry {
- char16_t filename[FILENAME_LENGTH]; // Entry name (UTF-16, null-terminated)
- std::array<char, 9> short_name; // 8.3 file name ('longfilename' -> 'LONGFI~1', null-terminated)
- char unknown1; // unknown (observed values: 0x0A, 0x70, 0xFD)
- std::array<char, 4>
- extension; // 8.3 file extension (set to spaces for directories, null-terminated)
- char unknown2; // unknown (always 0x01)
- char unknown3; // unknown (0x00 or 0x08)
- char is_directory; // directory flag
- char is_hidden; // hidden flag
- char is_archive; // archive flag
- char is_read_only; // read-only flag
- u64 file_size; // file size (for files only)
-};
-static_assert(sizeof(Entry) == 0x228, "Directory Entry struct isn't exactly 0x228 bytes long!");
-static_assert(offsetof(Entry, short_name) == 0x20C, "Wrong offset for short_name in Entry.");
-static_assert(offsetof(Entry, extension) == 0x216, "Wrong offset for extension in Entry.");
-static_assert(offsetof(Entry, is_archive) == 0x21E, "Wrong offset for is_archive in Entry.");
-static_assert(offsetof(Entry, file_size) == 0x220, "Wrong offset for file_size in Entry.");
-
-class DirectoryBackend : NonCopyable {
-public:
- DirectoryBackend() {}
- virtual ~DirectoryBackend() {}
-
- /**
- * List files contained in the directory
- * @param count Number of entries to return at once in entries
- * @param entries Buffer to read data into
- * @return Number of entries listed
- */
- virtual u32 Read(const u32 count, Entry* entries) = 0;
-
- /**
- * Close the directory
- * @return true if the directory closed correctly
- */
- virtual bool Close() const = 0;
-};
-
-} // namespace FileSys
diff --git a/src/core/file_sys/disk_archive.cpp b/src/core/file_sys/disk_archive.cpp
deleted file mode 100644
index 98d80aabc..000000000
--- a/src/core/file_sys/disk_archive.cpp
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <cstdio>
-#include <memory>
-#include "common/common_types.h"
-#include "common/file_util.h"
-#include "common/logging/log.h"
-#include "core/file_sys/disk_archive.h"
-#include "core/file_sys/errors.h"
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// FileSys namespace
-
-namespace FileSys {
-
-ResultVal<size_t> DiskFile::Read(const u64 offset, const size_t length, u8* buffer) const {
- if (!mode.read_flag)
- return ERROR_INVALID_OPEN_FLAGS;
-
- file->Seek(offset, SEEK_SET);
- return MakeResult<size_t>(file->ReadBytes(buffer, length));
-}
-
-ResultVal<size_t> DiskFile::Write(const u64 offset, const size_t length, const bool flush,
- const u8* buffer) const {
- if (!mode.write_flag)
- return ERROR_INVALID_OPEN_FLAGS;
-
- file->Seek(offset, SEEK_SET);
- size_t written = file->WriteBytes(buffer, length);
- if (flush)
- file->Flush();
- return MakeResult<size_t>(written);
-}
-
-u64 DiskFile::GetSize() const {
- return file->GetSize();
-}
-
-bool DiskFile::SetSize(const u64 size) const {
- file->Resize(size);
- file->Flush();
- return true;
-}
-
-bool DiskFile::Close() const {
- return file->Close();
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-
-DiskDirectory::DiskDirectory(const std::string& path) : directory() {
- unsigned size = FileUtil::ScanDirectoryTree(path, directory);
- directory.size = size;
- directory.isDirectory = true;
- children_iterator = directory.children.begin();
-}
-
-u32 DiskDirectory::Read(const u32 count, Entry* entries) {
- u32 entries_read = 0;
-
- while (entries_read < count && children_iterator != directory.children.cend()) {
- const FileUtil::FSTEntry& file = *children_iterator;
- const std::string& filename = file.virtualName;
- Entry& entry = entries[entries_read];
-
- LOG_TRACE(Service_FS, "File %s: size=%llu dir=%d", filename.c_str(), file.size,
- file.isDirectory);
-
- // TODO(Link Mauve): use a proper conversion to UTF-16.
- for (size_t j = 0; j < FILENAME_LENGTH; ++j) {
- entry.filename[j] = filename[j];
- if (!filename[j])
- break;
- }
-
- FileUtil::SplitFilename83(filename, entry.short_name, entry.extension);
-
- entry.is_directory = file.isDirectory;
- entry.is_hidden = (filename[0] == '.');
- entry.is_read_only = 0;
- entry.file_size = file.size;
-
- // We emulate a SD card where the archive bit has never been cleared, as it would be on
- // most user SD cards.
- // Some homebrews (blargSNES for instance) are known to mistakenly use the archive bit as a
- // file bit.
- entry.is_archive = !file.isDirectory;
-
- ++entries_read;
- ++children_iterator;
- }
- return entries_read;
-}
-
-} // namespace FileSys
diff --git a/src/core/file_sys/disk_archive.h b/src/core/file_sys/disk_archive.h
deleted file mode 100644
index eb9166df6..000000000
--- a/src/core/file_sys/disk_archive.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <cstddef>
-#include <memory>
-#include <string>
-#include <vector>
-#include "common/common_types.h"
-#include "common/file_util.h"
-#include "core/file_sys/archive_backend.h"
-#include "core/file_sys/directory_backend.h"
-#include "core/file_sys/file_backend.h"
-#include "core/hle/result.h"
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// FileSys namespace
-
-namespace FileSys {
-
-class DiskFile : public FileBackend {
-public:
- DiskFile(FileUtil::IOFile&& file_, const Mode& mode_)
- : file(new FileUtil::IOFile(std::move(file_))) {
- mode.hex = mode_.hex;
- }
-
- ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override;
- ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override;
- u64 GetSize() const override;
- bool SetSize(u64 size) const override;
- bool Close() const override;
-
- void Flush() const override {
- file->Flush();
- }
-
-protected:
- Mode mode;
- std::unique_ptr<FileUtil::IOFile> file;
-};
-
-class DiskDirectory : public DirectoryBackend {
-public:
- DiskDirectory(const std::string& path);
-
- ~DiskDirectory() override {
- Close();
- }
-
- u32 Read(const u32 count, Entry* entries) override;
-
- bool Close() const override {
- return true;
- }
-
-protected:
- u32 total_entries_in_directory;
- FileUtil::FSTEntry directory;
-
- // We need to remember the last entry we returned, so a subsequent call to Read will continue
- // from the next one. This iterator will always point to the next unread entry.
- std::vector<FileUtil::FSTEntry>::iterator children_iterator;
-};
-
-} // namespace FileSys
diff --git a/src/core/file_sys/disk_filesystem.cpp b/src/core/file_sys/disk_filesystem.cpp
new file mode 100644
index 000000000..4235f3935
--- /dev/null
+++ b/src/core/file_sys/disk_filesystem.cpp
@@ -0,0 +1,228 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include <memory>
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "core/file_sys/disk_filesystem.h"
+#include "core/file_sys/errors.h"
+
+namespace FileSys {
+
+static std::string ModeFlagsToString(Mode mode) {
+ std::string mode_str;
+ u32 mode_flags = static_cast<u32>(mode);
+
+ // Calculate the correct open mode for the file.
+ if ((mode_flags & static_cast<u32>(Mode::Read)) &&
+ (mode_flags & static_cast<u32>(Mode::Write))) {
+ if (mode_flags & static_cast<u32>(Mode::Append))
+ mode_str = "a+";
+ else
+ mode_str = "r+";
+ } else {
+ if (mode_flags & static_cast<u32>(Mode::Read))
+ mode_str = "r";
+ else if (mode_flags & static_cast<u32>(Mode::Append))
+ mode_str = "a";
+ else if (mode_flags & static_cast<u32>(Mode::Write))
+ mode_str = "w";
+ }
+
+ mode_str += "b";
+
+ return mode_str;
+}
+
+std::string Disk_FileSystem::GetName() const {
+ return "Disk";
+}
+
+ResultVal<std::unique_ptr<StorageBackend>> Disk_FileSystem::OpenFile(const std::string& path,
+ Mode mode) const {
+
+ // Calculate the correct open mode for the file.
+ std::string mode_str = ModeFlagsToString(mode);
+
+ std::string full_path = base_directory + path;
+ auto file = std::make_shared<FileUtil::IOFile>(full_path, mode_str.c_str());
+
+ if (!file->IsOpen()) {
+ return ERROR_PATH_NOT_FOUND;
+ }
+
+ return MakeResult<std::unique_ptr<StorageBackend>>(
+ std::make_unique<Disk_Storage>(std::move(file)));
+}
+
+ResultCode Disk_FileSystem::DeleteFile(const Path& path) const {
+ LOG_WARNING(Service_FS, "(STUBBED) called");
+ // TODO(bunnei): Use correct error code
+ return ResultCode(-1);
+}
+
+ResultCode Disk_FileSystem::RenameFile(const Path& src_path, const Path& dest_path) const {
+ LOG_WARNING(Service_FS, "(STUBBED) called");
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
+}
+
+ResultCode Disk_FileSystem::DeleteDirectory(const Path& path) const {
+ LOG_WARNING(Service_FS, "(STUBBED) called");
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
+}
+
+ResultCode Disk_FileSystem::DeleteDirectoryRecursively(const Path& path) const {
+ LOG_WARNING(Service_FS, "(STUBBED) called");
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
+}
+
+ResultCode Disk_FileSystem::CreateFile(const std::string& path, u64 size) const {
+ LOG_WARNING(Service_FS, "(STUBBED) called");
+
+ std::string full_path = base_directory + path;
+ if (size == 0) {
+ FileUtil::CreateEmptyFile(full_path);
+ return RESULT_SUCCESS;
+ }
+
+ FileUtil::IOFile file(full_path, "wb");
+ // Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
+ // We do this by seeking to the right size, then writing a single null byte.
+ if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) {
+ return RESULT_SUCCESS;
+ }
+
+ LOG_ERROR(Service_FS, "Too large file");
+ // TODO(Subv): Find out the correct error code
+ return ResultCode(-1);
+}
+
+ResultCode Disk_FileSystem::CreateDirectory(const std::string& path) const {
+ // TODO(Subv): Perform path validation to prevent escaping the emulator sandbox.
+ std::string full_path = base_directory + path;
+
+ if (FileUtil::CreateDir(full_path)) {
+ return RESULT_SUCCESS;
+ }
+
+ LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", full_path.c_str());
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
+}
+
+ResultCode Disk_FileSystem::RenameDirectory(const Path& src_path, const Path& dest_path) const {
+ LOG_WARNING(Service_FS, "(STUBBED) called");
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
+}
+
+ResultVal<std::unique_ptr<DirectoryBackend>> Disk_FileSystem::OpenDirectory(
+ const std::string& path) const {
+
+ std::string full_path = base_directory + path;
+
+ if (!FileUtil::IsDirectory(full_path)) {
+ // TODO(Subv): Find the correct error code for this.
+ return ResultCode(-1);
+ }
+
+ auto directory = std::make_unique<Disk_Directory>(full_path);
+ return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory));
+}
+
+u64 Disk_FileSystem::GetFreeSpaceSize() const {
+ LOG_WARNING(Service_FS, "(STUBBED) called");
+ return 0;
+}
+
+ResultVal<FileSys::EntryType> Disk_FileSystem::GetEntryType(const std::string& path) const {
+ std::string full_path = base_directory + path;
+ if (!FileUtil::Exists(full_path)) {
+ return ERROR_PATH_NOT_FOUND;
+ }
+
+ if (FileUtil::IsDirectory(full_path))
+ return MakeResult(EntryType::Directory);
+
+ return MakeResult(EntryType::File);
+}
+
+ResultVal<size_t> Disk_Storage::Read(const u64 offset, const size_t length, u8* buffer) const {
+ LOG_TRACE(Service_FS, "called offset=%llu, length=%zu", offset, length);
+ file->Seek(offset, SEEK_SET);
+ return MakeResult<size_t>(file->ReadBytes(buffer, length));
+}
+
+ResultVal<size_t> Disk_Storage::Write(const u64 offset, const size_t length, const bool flush,
+ const u8* buffer) const {
+ LOG_WARNING(Service_FS, "(STUBBED) called");
+ file->Seek(offset, SEEK_SET);
+ size_t written = file->WriteBytes(buffer, length);
+ if (flush) {
+ file->Flush();
+ }
+ return MakeResult<size_t>(written);
+}
+
+u64 Disk_Storage::GetSize() const {
+ return file->GetSize();
+}
+
+bool Disk_Storage::SetSize(const u64 size) const {
+ file->Resize(size);
+ file->Flush();
+ return true;
+}
+
+Disk_Directory::Disk_Directory(const std::string& path) : directory() {
+ unsigned size = FileUtil::ScanDirectoryTree(path, directory);
+ directory.size = size;
+ directory.isDirectory = true;
+ children_iterator = directory.children.begin();
+}
+
+u64 Disk_Directory::Read(const u64 count, Entry* entries) {
+ u64 entries_read = 0;
+
+ while (entries_read < count && children_iterator != directory.children.cend()) {
+ const FileUtil::FSTEntry& file = *children_iterator;
+ const std::string& filename = file.virtualName;
+ Entry& entry = entries[entries_read];
+
+ LOG_TRACE(Service_FS, "File %s: size=%llu dir=%d", filename.c_str(), file.size,
+ file.isDirectory);
+
+ // TODO(Link Mauve): use a proper conversion to UTF-16.
+ for (size_t j = 0; j < FILENAME_LENGTH; ++j) {
+ entry.filename[j] = filename[j];
+ if (!filename[j])
+ break;
+ }
+
+ if (file.isDirectory) {
+ entry.file_size = 0;
+ entry.type = EntryType::Directory;
+ } else {
+ entry.file_size = file.size;
+ entry.type = EntryType::File;
+ }
+
+ ++entries_read;
+ ++children_iterator;
+ }
+ return entries_read;
+}
+
+u64 Disk_Directory::GetEntryCount() const {
+ // We convert the children iterator into a const_iterator to allow template argument deduction
+ // in std::distance.
+ std::vector<FileUtil::FSTEntry>::const_iterator current = children_iterator;
+ return std::distance(current, directory.children.end());
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/disk_filesystem.h b/src/core/file_sys/disk_filesystem.h
new file mode 100644
index 000000000..742d7db1a
--- /dev/null
+++ b/src/core/file_sys/disk_filesystem.h
@@ -0,0 +1,85 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstddef>
+#include <memory>
+#include <string>
+#include "common/common_types.h"
+#include "common/file_util.h"
+#include "core/file_sys/directory.h"
+#include "core/file_sys/filesystem.h"
+#include "core/file_sys/storage.h"
+#include "core/hle/result.h"
+
+namespace FileSys {
+
+class Disk_FileSystem : public FileSystemBackend {
+public:
+ explicit Disk_FileSystem(std::string base_directory)
+ : base_directory(std::move(base_directory)) {}
+
+ std::string GetName() const override;
+
+ ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path,
+ Mode mode) const override;
+ ResultCode DeleteFile(const Path& path) const override;
+ ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override;
+ ResultCode DeleteDirectory(const Path& path) const override;
+ ResultCode DeleteDirectoryRecursively(const Path& path) const override;
+ ResultCode CreateFile(const std::string& path, u64 size) const override;
+ ResultCode CreateDirectory(const std::string& path) const override;
+ ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
+ ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
+ const std::string& path) const override;
+ u64 GetFreeSpaceSize() const override;
+ ResultVal<EntryType> GetEntryType(const std::string& path) const override;
+
+protected:
+ std::string base_directory;
+};
+
+class Disk_Storage : public StorageBackend {
+public:
+ Disk_Storage(std::shared_ptr<FileUtil::IOFile> file) : file(std::move(file)) {}
+
+ ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override;
+ ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override;
+ u64 GetSize() const override;
+ bool SetSize(u64 size) const override;
+ bool Close() const override {
+ return false;
+ }
+ void Flush() const override {}
+
+private:
+ std::shared_ptr<FileUtil::IOFile> file;
+};
+
+class Disk_Directory : public DirectoryBackend {
+public:
+ Disk_Directory(const std::string& path);
+
+ ~Disk_Directory() override {
+ Close();
+ }
+
+ u64 Read(const u64 count, Entry* entries) override;
+ u64 GetEntryCount() const override;
+
+ bool Close() const override {
+ return true;
+ }
+
+protected:
+ u32 total_entries_in_directory;
+ FileUtil::FSTEntry directory;
+
+ // We need to remember the last entry we returned, so a subsequent call to Read will continue
+ // from the next one. This iterator will always point to the next unread entry.
+ std::vector<FileUtil::FSTEntry>::iterator children_iterator;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h
index be3224ef8..0ed7d2a0c 100644
--- a/src/core/file_sys/errors.h
+++ b/src/core/file_sys/errors.h
@@ -10,36 +10,17 @@ namespace FileSys {
namespace ErrCodes {
enum {
- RomFSNotFound = 100,
- ArchiveNotMounted = 101,
- FileNotFound = 112,
- PathNotFound = 113,
- GameCardNotInserted = 141,
- NotFound = 120,
- FileAlreadyExists = 180,
- DirectoryAlreadyExists = 185,
- AlreadyExists = 190,
- InvalidOpenFlags = 230,
- DirectoryNotEmpty = 240,
- NotAFile = 250,
- NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive
- ExeFSSectionNotFound = 567,
- CommandNotAllowed = 630,
- InvalidReadFlag = 700,
- InvalidPath = 702,
- WriteBeyondEnd = 705,
- UnsupportedOpenFlags = 760,
- IncorrectExeFSReadSize = 761,
- UnexpectedFileOrDirectory = 770,
+ NotFound = 1,
};
}
+constexpr ResultCode ERROR_PATH_NOT_FOUND(ErrorModule::FS, ErrCodes::NotFound);
+
// TODO(bunnei): Replace these with correct errors for Switch OS
constexpr ResultCode ERROR_INVALID_PATH(ResultCode(-1));
constexpr ResultCode ERROR_UNSUPPORTED_OPEN_FLAGS(ResultCode(-1));
constexpr ResultCode ERROR_INVALID_OPEN_FLAGS(ResultCode(-1));
constexpr ResultCode ERROR_FILE_NOT_FOUND(ResultCode(-1));
-constexpr ResultCode ERROR_PATH_NOT_FOUND(ResultCode(-1));
constexpr ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY(ResultCode(-1));
constexpr ResultCode ERROR_DIRECTORY_ALREADY_EXISTS(ResultCode(-1));
constexpr ResultCode ERROR_FILE_ALREADY_EXISTS(ResultCode(-1));
diff --git a/src/core/file_sys/archive_backend.cpp b/src/core/file_sys/filesystem.cpp
index 87a240d7a..82fdb3c46 100644
--- a/src/core/file_sys/archive_backend.cpp
+++ b/src/core/file_sys/filesystem.cpp
@@ -1,4 +1,4 @@
-// Copyright 2015 Citra Emulator Project
+// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -7,7 +7,7 @@
#include <sstream>
#include "common/logging/log.h"
#include "common/string_util.h"
-#include "core/file_sys/archive_backend.h"
+#include "core/file_sys/filesystem.h"
#include "core/memory.h"
namespace FileSys {
@@ -119,4 +119,4 @@ std::vector<u8> Path::AsBinary() const {
return {};
}
}
-}
+} // namespace FileSys
diff --git a/src/core/file_sys/archive_backend.h b/src/core/file_sys/filesystem.h
index 58f6c150c..399427ca2 100644
--- a/src/core/file_sys/archive_backend.h
+++ b/src/core/file_sys/filesystem.h
@@ -1,4 +1,4 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -15,7 +15,7 @@
namespace FileSys {
-class FileBackend;
+class StorageBackend;
class DirectoryBackend;
// Path string type
@@ -27,11 +27,15 @@ enum LowPathType : u32 {
Wchar = 4,
};
-union Mode {
- u32 hex;
- BitField<0, 1, u32> read_flag;
- BitField<1, 1, u32> write_flag;
- BitField<2, 1, u32> create_flag;
+enum EntryType : u8 {
+ Directory = 0,
+ File = 1,
+};
+
+enum class Mode : u32 {
+ Read = 1,
+ Write = 2,
+ Append = 4,
};
class Path {
@@ -71,9 +75,9 @@ struct ArchiveFormatInfo {
};
static_assert(std::is_pod<ArchiveFormatInfo>::value, "ArchiveFormatInfo is not POD");
-class ArchiveBackend : NonCopyable {
+class FileSystemBackend : NonCopyable {
public:
- virtual ~ArchiveBackend() {}
+ virtual ~FileSystemBackend() {}
/**
* Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.)
@@ -81,13 +85,12 @@ public:
virtual std::string GetName() const = 0;
/**
- * Open a file specified by its path, using the specified mode
- * @param path Path relative to the archive
- * @param mode Mode to open the file with
- * @return Opened file, or error code
+ * Create a file specified by its path
+ * @param path Path relative to the Archive
+ * @param size The size of the new file, filled with zeroes
+ * @return Result of the operation
*/
- virtual ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
- const Mode& mode) const = 0;
+ virtual ResultCode CreateFile(const std::string& path, u64 size) const = 0;
/**
* Delete a file specified by its path
@@ -97,12 +100,11 @@ public:
virtual ResultCode DeleteFile(const Path& path) const = 0;
/**
- * Rename a File specified by its path
- * @param src_path Source path relative to the archive
- * @param dest_path Destination path relative to the archive
+ * Create a directory specified by its path
+ * @param path Path relative to the archive
* @return Result of the operation
*/
- virtual ResultCode RenameFile(const Path& src_path, const Path& dest_path) const = 0;
+ virtual ResultCode CreateDirectory(const std::string& path) const = 0;
/**
* Delete a directory specified by its path
@@ -119,19 +121,12 @@ public:
virtual ResultCode DeleteDirectoryRecursively(const Path& path) const = 0;
/**
- * Create a file specified by its path
- * @param path Path relative to the Archive
- * @param size The size of the new file, filled with zeroes
- * @return Result of the operation
- */
- virtual ResultCode CreateFile(const Path& path, u64 size) const = 0;
-
- /**
- * Create a directory specified by its path
- * @param path Path relative to the archive
+ * Rename a File specified by its path
+ * @param src_path Source path relative to the archive
+ * @param dest_path Destination path relative to the archive
* @return Result of the operation
*/
- virtual ResultCode CreateDirectory(const Path& path) const = 0;
+ virtual ResultCode RenameFile(const Path& src_path, const Path& dest_path) const = 0;
/**
* Rename a Directory specified by its path
@@ -142,22 +137,38 @@ public:
virtual ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const = 0;
/**
+ * Open a file specified by its path, using the specified mode
+ * @param path Path relative to the archive
+ * @param mode Mode to open the file with
+ * @return Opened file, or error code
+ */
+ virtual ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path,
+ Mode mode) const = 0;
+
+ /**
* Open a directory specified by its path
* @param path Path relative to the archive
* @return Opened directory, or error code
*/
- virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const = 0;
+ virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
+ const std::string& path) const = 0;
/**
* Get the free space
* @return The number of free bytes in the archive
*/
- virtual u64 GetFreeBytes() const = 0;
+ virtual u64 GetFreeSpaceSize() const = 0;
+
+ /**
+ * Get the type of the specified path
+ * @return The type of the specified path or error code
+ */
+ virtual ResultVal<EntryType> GetEntryType(const std::string& path) const = 0;
};
-class ArchiveFactory : NonCopyable {
+class FileSystemFactory : NonCopyable {
public:
- virtual ~ArchiveFactory() {}
+ virtual ~FileSystemFactory() {}
/**
* Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.)
@@ -169,15 +180,14 @@ public:
* @param path Path to the archive
* @return An ArchiveBackend corresponding operating specified archive path.
*/
- virtual ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) = 0;
+ virtual ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) = 0;
/**
* Deletes the archive contents and then re-creates the base folder
* @param path Path to the archive
- * @param format_info Format information for the new archive
* @return ResultCode of the operation, 0 on success
*/
- virtual ResultCode Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) = 0;
+ virtual ResultCode Format(const Path& path) = 0;
/**
* Retrieves the format info about the archive with the specified path
diff --git a/src/core/file_sys/ivfc_archive.cpp b/src/core/file_sys/ivfc_archive.cpp
deleted file mode 100644
index b3c3f2c6f..000000000
--- a/src/core/file_sys/ivfc_archive.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <cstring>
-#include <memory>
-#include "common/common_types.h"
-#include "common/logging/log.h"
-#include "core/file_sys/ivfc_archive.h"
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// FileSys namespace
-
-namespace FileSys {
-
-std::string IVFCArchive::GetName() const {
- return "IVFC";
-}
-
-ResultVal<std::unique_ptr<FileBackend>> IVFCArchive::OpenFile(const Path& path,
- const Mode& mode) const {
- return MakeResult<std::unique_ptr<FileBackend>>(
- std::make_unique<IVFCFile>(romfs_file, data_offset, data_size));
-}
-
-ResultCode IVFCArchive::DeleteFile(const Path& path) const {
- LOG_CRITICAL(Service_FS, "Attempted to delete a file from an IVFC archive (%s).",
- GetName().c_str());
- // TODO(bunnei): Use correct error code
- return ResultCode(-1);
-}
-
-ResultCode IVFCArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
- LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).",
- GetName().c_str());
- // TODO(wwylele): Use correct error code
- return ResultCode(-1);
-}
-
-ResultCode IVFCArchive::DeleteDirectory(const Path& path) const {
- LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).",
- GetName().c_str());
- // TODO(wwylele): Use correct error code
- return ResultCode(-1);
-}
-
-ResultCode IVFCArchive::DeleteDirectoryRecursively(const Path& path) const {
- LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).",
- GetName().c_str());
- // TODO(wwylele): Use correct error code
- return ResultCode(-1);
-}
-
-ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const {
- LOG_CRITICAL(Service_FS, "Attempted to create a file in an IVFC archive (%s).",
- GetName().c_str());
- // TODO(bunnei): Use correct error code
- return ResultCode(-1);
-}
-
-ResultCode IVFCArchive::CreateDirectory(const Path& path) const {
- LOG_CRITICAL(Service_FS, "Attempted to create a directory in an IVFC archive (%s).",
- GetName().c_str());
- // TODO(wwylele): Use correct error code
- return ResultCode(-1);
-}
-
-ResultCode IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
- LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).",
- GetName().c_str());
- // TODO(wwylele): Use correct error code
- return ResultCode(-1);
-}
-
-ResultVal<std::unique_ptr<DirectoryBackend>> IVFCArchive::OpenDirectory(const Path& path) const {
- return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<IVFCDirectory>());
-}
-
-u64 IVFCArchive::GetFreeBytes() const {
- LOG_WARNING(Service_FS, "Attempted to get the free space in an IVFC archive");
- return 0;
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-
-ResultVal<size_t> IVFCFile::Read(const u64 offset, const size_t length, u8* buffer) const {
- LOG_TRACE(Service_FS, "called offset=%llu, length=%zu", offset, length);
- romfs_file->Seek(data_offset + offset, SEEK_SET);
- size_t read_length = (size_t)std::min((u64)length, data_size - offset);
-
- return MakeResult<size_t>(romfs_file->ReadBytes(buffer, read_length));
-}
-
-ResultVal<size_t> IVFCFile::Write(const u64 offset, const size_t length, const bool flush,
- const u8* buffer) const {
- LOG_ERROR(Service_FS, "Attempted to write to IVFC file");
- // TODO(Subv): Find error code
- return MakeResult<size_t>(0);
-}
-
-u64 IVFCFile::GetSize() const {
- return data_size;
-}
-
-bool IVFCFile::SetSize(const u64 size) const {
- LOG_ERROR(Service_FS, "Attempted to set the size of an IVFC file");
- return false;
-}
-
-} // namespace FileSys
diff --git a/src/core/file_sys/path_parser.h b/src/core/file_sys/path_parser.h
index b9f52f65d..184f59d55 100644
--- a/src/core/file_sys/path_parser.h
+++ b/src/core/file_sys/path_parser.h
@@ -6,7 +6,7 @@
#include <string>
#include <vector>
-#include "core/file_sys/archive_backend.h"
+#include "core/file_sys/filesystem.h"
namespace FileSys {
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
new file mode 100644
index 000000000..a6dcebcc3
--- /dev/null
+++ b/src/core/file_sys/program_metadata.cpp
@@ -0,0 +1,114 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cinttypes>
+#include "common/file_util.h"
+#include "common/logging/log.h"
+#include "core/file_sys/program_metadata.h"
+#include "core/loader/loader.h"
+
+namespace FileSys {
+
+Loader::ResultStatus ProgramMetadata::Load(const std::string& file_path) {
+ FileUtil::IOFile file(file_path, "rb");
+ if (!file.IsOpen())
+ return Loader::ResultStatus::Error;
+
+ std::vector<u8> file_data(file.GetSize());
+
+ if (!file.ReadBytes(file_data.data(), file_data.size()))
+ return Loader::ResultStatus::Error;
+
+ Loader::ResultStatus result = Load(file_data);
+ if (result != Loader::ResultStatus::Success)
+ LOG_ERROR(Service_FS, "Failed to load NPDM from file %s!", file_path.c_str());
+
+ return result;
+}
+
+Loader::ResultStatus ProgramMetadata::Load(const std::vector<u8> file_data, size_t offset) {
+ size_t total_size = static_cast<size_t>(file_data.size() - offset);
+ if (total_size < sizeof(Header))
+ return Loader::ResultStatus::Error;
+
+ size_t header_offset = offset;
+ memcpy(&npdm_header, &file_data[offset], sizeof(Header));
+
+ size_t aci_offset = header_offset + npdm_header.aci_offset;
+ size_t acid_offset = header_offset + npdm_header.acid_offset;
+ memcpy(&aci_header, &file_data[aci_offset], sizeof(AciHeader));
+ memcpy(&acid_header, &file_data[acid_offset], sizeof(AcidHeader));
+
+ size_t fac_offset = acid_offset + acid_header.fac_offset;
+ size_t fah_offset = aci_offset + aci_header.fah_offset;
+ memcpy(&acid_file_access, &file_data[fac_offset], sizeof(FileAccessControl));
+ memcpy(&aci_file_access, &file_data[fah_offset], sizeof(FileAccessHeader));
+
+ return Loader::ResultStatus::Success;
+}
+
+bool ProgramMetadata::Is64BitProgram() const {
+ return npdm_header.has_64_bit_instructions;
+}
+
+ProgramAddressSpaceType ProgramMetadata::GetAddressSpaceType() const {
+ return npdm_header.address_space_type;
+}
+
+u8 ProgramMetadata::GetMainThreadPriority() const {
+ return npdm_header.main_thread_priority;
+}
+
+u8 ProgramMetadata::GetMainThreadCore() const {
+ return npdm_header.main_thread_cpu;
+}
+
+u32 ProgramMetadata::GetMainThreadStackSize() const {
+ return npdm_header.main_stack_size;
+}
+
+u64 ProgramMetadata::GetTitleID() const {
+ return aci_header.title_id;
+}
+
+u64 ProgramMetadata::GetFilesystemPermissions() const {
+ return aci_file_access.permissions;
+}
+
+void ProgramMetadata::Print() const {
+ LOG_DEBUG(Service_FS, "Magic: %.4s", npdm_header.magic.data());
+ LOG_DEBUG(Service_FS, "Main thread priority: 0x%02x", npdm_header.main_thread_priority);
+ LOG_DEBUG(Service_FS, "Main thread core: %u", npdm_header.main_thread_cpu);
+ LOG_DEBUG(Service_FS, "Main thread stack size: 0x%x bytes", npdm_header.main_stack_size);
+ LOG_DEBUG(Service_FS, "Process category: %u", npdm_header.process_category);
+ LOG_DEBUG(Service_FS, "Flags: %02x", npdm_header.flags);
+ LOG_DEBUG(Service_FS, " > 64-bit instructions: %s",
+ npdm_header.has_64_bit_instructions ? "YES" : "NO");
+
+ auto address_space = "Unknown";
+ switch (npdm_header.address_space_type) {
+ case ProgramAddressSpaceType::Is64Bit:
+ address_space = "64-bit";
+ break;
+ case ProgramAddressSpaceType::Is32Bit:
+ address_space = "32-bit";
+ break;
+ }
+
+ LOG_DEBUG(Service_FS, " > Address space: %s\n", address_space);
+
+ // Begin ACID printing (potential perms, signed)
+ LOG_DEBUG(Service_FS, "Magic: %.4s", acid_header.magic.data());
+ LOG_DEBUG(Service_FS, "Flags: %02x", acid_header.flags);
+ LOG_DEBUG(Service_FS, " > Is Retail: %s", acid_header.is_retail ? "YES" : "NO");
+ LOG_DEBUG(Service_FS, "Title ID Min: %016" PRIX64, acid_header.title_id_min);
+ LOG_DEBUG(Service_FS, "Title ID Max: %016" PRIX64, acid_header.title_id_max);
+ LOG_DEBUG(Service_FS, "Filesystem Access: %016" PRIX64 "\n", acid_file_access.permissions);
+
+ // Begin ACI0 printing (actual perms, unsigned)
+ LOG_DEBUG(Service_FS, "Magic: %.4s", aci_header.magic.data());
+ LOG_DEBUG(Service_FS, "Title ID: %016" PRIX64, aci_header.title_id);
+ LOG_DEBUG(Service_FS, "Filesystem Access: %016" PRIX64 "\n", aci_file_access.permissions);
+}
+} // namespace FileSys
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
new file mode 100644
index 000000000..b80a08485
--- /dev/null
+++ b/src/core/file_sys/program_metadata.h
@@ -0,0 +1,154 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <string>
+#include <vector>
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+
+namespace Loader {
+enum class ResultStatus;
+}
+
+namespace FileSys {
+
+enum class ProgramAddressSpaceType : u8 {
+ Is64Bit = 1,
+ Is32Bit = 2,
+};
+
+enum class ProgramFilePermission : u64 {
+ MountContent = 1ULL << 0,
+ SaveDataBackup = 1ULL << 5,
+ SdCard = 1ULL << 21,
+ Calibration = 1ULL << 34,
+ Bit62 = 1ULL << 62,
+ Everything = 1ULL << 63,
+};
+
+/**
+ * Helper which implements an interface to parse Program Description Metadata (NPDM)
+ * Data can either be loaded from a file path or with data and an offset into it.
+ */
+class ProgramMetadata {
+public:
+ Loader::ResultStatus Load(const std::string& file_path);
+ Loader::ResultStatus Load(const std::vector<u8> file_data, size_t offset = 0);
+
+ bool Is64BitProgram() const;
+ ProgramAddressSpaceType GetAddressSpaceType() const;
+ u8 GetMainThreadPriority() const;
+ u8 GetMainThreadCore() const;
+ u32 GetMainThreadStackSize() const;
+ u64 GetTitleID() const;
+ u64 GetFilesystemPermissions() const;
+
+ void Print() const;
+
+private:
+ struct Header {
+ std::array<char, 4> magic;
+ std::array<u8, 8> reserved;
+ union {
+ u8 flags;
+
+ BitField<0, 1, u8> has_64_bit_instructions;
+ BitField<1, 3, ProgramAddressSpaceType> address_space_type;
+ BitField<4, 4, u8> reserved_2;
+ };
+ u8 reserved_3;
+ u8 main_thread_priority;
+ u8 main_thread_cpu;
+ std::array<u8, 8> reserved_4;
+ u32_le process_category;
+ u32_le main_stack_size;
+ std::array<u8, 0x10> application_name;
+ std::array<u8, 0x40> reserved_5;
+ u32_le aci_offset;
+ u32_le aci_size;
+ u32_le acid_offset;
+ u32_le acid_size;
+ };
+
+ static_assert(sizeof(Header) == 0x80, "NPDM header structure size is wrong");
+
+ struct AcidHeader {
+ std::array<u8, 0x100> signature;
+ std::array<u8, 0x100> nca_modulus;
+ std::array<char, 4> magic;
+ u32_le nca_size;
+ std::array<u8, 0x4> reserved;
+ union {
+ u32 flags;
+
+ BitField<0, 1, u32> is_retail;
+ BitField<1, 31, u32> flags_unk;
+ };
+ u64_le title_id_min;
+ u64_le title_id_max;
+ u32_le fac_offset;
+ u32_le fac_size;
+ u32_le sac_offset;
+ u32_le sac_size;
+ u32_le kac_offset;
+ u32_le kac_size;
+ INSERT_PADDING_BYTES(0x8);
+ };
+
+ static_assert(sizeof(AcidHeader) == 0x240, "ACID header structure size is wrong");
+
+ struct AciHeader {
+ std::array<char, 4> magic;
+ std::array<u8, 0xC> reserved;
+ u64_le title_id;
+ INSERT_PADDING_BYTES(0x8);
+ u32_le fah_offset;
+ u32_le fah_size;
+ u32_le sac_offset;
+ u32_le sac_size;
+ u32_le kac_offset;
+ u32_le kac_size;
+ INSERT_PADDING_BYTES(0x8);
+ };
+
+ static_assert(sizeof(AciHeader) == 0x40, "ACI0 header structure size is wrong");
+
+#pragma pack(push, 1)
+
+ struct FileAccessControl {
+ u8 version;
+ INSERT_PADDING_BYTES(3);
+ u64_le permissions;
+ std::array<u8, 0x20> unknown;
+ };
+
+ static_assert(sizeof(FileAccessControl) == 0x2C, "FS access control structure size is wrong");
+
+ struct FileAccessHeader {
+ u8 version;
+ INSERT_PADDING_BYTES(3);
+ u64_le permissions;
+ u32_le unk_offset;
+ u32_le unk_size;
+ u32_le unk_offset_2;
+ u32_le unk_size_2;
+ };
+
+ static_assert(sizeof(FileAccessHeader) == 0x1C, "FS access header structure size is wrong");
+
+#pragma pack(pop)
+
+ Header npdm_header;
+ AciHeader aci_header;
+ AcidHeader acid_header;
+
+ FileAccessControl acid_file_access;
+ FileAccessHeader aci_file_access;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
new file mode 100644
index 000000000..b21427948
--- /dev/null
+++ b/src/core/file_sys/romfs_factory.cpp
@@ -0,0 +1,38 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <memory>
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "core/file_sys/romfs_factory.h"
+#include "core/file_sys/romfs_filesystem.h"
+
+namespace FileSys {
+
+RomFS_Factory::RomFS_Factory(Loader::AppLoader& app_loader) {
+ // Load the RomFS from the app
+ if (Loader::ResultStatus::Success != app_loader.ReadRomFS(romfs_file, data_offset, data_size)) {
+ LOG_ERROR(Service_FS, "Unable to read RomFS!");
+ }
+}
+
+ResultVal<std::unique_ptr<FileSystemBackend>> RomFS_Factory::Open(const Path& path) {
+ auto archive = std::make_unique<RomFS_FileSystem>(romfs_file, data_offset, data_size);
+ return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive));
+}
+
+ResultCode RomFS_Factory::Format(const Path& path) {
+ LOG_ERROR(Service_FS, "Unimplemented Format archive %s", GetName().c_str());
+ // TODO(bunnei): Find the right error code for this
+ return ResultCode(-1);
+}
+
+ResultVal<ArchiveFormatInfo> RomFS_Factory::GetFormatInfo(const Path& path) const {
+ LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str());
+ // TODO(bunnei): Find the right error code for this
+ return ResultCode(-1);
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h
new file mode 100644
index 000000000..e0698e642
--- /dev/null
+++ b/src/core/file_sys/romfs_factory.h
@@ -0,0 +1,35 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <vector>
+#include "common/common_types.h"
+#include "core/file_sys/filesystem.h"
+#include "core/hle/result.h"
+#include "core/loader/loader.h"
+
+namespace FileSys {
+
+/// File system interface to the RomFS archive
+class RomFS_Factory final : public FileSystemFactory {
+public:
+ explicit RomFS_Factory(Loader::AppLoader& app_loader);
+
+ std::string GetName() const override {
+ return "ArchiveFactory_RomFS";
+ }
+ ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override;
+ ResultCode Format(const Path& path) override;
+ ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
+
+private:
+ std::shared_ptr<FileUtil::IOFile> romfs_file;
+ u64 data_offset;
+ u64 data_size;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/romfs_filesystem.cpp b/src/core/file_sys/romfs_filesystem.cpp
new file mode 100644
index 000000000..0c6cc3157
--- /dev/null
+++ b/src/core/file_sys/romfs_filesystem.cpp
@@ -0,0 +1,113 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include <memory>
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "core/file_sys/romfs_filesystem.h"
+
+namespace FileSys {
+
+std::string RomFS_FileSystem::GetName() const {
+ return "RomFS";
+}
+
+ResultVal<std::unique_ptr<StorageBackend>> RomFS_FileSystem::OpenFile(const std::string& path,
+ Mode mode) const {
+ return MakeResult<std::unique_ptr<StorageBackend>>(
+ std::make_unique<RomFS_Storage>(romfs_file, data_offset, data_size));
+}
+
+ResultCode RomFS_FileSystem::DeleteFile(const Path& path) const {
+ LOG_CRITICAL(Service_FS, "Attempted to delete a file from an ROMFS archive (%s).",
+ GetName().c_str());
+ // TODO(bunnei): Use correct error code
+ return ResultCode(-1);
+}
+
+ResultCode RomFS_FileSystem::RenameFile(const Path& src_path, const Path& dest_path) const {
+ LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive (%s).",
+ GetName().c_str());
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
+}
+
+ResultCode RomFS_FileSystem::DeleteDirectory(const Path& path) const {
+ LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive (%s).",
+ GetName().c_str());
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
+}
+
+ResultCode RomFS_FileSystem::DeleteDirectoryRecursively(const Path& path) const {
+ LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive (%s).",
+ GetName().c_str());
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
+}
+
+ResultCode RomFS_FileSystem::CreateFile(const std::string& path, u64 size) const {
+ LOG_CRITICAL(Service_FS, "Attempted to create a file in an ROMFS archive (%s).",
+ GetName().c_str());
+ // TODO(bunnei): Use correct error code
+ return ResultCode(-1);
+}
+
+ResultCode RomFS_FileSystem::CreateDirectory(const std::string& path) const {
+ LOG_CRITICAL(Service_FS, "Attempted to create a directory in an ROMFS archive (%s).",
+ GetName().c_str());
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
+}
+
+ResultCode RomFS_FileSystem::RenameDirectory(const Path& src_path, const Path& dest_path) const {
+ LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive (%s).",
+ GetName().c_str());
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
+}
+
+ResultVal<std::unique_ptr<DirectoryBackend>> RomFS_FileSystem::OpenDirectory(
+ const std::string& path) const {
+ LOG_WARNING(Service_FS, "Opening Directory in a ROMFS archive");
+ return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<ROMFSDirectory>());
+}
+
+u64 RomFS_FileSystem::GetFreeSpaceSize() const {
+ LOG_WARNING(Service_FS, "Attempted to get the free space in an ROMFS archive");
+ return 0;
+}
+
+ResultVal<FileSys::EntryType> RomFS_FileSystem::GetEntryType(const std::string& path) const {
+ LOG_CRITICAL(Service_FS, "Called within an ROMFS archive (path %s).", path.c_str());
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
+}
+
+ResultVal<size_t> RomFS_Storage::Read(const u64 offset, const size_t length, u8* buffer) const {
+ LOG_TRACE(Service_FS, "called offset=%llu, length=%zu", offset, length);
+ romfs_file->Seek(data_offset + offset, SEEK_SET);
+ size_t read_length = (size_t)std::min((u64)length, data_size - offset);
+
+ return MakeResult<size_t>(romfs_file->ReadBytes(buffer, read_length));
+}
+
+ResultVal<size_t> RomFS_Storage::Write(const u64 offset, const size_t length, const bool flush,
+ const u8* buffer) const {
+ LOG_ERROR(Service_FS, "Attempted to write to ROMFS file");
+ // TODO(Subv): Find error code
+ return MakeResult<size_t>(0);
+}
+
+u64 RomFS_Storage::GetSize() const {
+ return data_size;
+}
+
+bool RomFS_Storage::SetSize(const u64 size) const {
+ LOG_ERROR(Service_FS, "Attempted to set the size of an ROMFS file");
+ return false;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/ivfc_archive.h b/src/core/file_sys/romfs_filesystem.h
index e6fbdfb1f..3f94c04d0 100644
--- a/src/core/file_sys/ivfc_archive.h
+++ b/src/core/file_sys/romfs_filesystem.h
@@ -1,4 +1,4 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -10,39 +10,38 @@
#include <vector>
#include "common/common_types.h"
#include "common/file_util.h"
-#include "core/file_sys/archive_backend.h"
-#include "core/file_sys/directory_backend.h"
-#include "core/file_sys/file_backend.h"
+#include "core/file_sys/directory.h"
+#include "core/file_sys/filesystem.h"
+#include "core/file_sys/storage.h"
#include "core/hle/result.h"
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// FileSys namespace
-
namespace FileSys {
/**
- * Helper which implements an interface to deal with IVFC images used in some archives
- * This should be subclassed by concrete archive types, which will provide the
- * input data (load the raw IVFC archive) and override any required methods
+ * Helper which implements an interface to deal with Switch .istorage ROMFS images used in some
+ * archives This should be subclassed by concrete archive types, which will provide the input data
+ * (load the raw ROMFS archive) and override any required methods
*/
-class IVFCArchive : public ArchiveBackend {
+class RomFS_FileSystem : public FileSystemBackend {
public:
- IVFCArchive(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size)
+ RomFS_FileSystem(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size)
: romfs_file(file), data_offset(offset), data_size(size) {}
std::string GetName() const override;
- ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
- const Mode& mode) const override;
+ ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path,
+ Mode mode) const override;
ResultCode DeleteFile(const Path& path) const override;
ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override;
ResultCode DeleteDirectory(const Path& path) const override;
ResultCode DeleteDirectoryRecursively(const Path& path) const override;
- ResultCode CreateFile(const Path& path, u64 size) const override;
- ResultCode CreateDirectory(const Path& path) const override;
+ ResultCode CreateFile(const std::string& path, u64 size) const override;
+ ResultCode CreateDirectory(const std::string& path) const override;
ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
- ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
- u64 GetFreeBytes() const override;
+ ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
+ const std::string& path) const override;
+ u64 GetFreeSpaceSize() const override;
+ ResultVal<EntryType> GetEntryType(const std::string& path) const override;
protected:
std::shared_ptr<FileUtil::IOFile> romfs_file;
@@ -50,9 +49,9 @@ protected:
u64 data_size;
};
-class IVFCFile : public FileBackend {
+class RomFS_Storage : public StorageBackend {
public:
- IVFCFile(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size)
+ RomFS_Storage(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size)
: romfs_file(file), data_offset(offset), data_size(size) {}
ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override;
@@ -70,9 +69,12 @@ private:
u64 data_size;
};
-class IVFCDirectory : public DirectoryBackend {
+class ROMFSDirectory : public DirectoryBackend {
public:
- u32 Read(const u32 count, Entry* entries) override {
+ u64 Read(const u64 count, Entry* entries) override {
+ return 0;
+ }
+ u64 GetEntryCount() const override {
return 0;
}
bool Close() const override {
diff --git a/src/core/file_sys/savedata_archive.cpp b/src/core/file_sys/savedata_archive.cpp
deleted file mode 100644
index d7b012f6e..000000000
--- a/src/core/file_sys/savedata_archive.cpp
+++ /dev/null
@@ -1,330 +0,0 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/file_util.h"
-#include "core/file_sys/disk_archive.h"
-#include "core/file_sys/errors.h"
-#include "core/file_sys/path_parser.h"
-#include "core/file_sys/savedata_archive.h"
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// FileSys namespace
-
-namespace FileSys {
-
-ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& path,
- const Mode& mode) const {
- LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex);
-
- const PathParser path_parser(path);
-
- if (!path_parser.IsValid()) {
- LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
- return ERROR_INVALID_PATH;
- }
-
- if (mode.hex == 0) {
- LOG_ERROR(Service_FS, "Empty open mode");
- return ERROR_UNSUPPORTED_OPEN_FLAGS;
- }
-
- if (mode.create_flag && !mode.write_flag) {
- LOG_ERROR(Service_FS, "Create flag set but write flag not set");
- return ERROR_UNSUPPORTED_OPEN_FLAGS;
- }
-
- const auto full_path = path_parser.BuildHostPath(mount_point);
-
- switch (path_parser.GetHostStatus(mount_point)) {
- case PathParser::InvalidMountPoint:
- LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
- return ERROR_FILE_NOT_FOUND;
- case PathParser::PathNotFound:
- LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
- return ERROR_PATH_NOT_FOUND;
- case PathParser::FileInPath:
- case PathParser::DirectoryFound:
- LOG_ERROR(Service_FS, "Unexpected file or directory in %s", full_path.c_str());
- return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
- case PathParser::NotFound:
- if (!mode.create_flag) {
- LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.",
- full_path.c_str());
- return ERROR_FILE_NOT_FOUND;
- } else {
- // Create the file
- FileUtil::CreateEmptyFile(full_path);
- }
- break;
- case PathParser::FileFound:
- break; // Expected 'success' case
- }
-
- FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb");
- if (!file.IsOpen()) {
- LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str());
- return ERROR_FILE_NOT_FOUND;
- }
-
- auto disk_file = std::make_unique<DiskFile>(std::move(file), mode);
- return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file));
-}
-
-ResultCode SaveDataArchive::DeleteFile(const Path& path) const {
- const PathParser path_parser(path);
-
- if (!path_parser.IsValid()) {
- LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
- return ERROR_INVALID_PATH;
- }
-
- const auto full_path = path_parser.BuildHostPath(mount_point);
-
- switch (path_parser.GetHostStatus(mount_point)) {
- case PathParser::InvalidMountPoint:
- LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
- return ERROR_FILE_NOT_FOUND;
- case PathParser::PathNotFound:
- LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
- return ERROR_PATH_NOT_FOUND;
- case PathParser::FileInPath:
- case PathParser::DirectoryFound:
- case PathParser::NotFound:
- LOG_ERROR(Service_FS, "File not found %s", full_path.c_str());
- return ERROR_FILE_NOT_FOUND;
- case PathParser::FileFound:
- break; // Expected 'success' case
- }
-
- if (FileUtil::Delete(full_path)) {
- return RESULT_SUCCESS;
- }
-
- LOG_CRITICAL(Service_FS, "(unreachable) Unknown error deleting %s", full_path.c_str());
- return ERROR_FILE_NOT_FOUND;
-}
-
-ResultCode SaveDataArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
- const PathParser path_parser_src(src_path);
-
- // TODO: Verify these return codes with HW
- if (!path_parser_src.IsValid()) {
- LOG_ERROR(Service_FS, "Invalid src path %s", src_path.DebugStr().c_str());
- return ERROR_INVALID_PATH;
- }
-
- const PathParser path_parser_dest(dest_path);
-
- if (!path_parser_dest.IsValid()) {
- LOG_ERROR(Service_FS, "Invalid dest path %s", dest_path.DebugStr().c_str());
- return ERROR_INVALID_PATH;
- }
-
- const auto src_path_full = path_parser_src.BuildHostPath(mount_point);
- const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point);
-
- if (FileUtil::Rename(src_path_full, dest_path_full)) {
- return RESULT_SUCCESS;
- }
-
- // TODO(bunnei): Use correct error code
- return ResultCode(-1);
-}
-
-template <typename T>
-static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mount_point,
- T deleter) {
- const PathParser path_parser(path);
-
- if (!path_parser.IsValid()) {
- LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
- return ERROR_INVALID_PATH;
- }
-
- if (path_parser.IsRootDirectory())
- return ERROR_DIRECTORY_NOT_EMPTY;
-
- const auto full_path = path_parser.BuildHostPath(mount_point);
-
- switch (path_parser.GetHostStatus(mount_point)) {
- case PathParser::InvalidMountPoint:
- LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
- return ERROR_PATH_NOT_FOUND;
- case PathParser::PathNotFound:
- case PathParser::NotFound:
- LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
- return ERROR_PATH_NOT_FOUND;
- case PathParser::FileInPath:
- case PathParser::FileFound:
- LOG_ERROR(Service_FS, "Unexpected file or directory %s", full_path.c_str());
- return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
- case PathParser::DirectoryFound:
- break; // Expected 'success' case
- }
-
- if (deleter(full_path)) {
- return RESULT_SUCCESS;
- }
-
- LOG_ERROR(Service_FS, "Directory not empty %s", full_path.c_str());
- return ERROR_DIRECTORY_NOT_EMPTY;
-}
-
-ResultCode SaveDataArchive::DeleteDirectory(const Path& path) const {
- return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir);
-}
-
-ResultCode SaveDataArchive::DeleteDirectoryRecursively(const Path& path) const {
- return DeleteDirectoryHelper(
- path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); });
-}
-
-ResultCode SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) const {
- const PathParser path_parser(path);
-
- if (!path_parser.IsValid()) {
- LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
- return ERROR_INVALID_PATH;
- }
-
- const auto full_path = path_parser.BuildHostPath(mount_point);
-
- switch (path_parser.GetHostStatus(mount_point)) {
- case PathParser::InvalidMountPoint:
- LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
- return ERROR_FILE_NOT_FOUND;
- case PathParser::PathNotFound:
- LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
- return ERROR_PATH_NOT_FOUND;
- case PathParser::FileInPath:
- LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
- return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
- case PathParser::DirectoryFound:
- case PathParser::FileFound:
- LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
- return ERROR_FILE_ALREADY_EXISTS;
- case PathParser::NotFound:
- break; // Expected 'success' case
- }
-
- if (size == 0) {
- FileUtil::CreateEmptyFile(full_path);
- return RESULT_SUCCESS;
- }
-
- FileUtil::IOFile file(full_path, "wb");
- // Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
- // We do this by seeking to the right size, then writing a single null byte.
- if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) {
- return RESULT_SUCCESS;
- }
-
- LOG_ERROR(Service_FS, "Too large file");
-
- // TODO(bunnei): Use correct error code
- return ResultCode(-1);
-}
-
-ResultCode SaveDataArchive::CreateDirectory(const Path& path) const {
- const PathParser path_parser(path);
-
- if (!path_parser.IsValid()) {
- LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
- return ERROR_INVALID_PATH;
- }
-
- const auto full_path = path_parser.BuildHostPath(mount_point);
-
- switch (path_parser.GetHostStatus(mount_point)) {
- case PathParser::InvalidMountPoint:
- LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
- return ERROR_FILE_NOT_FOUND;
- case PathParser::PathNotFound:
- LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
- return ERROR_PATH_NOT_FOUND;
- case PathParser::FileInPath:
- LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
- return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
- case PathParser::DirectoryFound:
- case PathParser::FileFound:
- LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
- return ERROR_DIRECTORY_ALREADY_EXISTS;
- case PathParser::NotFound:
- break; // Expected 'success' case
- }
-
- if (FileUtil::CreateDir(mount_point + path.AsString())) {
- return RESULT_SUCCESS;
- }
-
- LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", mount_point.c_str());
-
- // TODO(bunnei): Use correct error code
- return ResultCode(-1);
-}
-
-ResultCode SaveDataArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
- const PathParser path_parser_src(src_path);
-
- // TODO: Verify these return codes with HW
- if (!path_parser_src.IsValid()) {
- LOG_ERROR(Service_FS, "Invalid src path %s", src_path.DebugStr().c_str());
- return ERROR_INVALID_PATH;
- }
-
- const PathParser path_parser_dest(dest_path);
-
- if (!path_parser_dest.IsValid()) {
- LOG_ERROR(Service_FS, "Invalid dest path %s", dest_path.DebugStr().c_str());
- return ERROR_INVALID_PATH;
- }
-
- const auto src_path_full = path_parser_src.BuildHostPath(mount_point);
- const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point);
-
- if (FileUtil::Rename(src_path_full, dest_path_full)) {
- return RESULT_SUCCESS;
- }
-
- // TODO(bunnei): Use correct error code
- return ResultCode(-1);
-}
-
-ResultVal<std::unique_ptr<DirectoryBackend>> SaveDataArchive::OpenDirectory(
- const Path& path) const {
- const PathParser path_parser(path);
-
- if (!path_parser.IsValid()) {
- LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
- return ERROR_INVALID_PATH;
- }
-
- const auto full_path = path_parser.BuildHostPath(mount_point);
-
- switch (path_parser.GetHostStatus(mount_point)) {
- case PathParser::InvalidMountPoint:
- LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
- return ERROR_FILE_NOT_FOUND;
- case PathParser::PathNotFound:
- case PathParser::NotFound:
- LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
- return ERROR_PATH_NOT_FOUND;
- case PathParser::FileInPath:
- case PathParser::FileFound:
- LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
- return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
- case PathParser::DirectoryFound:
- break; // Expected 'success' case
- }
-
- auto directory = std::make_unique<DiskDirectory>(full_path);
- return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory));
-}
-
-u64 SaveDataArchive::GetFreeBytes() const {
- // TODO: Stubbed to return 1GiB
- return 1024 * 1024 * 1024;
-}
-
-} // namespace FileSys
diff --git a/src/core/file_sys/savedata_archive.h b/src/core/file_sys/savedata_archive.h
deleted file mode 100644
index 176d35710..000000000
--- a/src/core/file_sys/savedata_archive.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 <string>
-#include "core/file_sys/archive_backend.h"
-#include "core/file_sys/directory_backend.h"
-#include "core/file_sys/file_backend.h"
-#include "core/hle/result.h"
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// FileSys namespace
-
-namespace FileSys {
-
-/// Archive backend for general save data archive type (SaveData and SystemSaveData)
-class SaveDataArchive : public ArchiveBackend {
-public:
- explicit SaveDataArchive(const std::string& mount_point_) : mount_point(mount_point_) {}
-
- std::string GetName() const override {
- return "SaveDataArchive: " + mount_point;
- }
-
- ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
- const Mode& mode) const override;
- ResultCode DeleteFile(const Path& path) const override;
- ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override;
- ResultCode DeleteDirectory(const Path& path) const override;
- ResultCode DeleteDirectoryRecursively(const Path& path) const override;
- ResultCode CreateFile(const Path& path, u64 size) const override;
- ResultCode CreateDirectory(const Path& path) const override;
- ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
- ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
- u64 GetFreeBytes() const override;
-
-protected:
- std::string mount_point;
-};
-
-} // namespace FileSys
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
new file mode 100644
index 000000000..14868fed2
--- /dev/null
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -0,0 +1,57 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cinttypes>
+#include <memory>
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/file_sys/disk_filesystem.h"
+#include "core/file_sys/savedata_factory.h"
+#include "core/hle/kernel/process.h"
+
+namespace FileSys {
+
+SaveData_Factory::SaveData_Factory(std::string nand_directory)
+ : nand_directory(std::move(nand_directory)) {}
+
+ResultVal<std::unique_ptr<FileSystemBackend>> SaveData_Factory::Open(const Path& path) {
+ std::string save_directory = GetFullPath();
+ // Return an error if the save data doesn't actually exist.
+ if (!FileUtil::IsDirectory(save_directory)) {
+ // TODO(Subv): Find out correct error code.
+ return ResultCode(-1);
+ }
+
+ auto archive = std::make_unique<Disk_FileSystem>(save_directory);
+ return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive));
+}
+
+ResultCode SaveData_Factory::Format(const Path& path) {
+ LOG_WARNING(Service_FS, "Format archive %s", GetName().c_str());
+ // Create the save data directory.
+ if (!FileUtil::CreateFullPath(GetFullPath())) {
+ // TODO(Subv): Find the correct error code.
+ return ResultCode(-1);
+ }
+
+ return RESULT_SUCCESS;
+}
+
+ResultVal<ArchiveFormatInfo> SaveData_Factory::GetFormatInfo(const Path& path) const {
+ LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str());
+ // TODO(bunnei): Find the right error code for this
+ return ResultCode(-1);
+}
+
+std::string SaveData_Factory::GetFullPath() const {
+ u64 title_id = Core::CurrentProcess()->program_id;
+ // TODO(Subv): Somehow obtain this value.
+ u32 user = 0;
+ return Common::StringFromFormat("%ssave/%016" PRIX64 "/%08X/", nand_directory.c_str(), title_id,
+ user);
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h
new file mode 100644
index 000000000..73a42aab6
--- /dev/null
+++ b/src/core/file_sys/savedata_factory.h
@@ -0,0 +1,33 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include "common/common_types.h"
+#include "core/file_sys/filesystem.h"
+#include "core/hle/result.h"
+
+namespace FileSys {
+
+/// File system interface to the SaveData archive
+class SaveData_Factory final : public FileSystemFactory {
+public:
+ explicit SaveData_Factory(std::string nand_directory);
+
+ std::string GetName() const override {
+ return "SaveData_Factory";
+ }
+ ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override;
+ ResultCode Format(const Path& path) override;
+ ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
+
+private:
+ std::string nand_directory;
+
+ std::string GetFullPath() const;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp
new file mode 100644
index 000000000..00e80d2a7
--- /dev/null
+++ b/src/core/file_sys/sdmc_factory.cpp
@@ -0,0 +1,40 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cinttypes>
+#include <memory>
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/file_sys/disk_filesystem.h"
+#include "core/file_sys/sdmc_factory.h"
+
+namespace FileSys {
+
+SDMC_Factory::SDMC_Factory(std::string sd_directory) : sd_directory(std::move(sd_directory)) {}
+
+ResultVal<std::unique_ptr<FileSystemBackend>> SDMC_Factory::Open(const Path& path) {
+ // Create the SD Card directory if it doesn't already exist.
+ if (!FileUtil::IsDirectory(sd_directory)) {
+ FileUtil::CreateFullPath(sd_directory);
+ }
+
+ auto archive = std::make_unique<Disk_FileSystem>(sd_directory);
+ return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive));
+}
+
+ResultCode SDMC_Factory::Format(const Path& path) {
+ LOG_ERROR(Service_FS, "Unimplemented Format archive %s", GetName().c_str());
+ // TODO(Subv): Find the right error code for this
+ return ResultCode(-1);
+}
+
+ResultVal<ArchiveFormatInfo> SDMC_Factory::GetFormatInfo(const Path& path) const {
+ LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str());
+ // TODO(bunnei): Find the right error code for this
+ return ResultCode(-1);
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h
new file mode 100644
index 000000000..93becda25
--- /dev/null
+++ b/src/core/file_sys/sdmc_factory.h
@@ -0,0 +1,31 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include "common/common_types.h"
+#include "core/file_sys/filesystem.h"
+#include "core/hle/result.h"
+
+namespace FileSys {
+
+/// File system interface to the SDCard archive
+class SDMC_Factory final : public FileSystemFactory {
+public:
+ explicit SDMC_Factory(std::string sd_directory);
+
+ std::string GetName() const override {
+ return "SDMC_Factory";
+ }
+ ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override;
+ ResultCode Format(const Path& path) override;
+ ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
+
+private:
+ std::string sd_directory;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/file_backend.h b/src/core/file_sys/storage.h
index 5e7c2bab4..2a6811831 100644
--- a/src/core/file_sys/file_backend.h
+++ b/src/core/file_sys/storage.h
@@ -1,4 +1,4 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -8,15 +8,12 @@
#include "common/common_types.h"
#include "core/hle/result.h"
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// FileSys namespace
-
namespace FileSys {
-class FileBackend : NonCopyable {
+class StorageBackend : NonCopyable {
public:
- FileBackend() {}
- virtual ~FileBackend() {}
+ StorageBackend() {}
+ virtual ~StorageBackend() {}
/**
* Read data from the file
@@ -39,10 +36,9 @@ public:
const u8* buffer) const = 0;
/**
- * Get the size of the file in bytes
- * @return Size of the file in bytes
+ * Flushes the file
*/
- virtual u64 GetSize() const = 0;
+ virtual void Flush() const = 0;
/**
* Set the size of the file in bytes
@@ -52,15 +48,16 @@ public:
virtual bool SetSize(u64 size) const = 0;
/**
- * Close the file
- * @return true if the file closed correctly
+ * Get the size of the file in bytes
+ * @return Size of the file in bytes
*/
- virtual bool Close() const = 0;
+ virtual u64 GetSize() const = 0;
/**
- * Flushes the file
+ * Close the file
+ * @return true if the file closed correctly
*/
- virtual void Flush() const = 0;
+ virtual bool Close() const = 0;
};
} // namespace FileSys
diff --git a/src/core/file_sys/title_metadata.cpp b/src/core/file_sys/title_metadata.cpp
deleted file mode 100644
index e29ba6064..000000000
--- a/src/core/file_sys/title_metadata.cpp
+++ /dev/null
@@ -1,163 +0,0 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <cinttypes>
-#include "common/alignment.h"
-#include "common/file_util.h"
-#include "common/logging/log.h"
-#include "core/file_sys/title_metadata.h"
-#include "core/loader/loader.h"
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// FileSys namespace
-
-namespace FileSys {
-
-static u32 GetSignatureSize(u32 signature_type) {
- switch (signature_type) {
- case Rsa4096Sha1:
- case Rsa4096Sha256:
- return 0x200;
-
- case Rsa2048Sha1:
- case Rsa2048Sha256:
- return 0x100;
-
- case EllipticSha1:
- case EcdsaSha256:
- return 0x3C;
- }
-}
-
-Loader::ResultStatus TitleMetadata::Load() {
- FileUtil::IOFile file(filepath, "rb");
- if (!file.IsOpen())
- return Loader::ResultStatus::Error;
-
- if (!file.ReadBytes(&signature_type, sizeof(u32_be)))
- return Loader::ResultStatus::Error;
-
- // Signature lengths are variable, and the body follows the signature
- u32 signature_size = GetSignatureSize(signature_type);
-
- tmd_signature.resize(signature_size);
- if (!file.ReadBytes(&tmd_signature[0], signature_size))
- return Loader::ResultStatus::Error;
-
- // The TMD body start position is rounded to the nearest 0x40 after the signature
- size_t body_start = Common::AlignUp(signature_size + sizeof(u32), 0x40);
- file.Seek(body_start, SEEK_SET);
-
- // Read our TMD body, then load the amount of ContentChunks specified
- if (file.ReadBytes(&tmd_body, sizeof(TitleMetadata::Body)) != sizeof(TitleMetadata::Body))
- return Loader::ResultStatus::Error;
-
- for (u16 i = 0; i < tmd_body.content_count; i++) {
- ContentChunk chunk;
- if (file.ReadBytes(&chunk, sizeof(ContentChunk)) == sizeof(ContentChunk)) {
- tmd_chunks.push_back(chunk);
- } else {
- LOG_ERROR(Service_FS, "Malformed TMD %s, failed to load content chunk index %u!",
- filepath.c_str(), i);
- return Loader::ResultStatus::ErrorInvalidFormat;
- }
- }
-
- return Loader::ResultStatus::Success;
-}
-
-Loader::ResultStatus TitleMetadata::Save() {
- UNIMPLEMENTED();
- return Loader::ResultStatus::Success;
-}
-
-u64 TitleMetadata::GetTitleID() const {
- return tmd_body.title_id;
-}
-
-u32 TitleMetadata::GetTitleType() const {
- return tmd_body.title_type;
-}
-
-u16 TitleMetadata::GetTitleVersion() const {
- return tmd_body.title_version;
-}
-
-u64 TitleMetadata::GetSystemVersion() const {
- return tmd_body.system_version;
-}
-
-size_t TitleMetadata::GetContentCount() const {
- return tmd_chunks.size();
-}
-
-u32 TitleMetadata::GetBootContentID() const {
- return tmd_chunks[TMDContentIndex::Main].id;
-}
-
-u32 TitleMetadata::GetManualContentID() const {
- return tmd_chunks[TMDContentIndex::Manual].id;
-}
-
-u32 TitleMetadata::GetDLPContentID() const {
- return tmd_chunks[TMDContentIndex::DLP].id;
-}
-
-void TitleMetadata::SetTitleID(u64 title_id) {
- tmd_body.title_id = title_id;
-}
-
-void TitleMetadata::SetTitleType(u32 type) {
- tmd_body.title_type = type;
-}
-
-void TitleMetadata::SetTitleVersion(u16 version) {
- tmd_body.title_version = version;
-}
-
-void TitleMetadata::SetSystemVersion(u64 version) {
- tmd_body.system_version = version;
-}
-
-void TitleMetadata::AddContentChunk(const ContentChunk& chunk) {
- tmd_chunks.push_back(chunk);
-}
-
-void TitleMetadata::Print() const {
- LOG_DEBUG(Service_FS, "%s - %u chunks", filepath.c_str(),
- static_cast<u32>(tmd_body.content_count));
-
- // Content info describes ranges of content chunks
- LOG_DEBUG(Service_FS, "Content info:");
- for (size_t i = 0; i < tmd_body.contentinfo.size(); i++) {
- if (tmd_body.contentinfo[i].command_count == 0)
- break;
-
- LOG_DEBUG(Service_FS, " Index %04X, Command Count %04X",
- static_cast<u32>(tmd_body.contentinfo[i].index),
- static_cast<u32>(tmd_body.contentinfo[i].command_count));
- }
-
- // For each content info, print their content chunk range
- for (size_t i = 0; i < tmd_body.contentinfo.size(); i++) {
- u16 index = static_cast<u16>(tmd_body.contentinfo[i].index);
- u16 count = static_cast<u16>(tmd_body.contentinfo[i].command_count);
-
- if (count == 0)
- continue;
-
- LOG_DEBUG(Service_FS, "Content chunks for content info index %zu:", i);
- for (u16 j = index; j < index + count; j++) {
- // Don't attempt to print content we don't have
- if (j > tmd_body.content_count)
- break;
-
- const ContentChunk& chunk = tmd_chunks[j];
- LOG_DEBUG(Service_FS, " ID %08X, Index %04X, Type %04x, Size %016" PRIX64,
- static_cast<u32>(chunk.id), static_cast<u32>(chunk.index),
- static_cast<u32>(chunk.type), static_cast<u64>(chunk.size));
- }
- }
-}
-} // namespace FileSys
diff --git a/src/core/file_sys/title_metadata.h b/src/core/file_sys/title_metadata.h
deleted file mode 100644
index 1fc157bf3..000000000
--- a/src/core/file_sys/title_metadata.h
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <string>
-#include <vector>
-#include "common/common_types.h"
-#include "common/swap.h"
-
-namespace Loader {
-enum class ResultStatus;
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// FileSys namespace
-
-namespace FileSys {
-
-enum TMDSignatureType : u32 {
- Rsa4096Sha1 = 0x10000,
- Rsa2048Sha1 = 0x10001,
- EllipticSha1 = 0x10002,
- Rsa4096Sha256 = 0x10003,
- Rsa2048Sha256 = 0x10004,
- EcdsaSha256 = 0x10005
-};
-
-enum TMDContentTypeFlag : u16 {
- Encrypted = 1 << 1,
- Disc = 1 << 2,
- CFM = 1 << 3,
- Optional = 1 << 14,
- Shared = 1 << 15
-};
-
-/**
- * Helper which implements an interface to read and write Title Metadata (TMD) files.
- * If a file path is provided and the file exists, it can be parsed and used, otherwise
- * it must be created. The TMD file can then be interpreted, modified and/or saved.
- */
-class TitleMetadata {
-public:
- struct ContentChunk {
- u32_be id;
- u16_be index;
- u16_be type;
- u64_be size;
- std::array<u8, 0x20> hash;
- };
-
- static_assert(sizeof(ContentChunk) == 0x30, "TMD ContentChunk structure size is wrong");
-
- struct ContentInfo {
- u16_be index;
- u16_be command_count;
- std::array<u8, 0x20> hash;
- };
-
- static_assert(sizeof(ContentInfo) == 0x24, "TMD ContentInfo structure size is wrong");
-
-#pragma pack(push, 1)
-
- struct Body {
- std::array<u8, 0x40> issuer;
- u8 version;
- u8 ca_crl_version;
- u8 signer_crl_version;
- u8 reserved;
- u64_be system_version;
- u64_be title_id;
- u32_be title_type;
- u16_be group_id;
- u32_be savedata_size;
- u32_be srl_private_savedata_size;
- std::array<u8, 4> reserved_2;
- u8 srl_flag;
- std::array<u8, 0x31> reserved_3;
- u32_be access_rights;
- u16_be title_version;
- u16_be content_count;
- u16_be boot_content;
- std::array<u8, 2> reserved_4;
- std::array<u8, 0x20> contentinfo_hash;
- std::array<ContentInfo, 64> contentinfo;
- };
-
- static_assert(sizeof(Body) == 0x9C4, "TMD body structure size is wrong");
-
-#pragma pack(pop)
-
- explicit TitleMetadata(std::string& path) : filepath(std::move(path)) {}
- Loader::ResultStatus Load();
- Loader::ResultStatus Save();
-
- u64 GetTitleID() const;
- u32 GetTitleType() const;
- u16 GetTitleVersion() const;
- u64 GetSystemVersion() const;
- size_t GetContentCount() const;
- u32 GetBootContentID() const;
- u32 GetManualContentID() const;
- u32 GetDLPContentID() const;
-
- void SetTitleID(u64 title_id);
- void SetTitleType(u32 type);
- void SetTitleVersion(u16 version);
- void SetSystemVersion(u64 version);
- void AddContentChunk(const ContentChunk& chunk);
-
- void Print() const;
-
-private:
- enum TMDContentIndex { Main = 0, Manual = 1, DLP = 2 };
-
- Body tmd_body;
- u32_be signature_type;
- std::vector<u8> tmd_signature;
- std::vector<ContentChunk> tmd_chunks;
-
- std::string filepath;
-};
-
-} // namespace FileSys
diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp
index dedbd4bdf..e4f337a0a 100644
--- a/src/core/gdbstub/gdbstub.cpp
+++ b/src/core/gdbstub/gdbstub.cpp
@@ -6,6 +6,7 @@
#include <algorithm>
#include <atomic>
+#include <cinttypes>
#include <climits>
#include <csignal>
#include <cstdarg>
@@ -57,9 +58,10 @@ const u32 SIGTERM = 15;
const u32 MSG_WAITALL = 8;
#endif
-const u32 R15_REGISTER = 15;
-const u32 CPSR_REGISTER = 25;
-const u32 FPSCR_REGISTER = 58;
+const u32 X30_REGISTER = 30;
+const u32 SP_REGISTER = 31;
+const u32 PC_REGISTER = 32;
+const u32 CPSR_REGISTER = 33;
// For sample XML files see the GDB source /gdb/features
// GDB also wants the l character at the start
@@ -68,48 +70,62 @@ static const char* target_xml =
R"(l<?xml version="1.0"?>
<!DOCTYPE target SYSTEM "gdb-target.dtd">
<target version="1.0">
- <feature name="org.gnu.gdb.arm.core">
- <reg name="r0" bitsize="32"/>
- <reg name="r1" bitsize="32"/>
- <reg name="r2" bitsize="32"/>
- <reg name="r3" bitsize="32"/>
- <reg name="r4" bitsize="32"/>
- <reg name="r5" bitsize="32"/>
- <reg name="r6" bitsize="32"/>
- <reg name="r7" bitsize="32"/>
- <reg name="r8" bitsize="32"/>
- <reg name="r9" bitsize="32"/>
- <reg name="r10" bitsize="32"/>
- <reg name="r11" bitsize="32"/>
- <reg name="r12" bitsize="32"/>
- <reg name="sp" bitsize="32" type="data_ptr"/>
- <reg name="lr" bitsize="32"/>
- <reg name="pc" bitsize="32" type="code_ptr"/>
-
- <!-- The CPSR is register 25, rather than register 16, because
- the FPA registers historically were placed between the PC
- and the CPSR in the "g" packet. -->
-
- <reg name="cpsr" bitsize="32" regnum="25"/>
- </feature>
- <feature name="org.gnu.gdb.arm.vfp">
- <reg name="d0" bitsize="64" type="float"/>
- <reg name="d1" bitsize="64" type="float"/>
- <reg name="d2" bitsize="64" type="float"/>
- <reg name="d3" bitsize="64" type="float"/>
- <reg name="d4" bitsize="64" type="float"/>
- <reg name="d5" bitsize="64" type="float"/>
- <reg name="d6" bitsize="64" type="float"/>
- <reg name="d7" bitsize="64" type="float"/>
- <reg name="d8" bitsize="64" type="float"/>
- <reg name="d9" bitsize="64" type="float"/>
- <reg name="d10" bitsize="64" type="float"/>
- <reg name="d11" bitsize="64" type="float"/>
- <reg name="d12" bitsize="64" type="float"/>
- <reg name="d13" bitsize="64" type="float"/>
- <reg name="d14" bitsize="64" type="float"/>
- <reg name="d15" bitsize="64" type="float"/>
- <reg name="fpscr" bitsize="32" type="int" group="float"/>
+ <feature name="org.gnu.gdb.aarch64.core">
+ <reg name="x0" bitsize="64"/>
+ <reg name="x1" bitsize="64"/>
+ <reg name="x2" bitsize="64"/>
+ <reg name="x3" bitsize="64"/>
+ <reg name="x4" bitsize="64"/>
+ <reg name="x5" bitsize="64"/>
+ <reg name="x6" bitsize="64"/>
+ <reg name="x7" bitsize="64"/>
+ <reg name="x8" bitsize="64"/>
+ <reg name="x9" bitsize="64"/>
+ <reg name="x10" bitsize="64"/>
+ <reg name="x11" bitsize="64"/>
+ <reg name="x12" bitsize="64"/>
+ <reg name="x13" bitsize="64"/>
+ <reg name="x14" bitsize="64"/>
+ <reg name="x15" bitsize="64"/>
+ <reg name="x16" bitsize="64"/>
+ <reg name="x17" bitsize="64"/>
+ <reg name="x18" bitsize="64"/>
+ <reg name="x19" bitsize="64"/>
+ <reg name="x20" bitsize="64"/>
+ <reg name="x21" bitsize="64"/>
+ <reg name="x22" bitsize="64"/>
+ <reg name="x23" bitsize="64"/>
+ <reg name="x24" bitsize="64"/>
+ <reg name="x25" bitsize="64"/>
+ <reg name="x26" bitsize="64"/>
+ <reg name="x27" bitsize="64"/>
+ <reg name="x28" bitsize="64"/>
+ <reg name="x29" bitsize="64"/>
+ <reg name="x30" bitsize="64"/>
+ <reg name="sp" bitsize="64" type="data_ptr"/>
+
+ <reg name="pc" bitsize="64" type="code_ptr"/>
+
+ <flags id="cpsr_flags" size="4">
+ <field name="SP" start="0" end="0"/>
+ <field name="" start="1" end="1"/>
+ <field name="EL" start="2" end="3"/>
+ <field name="nRW" start="4" end="4"/>
+ <field name="" start="5" end="5"/>
+ <field name="F" start="6" end="6"/>
+ <field name="I" start="7" end="7"/>
+ <field name="A" start="8" end="8"/>
+ <field name="D" start="9" end="9"/>
+
+ <field name="IL" start="20" end="20"/>
+ <field name="SS" start="21" end="21"/>
+
+ <field name="V" start="28" end="28"/>
+ <field name="C" start="29" end="29"/>
+ <field name="Z" start="30" end="30"/>
+ <field name="N" start="31" end="31"/>
+ </flags>
+ <reg name="cpsr" bitsize="32" type="cpsr_flags"/>
</feature>
</target>
)";
@@ -143,12 +159,12 @@ WSADATA InitData;
struct Breakpoint {
bool active;
PAddr addr;
- u32 len;
+ u64 len;
};
-static std::map<u32, Breakpoint> breakpoints_execute;
-static std::map<u32, Breakpoint> breakpoints_read;
-static std::map<u32, Breakpoint> breakpoints_write;
+static std::map<u64, Breakpoint> breakpoints_execute;
+static std::map<u64, Breakpoint> breakpoints_read;
+static std::map<u64, Breakpoint> breakpoints_write;
/**
* Turns hex string character into the equivalent byte.
@@ -183,11 +199,11 @@ static u8 NibbleToHex(u8 n) {
}
/**
-* Converts input hex string characters into an array of equivalent of u8 bytes.
-*
-* @param src Pointer to array of output hex string characters.
-* @param len Length of src array.
-*/
+ * Converts input hex string characters into an array of equivalent of u8 bytes.
+ *
+ * @param src Pointer to array of output hex string characters.
+ * @param len Length of src array.
+ */
static u32 HexToInt(const u8* src, size_t len) {
u32 output = 0;
while (len-- > 0) {
@@ -198,6 +214,21 @@ static u32 HexToInt(const u8* src, size_t len) {
}
/**
+ * Converts input hex string characters into an array of equivalent of u8 bytes.
+ *
+ * @param src Pointer to array of output hex string characters.
+ * @param len Length of src array.
+ */
+static u64 HexToLong(const u8* src, size_t len) {
+ u64 output = 0;
+ while (len-- > 0) {
+ output = (output << 4) | HexCharToValue(src[0]);
+ src++;
+ }
+ return output;
+}
+
+/**
* Converts input array of u8 bytes into their equivalent hex string characters.
*
* @param dest Pointer to buffer to store output hex string characters.
@@ -234,8 +265,21 @@ static void GdbHexToMem(u8* dest, const u8* src, size_t len) {
*/
static void IntToGdbHex(u8* dest, u32 v) {
for (int i = 0; i < 8; i += 2) {
- dest[i + 1] = NibbleToHex(v >> (4 * i));
- dest[i] = NibbleToHex(v >> (4 * (i + 1)));
+ dest[i + 1] = NibbleToHex(static_cast<u8>(v >> (4 * i)));
+ dest[i] = NibbleToHex(static_cast<u8>(v >> (4 * (i + 1))));
+ }
+}
+
+/**
+ * Convert a u64 into a gdb-formatted hex string.
+ *
+ * @param dest Pointer to buffer to store output hex string characters.
+ * @param v Value to convert.
+ */
+static void LongToGdbHex(u8* dest, u64 v) {
+ for (int i = 0; i < 16; i += 2) {
+ dest[i + 1] = NibbleToHex(static_cast<u8>(v >> (4 * i)));
+ dest[i] = NibbleToHex(static_cast<u8>(v >> (4 * (i + 1))));
}
}
@@ -255,6 +299,22 @@ static u32 GdbHexToInt(const u8* src) {
return output;
}
+/**
+ * Convert a gdb-formatted hex string into a u64.
+ *
+ * @param src Pointer to hex string.
+ */
+static u64 GdbHexToLong(const u8* src) {
+ u64 output = 0;
+
+ for (int i = 0; i < 16; i += 2) {
+ output = (output << 4) | HexCharToValue(src[15 - i - 1]);
+ output = (output << 4) | HexCharToValue(src[15 - i]);
+ }
+
+ return output;
+}
+
/// Read a byte from the gdb client.
static u8 ReadByte() {
u8 c;
@@ -277,7 +337,7 @@ static u8 CalculateChecksum(const u8* buffer, size_t length) {
*
* @param type Type of breakpoint list.
*/
-static std::map<u32, Breakpoint>& GetBreakpointList(BreakpointType type) {
+static std::map<u64, Breakpoint>& GetBreakpointList(BreakpointType type) {
switch (type) {
case BreakpointType::Execute:
return breakpoints_execute;
@@ -297,19 +357,20 @@ static std::map<u32, Breakpoint>& GetBreakpointList(BreakpointType type) {
* @param addr Address of breakpoint.
*/
static void RemoveBreakpoint(BreakpointType type, PAddr addr) {
- std::map<u32, Breakpoint>& p = GetBreakpointList(type);
+ std::map<u64, Breakpoint>& p = GetBreakpointList(type);
- auto bp = p.find(addr);
+ auto bp = p.find(static_cast<u64>(addr));
if (bp != p.end()) {
- LOG_DEBUG(Debug_GDBStub, "gdb: removed a breakpoint: %08x bytes at %08x of type %d\n",
- bp->second.len, bp->second.addr, type);
- p.erase(addr);
+ LOG_DEBUG(Debug_GDBStub,
+ "gdb: removed a breakpoint: %016" PRIx64 " bytes at %016" PRIx64 " of type %d\n",
+ bp->second.len, bp->second.addr, static_cast<int>(type));
+ p.erase(static_cast<u64>(addr));
}
}
BreakpointAddress GetNextBreakpointFromAddress(PAddr addr, BreakpointType type) {
- std::map<u32, Breakpoint>& p = GetBreakpointList(type);
- auto next_breakpoint = p.lower_bound(addr);
+ std::map<u64, Breakpoint>& p = GetBreakpointList(type);
+ auto next_breakpoint = p.lower_bound(static_cast<u64>(addr));
BreakpointAddress breakpoint;
if (next_breakpoint != p.end()) {
@@ -328,11 +389,11 @@ bool CheckBreakpoint(PAddr addr, BreakpointType type) {
return false;
}
- std::map<u32, Breakpoint>& p = GetBreakpointList(type);
+ std::map<u64, Breakpoint>& p = GetBreakpointList(type);
- auto bp = p.find(addr);
+ auto bp = p.find(static_cast<u64>(addr));
if (bp != p.end()) {
- u32 len = bp->second.len;
+ u64 len = bp->second.len;
// IDA Pro defaults to 4-byte breakpoints for all non-hardware breakpoints
// no matter if it's a 4-byte or 2-byte instruction. When you execute a
@@ -348,8 +409,9 @@ bool CheckBreakpoint(PAddr addr, BreakpointType type) {
if (bp->second.active && (addr >= bp->second.addr && addr < bp->second.addr + len)) {
LOG_DEBUG(Debug_GDBStub,
- "Found breakpoint type %d @ %08x, range: %08x - %08x (%d bytes)\n", type,
- addr, bp->second.addr, bp->second.addr + len, len);
+ "Found breakpoint type %d @ %016" PRIx64 ", range: %016" PRIx64
+ " - %016" PRIx64 " (%" PRIx64 " bytes)\n",
+ static_cast<int>(type), addr, bp->second.addr, bp->second.addr + len, len);
return true;
}
}
@@ -419,7 +481,7 @@ static void HandleQuery() {
SendReply("T0");
} else if (strncmp(query, "Supported", strlen("Supported")) == 0) {
// PacketSize needs to be large enough for target xml
- SendReply("PacketSize=800;qXfer:features:read+");
+ SendReply("PacketSize=2000;qXfer:features:read+");
} else if (strncmp(query, "Xfer:features:read:target.xml:",
strlen("Xfer:features:read:target.xml:")) == 0) {
SendReply(target_xml);
@@ -450,9 +512,7 @@ static void SendSignal(u32 signal) {
latest_signal = signal;
- std::string buffer =
- Common::StringFromFormat("T%02x%02x:%08x;%02x:%08x;", latest_signal, 15,
- htonl(Core::CPU().GetPC()), 13, htonl(Core::CPU().GetReg(13)));
+ std::string buffer = Common::StringFromFormat("T%02x", latest_signal);
LOG_DEBUG(Debug_GDBStub, "Response: %s", buffer.c_str());
SendReply(buffer.c_str());
}
@@ -538,16 +598,12 @@ static void ReadRegister() {
id |= HexCharToValue(command_buffer[2]);
}
- if (id <= R15_REGISTER) {
- IntToGdbHex(reply, Core::CPU().GetReg(id));
+ if (id <= SP_REGISTER) {
+ LongToGdbHex(reply, Core::CPU().GetReg(static_cast<int>(id)));
+ } else if (id == PC_REGISTER) {
+ LongToGdbHex(reply, Core::CPU().GetPC());
} else if (id == CPSR_REGISTER) {
IntToGdbHex(reply, Core::CPU().GetCPSR());
- } else if (id > CPSR_REGISTER && id < FPSCR_REGISTER) {
- IntToGdbHex(reply, Core::CPU().GetVFPReg(
- id - CPSR_REGISTER -
- 1)); // VFP registers should start at 26, so one after CSPR_REGISTER
- } else if (id == FPSCR_REGISTER) {
- UNIMPLEMENTED();
} else {
return SendReply("E01");
}
@@ -562,21 +618,19 @@ static void ReadRegisters() {
u8* bufptr = buffer;
- for (int reg = 0; reg <= R15_REGISTER; reg++) {
- IntToGdbHex(bufptr + reg * CHAR_BIT, Core::CPU().GetReg(reg));
+ for (int reg = 0; reg <= SP_REGISTER; reg++) {
+ LongToGdbHex(bufptr + reg * 16, Core::CPU().GetReg(reg));
}
- bufptr += (16 * CHAR_BIT);
+ bufptr += (32 * 16);
- IntToGdbHex(bufptr, Core::CPU().GetCPSR());
+ LongToGdbHex(bufptr, Core::CPU().GetPC());
- bufptr += CHAR_BIT;
+ bufptr += 16;
- for (int reg = 0; reg <= 31; reg++) {
- IntToGdbHex(bufptr + reg * CHAR_BIT, Core::CPU().GetVFPReg(reg));
- }
+ IntToGdbHex(bufptr, Core::CPU().GetCPSR());
- bufptr += (32 * CHAR_BIT);
+ bufptr += 8;
SendReply(reinterpret_cast<char*>(buffer));
}
@@ -592,14 +646,12 @@ static void WriteRegister() {
id |= HexCharToValue(command_buffer[2]);
}
- if (id <= R15_REGISTER) {
- Core::CPU().SetReg(id, GdbHexToInt(buffer_ptr));
+ if (id <= SP_REGISTER) {
+ Core::CPU().SetReg(id, GdbHexToLong(buffer_ptr));
+ } else if (id == PC_REGISTER) {
+ Core::CPU().SetPC(GdbHexToLong(buffer_ptr));
} else if (id == CPSR_REGISTER) {
Core::CPU().SetCPSR(GdbHexToInt(buffer_ptr));
- } else if (id > CPSR_REGISTER && id < FPSCR_REGISTER) {
- Core::CPU().SetVFPReg(id - CPSR_REGISTER - 1, GdbHexToInt(buffer_ptr));
- } else if (id == FPSCR_REGISTER) {
- UNIMPLEMENTED();
} else {
return SendReply("E01");
}
@@ -614,20 +666,14 @@ static void WriteRegisters() {
if (command_buffer[0] != 'G')
return SendReply("E01");
- for (int i = 0, reg = 0; reg <= FPSCR_REGISTER; i++, reg++) {
- if (reg <= R15_REGISTER) {
- Core::CPU().SetReg(reg, GdbHexToInt(buffer_ptr + i * CHAR_BIT));
+ for (int i = 0, reg = 0; reg <= CPSR_REGISTER; i++, reg++) {
+ if (reg <= SP_REGISTER) {
+ Core::CPU().SetReg(reg, GdbHexToLong(buffer_ptr + i * 16));
+ } else if (reg == PC_REGISTER) {
+ Core::CPU().SetPC(GdbHexToLong(buffer_ptr + i * 16));
} else if (reg == CPSR_REGISTER) {
- Core::CPU().SetCPSR(GdbHexToInt(buffer_ptr + i * CHAR_BIT));
- } else if (reg == CPSR_REGISTER - 1) {
- // Dummy FPA register, ignore
- } else if (reg < CPSR_REGISTER) {
- // Dummy FPA registers, ignore
- i += 2;
- } else if (reg > CPSR_REGISTER && reg < FPSCR_REGISTER) {
- Core::CPU().SetVFPReg(reg - CPSR_REGISTER - 1, GdbHexToInt(buffer_ptr + i * CHAR_BIT));
- i++; // Skip padding
- } else if (reg == FPSCR_REGISTER) {
+ Core::CPU().SetCPSR(GdbHexToInt(buffer_ptr + i * 16));
+ } else {
UNIMPLEMENTED();
}
}
@@ -641,13 +687,13 @@ static void ReadMemory() {
auto start_offset = command_buffer + 1;
auto addr_pos = std::find(start_offset, command_buffer + command_length, ',');
- VAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset));
+ VAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset));
start_offset = addr_pos + 1;
- u32 len =
- HexToInt(start_offset, static_cast<u32>((command_buffer + command_length) - start_offset));
+ u64 len =
+ HexToLong(start_offset, static_cast<u64>((command_buffer + command_length) - start_offset));
- LOG_DEBUG(Debug_GDBStub, "gdb: addr: %08x len: %08x\n", addr, len);
+ LOG_DEBUG(Debug_GDBStub, "gdb: addr: %016lx len: %016lx\n", addr, len);
if (len * 2 > sizeof(reply)) {
SendReply("E01");
@@ -669,11 +715,11 @@ static void ReadMemory() {
static void WriteMemory() {
auto start_offset = command_buffer + 1;
auto addr_pos = std::find(start_offset, command_buffer + command_length, ',');
- VAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset));
+ VAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset));
start_offset = addr_pos + 1;
auto len_pos = std::find(start_offset, command_buffer + command_length, ':');
- u32 len = HexToInt(start_offset, static_cast<u32>(len_pos - start_offset));
+ u64 len = HexToLong(start_offset, static_cast<u64>(len_pos - start_offset));
if (!Memory::IsValidVirtualAddress(addr)) {
return SendReply("E00");
@@ -726,8 +772,8 @@ static void Continue() {
* @param addr Address of breakpoint.
* @param len Length of breakpoint.
*/
-static bool CommitBreakpoint(BreakpointType type, PAddr addr, u32 len) {
- std::map<u32, Breakpoint>& p = GetBreakpointList(type);
+static bool CommitBreakpoint(BreakpointType type, PAddr addr, u64 len) {
+ std::map<u64, Breakpoint>& p = GetBreakpointList(type);
Breakpoint breakpoint;
breakpoint.active = true;
@@ -735,8 +781,8 @@ static bool CommitBreakpoint(BreakpointType type, PAddr addr, u32 len) {
breakpoint.len = len;
p.insert({addr, breakpoint});
- LOG_DEBUG(Debug_GDBStub, "gdb: added %d breakpoint: %08x bytes at %08x\n", type, breakpoint.len,
- breakpoint.addr);
+ LOG_DEBUG(Debug_GDBStub, "gdb: added %d breakpoint: %016" PRIx64 " bytes at %016" PRIx64 "\n",
+ static_cast<int>(type), breakpoint.len, breakpoint.addr);
return true;
}
@@ -766,11 +812,11 @@ static void AddBreakpoint() {
auto start_offset = command_buffer + 3;
auto addr_pos = std::find(start_offset, command_buffer + command_length, ',');
- PAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset));
+ PAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset));
start_offset = addr_pos + 1;
- u32 len =
- HexToInt(start_offset, static_cast<u32>((command_buffer + command_length) - start_offset));
+ u64 len =
+ HexToLong(start_offset, static_cast<u64>((command_buffer + command_length) - start_offset));
if (type == BreakpointType::Access) {
// Access is made up of Read and Write types, so add both breakpoints
@@ -815,7 +861,7 @@ static void RemoveBreakpoint() {
auto start_offset = command_buffer + 3;
auto addr_pos = std::find(start_offset, command_buffer + command_length, ',');
- PAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset));
+ PAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset));
if (type == BreakpointType::Access) {
// Access is made up of Read and Write types, so add both breakpoints
@@ -905,7 +951,7 @@ void ToggleServer(bool status) {
server_enabled = status;
// Start server
- if (!IsConnected() && Core::System().GetInstance().IsPoweredOn()) {
+ if (!IsConnected() && Core::System::GetInstance().IsPoweredOn()) {
Init();
}
} else {
@@ -1034,4 +1080,4 @@ bool GetCpuStepFlag() {
void SetCpuStepFlag(bool is_step) {
step_loop = is_step;
}
-};
+}; // namespace GDBStub
diff --git a/src/core/gdbstub/gdbstub.h b/src/core/gdbstub/gdbstub.h
index 8f12c6a1d..201fca095 100644
--- a/src/core/gdbstub/gdbstub.h
+++ b/src/core/gdbstub/gdbstub.h
@@ -91,4 +91,4 @@ bool GetCpuStepFlag();
* @param is_step
*/
void SetCpuStepFlag(bool is_step);
-}
+} // namespace GDBStub
diff --git a/src/core/hle/config_mem.cpp b/src/core/hle/config_mem.cpp
index e386ccdc6..038af7909 100644
--- a/src/core/hle/config_mem.cpp
+++ b/src/core/hle/config_mem.cpp
@@ -28,4 +28,4 @@ void Init() {
config_mem.firm_ctr_sdk_ver = 0x0000F297;
}
-} // namespace
+} // namespace ConfigMem
diff --git a/src/core/hle/config_mem.h b/src/core/hle/config_mem.h
index 42fa6d789..1840d1760 100644
--- a/src/core/hle/config_mem.h
+++ b/src/core/hle/config_mem.h
@@ -53,4 +53,4 @@ extern ConfigMemDef config_mem;
void Init();
-} // namespace
+} // namespace ConfigMem
diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h
index 88ba105e5..a6602e12c 100644
--- a/src/core/hle/ipc.h
+++ b/src/core/hle/ipc.h
@@ -91,6 +91,10 @@ struct BufferDescriptorX {
address |= static_cast<VAddr>(address_bits_36_38) << 36;
return address;
}
+
+ u64 Size() const {
+ return static_cast<u64>(size);
+ }
};
static_assert(sizeof(BufferDescriptorX) == 8, "BufferDescriptorX size is incorrect");
@@ -133,6 +137,10 @@ struct BufferDescriptorC {
address |= static_cast<VAddr>(address_bits_32_47) << 32;
return address;
}
+
+ u64 Size() const {
+ return static_cast<u64>(size);
+ }
};
static_assert(sizeof(BufferDescriptorC) == 8, "BufferDescriptorC size is incorrect");
@@ -143,6 +151,11 @@ struct DataPayloadHeader {
static_assert(sizeof(DataPayloadHeader) == 8, "DataPayloadRequest size is incorrect");
struct DomainMessageHeader {
+ enum class CommandType : u32_le {
+ SendMessage = 1,
+ CloseVirtualHandle = 2,
+ };
+
union {
// Used when responding to an IPC request, Server -> Client.
struct {
@@ -153,7 +166,7 @@ struct DomainMessageHeader {
// Used when performing an IPC request, Client -> Server.
struct {
union {
- BitField<0, 8, u32_le> command;
+ BitField<0, 8, CommandType> command;
BitField<16, 16, u32_le> size;
};
u32_le object_id;
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h
index 25530a3c8..3f87c4297 100644
--- a/src/core/hle/ipc_helpers.h
+++ b/src/core/hle/ipc_helpers.h
@@ -11,7 +11,6 @@
#include "core/hle/ipc.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
-#include "core/hle/kernel/domain.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h"
@@ -31,11 +30,6 @@ public:
RequestHelperBase(Kernel::HLERequestContext& context)
: context(&context), cmdbuf(context.CommandBuffer()) {}
- void ValidateHeader() {
- // DEBUG_ASSERT_MSG(index == TotalSize(), "Operations do not match the header (cmd 0x%x)",
- // header.raw);
- }
-
void Skip(unsigned size_in_words, bool set_to_null) {
if (set_to_null)
memset(cmdbuf + index, 0, size_in_words * sizeof(u32));
@@ -54,16 +48,36 @@ public:
unsigned GetCurrentOffset() const {
return static_cast<unsigned>(index);
}
+
+ void SetCurrentOffset(unsigned offset) {
+ index = static_cast<ptrdiff_t>(offset);
+ }
};
-class RequestBuilder : public RequestHelperBase {
+class ResponseBuilder : public RequestHelperBase {
public:
- RequestBuilder(u32* command_buffer) : RequestHelperBase(command_buffer) {}
+ ResponseBuilder(u32* command_buffer) : RequestHelperBase(command_buffer) {}
+
+ u32 normal_params_size{};
+ u32 num_handles_to_copy{};
+ u32 num_objects_to_move{}; ///< Domain objects or move handles, context dependent
+ std::ptrdiff_t datapayload_index{};
+
+ /// Flags used for customizing the behavior of ResponseBuilder
+ enum class Flags : u32 {
+ None = 0,
+ /// Uses move handles to move objects in the response, even when in a domain. This is
+ /// required when PushMoveObjects is used.
+ AlwaysMoveHandles = 1,
+ };
+
+ ResponseBuilder(Kernel::HLERequestContext& context, u32 normal_params_size,
+ u32 num_handles_to_copy = 0, u32 num_objects_to_move = 0,
+ Flags flags = Flags::None)
+
+ : RequestHelperBase(context), normal_params_size(normal_params_size),
+ num_handles_to_copy(num_handles_to_copy), num_objects_to_move(num_objects_to_move) {
- RequestBuilder(Kernel::HLERequestContext& context, unsigned normal_params_size,
- u32 num_handles_to_copy = 0, u32 num_handles_to_move = 0,
- u32 num_domain_objects = 0)
- : RequestHelperBase(context) {
memset(cmdbuf, 0, sizeof(u32) * IPC::COMMAND_BUFFER_LENGTH);
context.ClearIncomingObjects();
@@ -73,12 +87,19 @@ public:
// The entire size of the raw data section in u32 units, including the 16 bytes of mandatory
// padding.
u32 raw_data_size = sizeof(IPC::DataPayloadHeader) / 4 + 4 + normal_params_size;
- if (context.IsDomain()) {
- raw_data_size += sizeof(DomainMessageHeader) / 4 + num_domain_objects;
+
+ u32 num_handles_to_move{};
+ u32 num_domain_objects{};
+ const bool always_move_handles{
+ (static_cast<u32>(flags) & static_cast<u32>(Flags::AlwaysMoveHandles)) != 0};
+ if (!context.Session()->IsDomain() || always_move_handles) {
+ num_handles_to_move = num_objects_to_move;
} else {
- // If we're not in a domain, turn the domain object parameters into move handles.
- num_handles_to_move += num_domain_objects;
- num_domain_objects = 0;
+ num_domain_objects = num_objects_to_move;
+ }
+
+ if (context.Session()->IsDomain()) {
+ raw_data_size += sizeof(DomainMessageHeader) / 4 + num_domain_objects;
}
header.data_size.Assign(raw_data_size);
@@ -97,7 +118,8 @@ public:
AlignWithPadding();
- if (context.IsDomain()) {
+ const bool request_has_domain_header{context.GetDomainMessageHeader() != nullptr};
+ if (context.Session()->IsDomain() && request_has_domain_header) {
IPC::DomainMessageHeader domain_header{};
domain_header.num_objects = num_domain_objects;
PushRaw(domain_header);
@@ -106,23 +128,43 @@ public:
IPC::DataPayloadHeader data_payload_header{};
data_payload_header.magic = Common::MakeMagic('S', 'F', 'C', 'O');
PushRaw(data_payload_header);
+
+ datapayload_index = index;
}
- template <class T, class... Args>
- void PushIpcInterface(Args&&... args) {
- auto iface = std::make_shared<T>(std::forward<Args>(args)...);
- if (context->IsDomain()) {
+ template <class T>
+ void PushIpcInterface(std::shared_ptr<T> iface) {
+ if (context->Session()->IsDomain()) {
context->AddDomainObject(std::move(iface));
} else {
- auto port = iface->CreatePort();
- auto session = port->Connect();
- ASSERT(session.Succeeded());
- context->AddMoveObject(std::move(session).Unwrap());
+ auto sessions = Kernel::ServerSession::CreateSessionPair(iface->GetServiceName());
+ auto server = std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions);
+ auto client = std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions);
+ iface->ClientConnected(server);
+ context->AddMoveObject(std::move(client));
}
}
+ template <class T, class... Args>
+ void PushIpcInterface(Args&&... args) {
+ PushIpcInterface<T>(std::make_shared<T>(std::forward<Args>(args)...));
+ }
+
+ void ValidateHeader() {
+ const size_t num_domain_objects = context->NumDomainObjects();
+ const size_t num_move_objects = context->NumMoveObjects();
+ ASSERT_MSG(!num_domain_objects || !num_move_objects,
+ "cannot move normal handles and domain objects");
+ ASSERT_MSG((index - datapayload_index) == normal_params_size,
+ "normal_params_size value is incorrect");
+ ASSERT_MSG((num_domain_objects + num_move_objects) == num_objects_to_move,
+ "num_objects_to_move value is incorrect");
+ ASSERT_MSG(context->NumCopyObjects() == num_handles_to_copy,
+ "num_handles_to_copy value is incorrect");
+ }
+
// Validate on destruction, as there shouldn't be any case where we don't want it
- ~RequestBuilder() {
+ ~ResponseBuilder() {
ValidateHeader();
}
@@ -150,52 +192,52 @@ public:
/// Push ///
template <>
-inline void RequestBuilder::Push(u32 value) {
+inline void ResponseBuilder::Push(u32 value) {
cmdbuf[index++] = value;
}
template <typename T>
-void RequestBuilder::PushRaw(const T& value) {
+void ResponseBuilder::PushRaw(const T& value) {
std::memcpy(cmdbuf + index, &value, sizeof(T));
index += (sizeof(T) + 3) / 4; // round up to word length
}
template <>
-inline void RequestBuilder::Push(ResultCode value) {
+inline void ResponseBuilder::Push(ResultCode value) {
// Result codes are actually 64-bit in the IPC buffer, but only the high part is discarded.
Push(value.raw);
Push<u32>(0);
}
template <>
-inline void RequestBuilder::Push(u8 value) {
+inline void ResponseBuilder::Push(u8 value) {
PushRaw(value);
}
template <>
-inline void RequestBuilder::Push(u16 value) {
+inline void ResponseBuilder::Push(u16 value) {
PushRaw(value);
}
template <>
-inline void RequestBuilder::Push(u64 value) {
+inline void ResponseBuilder::Push(u64 value) {
Push(static_cast<u32>(value));
Push(static_cast<u32>(value >> 32));
}
template <>
-inline void RequestBuilder::Push(bool value) {
+inline void ResponseBuilder::Push(bool value) {
Push(static_cast<u8>(value));
}
template <typename First, typename... Other>
-void RequestBuilder::Push(const First& first_value, const Other&... other_values) {
+void ResponseBuilder::Push(const First& first_value, const Other&... other_values) {
Push(first_value);
Push(other_values...);
}
template <typename... O>
-inline void RequestBuilder::PushCopyObjects(Kernel::SharedPtr<O>... pointers) {
+inline void ResponseBuilder::PushCopyObjects(Kernel::SharedPtr<O>... pointers) {
auto objects = {pointers...};
for (auto& object : objects) {
context->AddCopyObject(std::move(object));
@@ -203,7 +245,7 @@ inline void RequestBuilder::PushCopyObjects(Kernel::SharedPtr<O>... pointers) {
}
template <typename... O>
-inline void RequestBuilder::PushMoveObjects(Kernel::SharedPtr<O>... pointers) {
+inline void ResponseBuilder::PushMoveObjects(Kernel::SharedPtr<O>... pointers) {
auto objects = {pointers...};
for (auto& object : objects) {
context->AddMoveObject(std::move(object));
@@ -222,15 +264,10 @@ public:
Skip(CommandIdSize, false);
}
- RequestBuilder MakeBuilder(u32 normal_params_size, u32 num_handles_to_copy,
- u32 num_handles_to_move, u32 num_domain_objects,
- bool validate_header = true) {
- if (validate_header) {
- ValidateHeader();
- }
-
- return {*context, normal_params_size, num_handles_to_copy, num_handles_to_move,
- num_domain_objects};
+ ResponseBuilder MakeBuilder(u32 normal_params_size, u32 num_handles_to_copy,
+ u32 num_handles_to_move,
+ ResponseBuilder::Flags flags = ResponseBuilder::Flags::None) {
+ return {*context, normal_params_size, num_handles_to_copy, num_handles_to_move, flags};
}
template <typename T>
@@ -301,6 +338,11 @@ inline u64 RequestParser::Pop() {
}
template <>
+inline s64 RequestParser::Pop() {
+ return static_cast<s64>(Pop<u64>());
+}
+
+template <>
inline bool RequestParser::Pop() {
return Pop<u8>() != 0;
}
diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp
deleted file mode 100644
index 776d342f0..000000000
--- a/src/core/hle/kernel/address_arbiter.cpp
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/common_types.h"
-#include "common/logging/log.h"
-#include "core/hle/kernel/address_arbiter.h"
-#include "core/hle/kernel/errors.h"
-#include "core/hle/kernel/thread.h"
-#include "core/memory.h"
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// Kernel namespace
-
-namespace Kernel {
-
-AddressArbiter::AddressArbiter() {}
-AddressArbiter::~AddressArbiter() {}
-
-SharedPtr<AddressArbiter> AddressArbiter::Create(std::string name) {
- SharedPtr<AddressArbiter> address_arbiter(new AddressArbiter);
-
- address_arbiter->name = std::move(name);
-
- return address_arbiter;
-}
-
-ResultCode AddressArbiter::ArbitrateAddress(ArbitrationType type, VAddr address, s32 value,
- u64 nanoseconds) {
- switch (type) {
-
- // Signal thread(s) waiting for arbitrate address...
- case ArbitrationType::Signal:
- // Negative value means resume all threads
- if (value < 0) {
- ArbitrateAllThreads(address);
- } else {
- // Resume first N threads
- for (int i = 0; i < value; i++)
- ArbitrateHighestPriorityThread(address);
- }
- break;
-
- // Wait current thread (acquire the arbiter)...
- case ArbitrationType::WaitIfLessThan:
- if ((s32)Memory::Read32(address) < value) {
- Kernel::WaitCurrentThread_ArbitrateAddress(address);
- }
- break;
- case ArbitrationType::WaitIfLessThanWithTimeout:
- if ((s32)Memory::Read32(address) < value) {
- Kernel::WaitCurrentThread_ArbitrateAddress(address);
- GetCurrentThread()->WakeAfterDelay(nanoseconds);
- }
- break;
- case ArbitrationType::DecrementAndWaitIfLessThan: {
- s32 memory_value = Memory::Read32(address);
- if (memory_value < value) {
- // Only change the memory value if the thread should wait
- Memory::Write32(address, (s32)memory_value - 1);
- Kernel::WaitCurrentThread_ArbitrateAddress(address);
- }
- break;
- }
- case ArbitrationType::DecrementAndWaitIfLessThanWithTimeout: {
- s32 memory_value = Memory::Read32(address);
- if (memory_value < value) {
- // Only change the memory value if the thread should wait
- Memory::Write32(address, (s32)memory_value - 1);
- Kernel::WaitCurrentThread_ArbitrateAddress(address);
- GetCurrentThread()->WakeAfterDelay(nanoseconds);
- }
- break;
- }
-
- default:
- LOG_ERROR(Kernel, "unknown type=%d", type);
- return ERR_INVALID_ENUM_VALUE_FND;
- }
-
- // The calls that use a timeout seem to always return a Timeout error even if they did not put
- // the thread to sleep
- if (type == ArbitrationType::WaitIfLessThanWithTimeout ||
- type == ArbitrationType::DecrementAndWaitIfLessThanWithTimeout) {
-
- return RESULT_TIMEOUT;
- }
- return RESULT_SUCCESS;
-}
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/address_arbiter.h b/src/core/hle/kernel/address_arbiter.h
deleted file mode 100644
index 1d24401b1..000000000
--- a/src/core/hle/kernel/address_arbiter.h
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "common/common_types.h"
-#include "core/hle/kernel/kernel.h"
-#include "core/hle/result.h"
-
-// Address arbiters are an underlying kernel synchronization object that can be created/used via
-// supervisor calls (SVCs). They function as sort of a global lock. Typically, games/other CTR
-// applications use them as an underlying mechanism to implement thread-safe barriers, events, and
-// semphores.
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// Kernel namespace
-
-namespace Kernel {
-
-enum class ArbitrationType : u32 {
- Signal,
- WaitIfLessThan,
- DecrementAndWaitIfLessThan,
- WaitIfLessThanWithTimeout,
- DecrementAndWaitIfLessThanWithTimeout,
-};
-
-class AddressArbiter final : public Object {
-public:
- /**
- * Creates an address arbiter.
- *
- * @param name Optional name used for debugging.
- * @returns The created AddressArbiter.
- */
- static SharedPtr<AddressArbiter> Create(std::string name = "Unknown");
-
- std::string GetTypeName() const override {
- return "Arbiter";
- }
- std::string GetName() const override {
- return name;
- }
-
- static const HandleType HANDLE_TYPE = HandleType::AddressArbiter;
- HandleType GetHandleType() const override {
- return HANDLE_TYPE;
- }
-
- std::string name; ///< Name of address arbiter object (optional)
-
- ResultCode ArbitrateAddress(ArbitrationType type, VAddr address, s32 value, u64 nanoseconds);
-
-private:
- AddressArbiter();
- ~AddressArbiter() override;
-};
-
-} // namespace FileSys
diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp
index ce5d94e99..fb2b6f7a3 100644
--- a/src/core/hle/kernel/client_port.cpp
+++ b/src/core/hle/kernel/client_port.cpp
@@ -39,4 +39,4 @@ ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() {
return MakeResult(std::get<SharedPtr<ClientSession>>(sessions));
}
-} // namespace
+} // namespace Kernel
diff --git a/src/core/hle/kernel/client_port.h b/src/core/hle/kernel/client_port.h
index 8f7d6ac44..a829aeb6d 100644
--- a/src/core/hle/kernel/client_port.h
+++ b/src/core/hle/kernel/client_port.h
@@ -47,4 +47,4 @@ private:
~ClientPort() override;
};
-} // namespace
+} // namespace Kernel
diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp
index 646a5cc64..72773d8b1 100644
--- a/src/core/hle/kernel/client_session.cpp
+++ b/src/core/hle/kernel/client_session.cpp
@@ -48,4 +48,4 @@ ResultCode ClientSession::SendSyncRequest(SharedPtr<Thread> thread) {
return server->HandleSyncRequest(std::move(thread));
}
-} // namespace
+} // namespace Kernel
diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h
index 671174ec4..2258f95bc 100644
--- a/src/core/hle/kernel/client_session.h
+++ b/src/core/hle/kernel/client_session.h
@@ -7,7 +7,7 @@
#include <memory>
#include <string>
#include "common/common_types.h"
-#include "core/hle/kernel/sync_object.h"
+#include "core/hle/kernel/kernel.h"
#include "core/hle/result.h"
namespace Kernel {
@@ -16,7 +16,7 @@ class ServerSession;
class Session;
class Thread;
-class ClientSession final : public SyncObject {
+class ClientSession final : public Object {
public:
friend class ServerSession;
@@ -33,7 +33,7 @@ public:
return HANDLE_TYPE;
}
- ResultCode SendSyncRequest(SharedPtr<Thread> thread) override;
+ ResultCode SendSyncRequest(SharedPtr<Thread> thread);
std::string name; ///< Name of client port (optional)
@@ -45,4 +45,4 @@ private:
~ClientSession() override;
};
-} // namespace
+} // namespace Kernel
diff --git a/src/core/hle/kernel/condition_variable.cpp b/src/core/hle/kernel/condition_variable.cpp
index 5942eae61..a786d7f74 100644
--- a/src/core/hle/kernel/condition_variable.cpp
+++ b/src/core/hle/kernel/condition_variable.cpp
@@ -15,13 +15,12 @@ ConditionVariable::ConditionVariable() {}
ConditionVariable::~ConditionVariable() {}
ResultVal<SharedPtr<ConditionVariable>> ConditionVariable::Create(VAddr guest_addr,
- VAddr mutex_addr,
std::string name) {
SharedPtr<ConditionVariable> condition_variable(new ConditionVariable);
condition_variable->name = std::move(name);
condition_variable->guest_addr = guest_addr;
- condition_variable->mutex_addr = mutex_addr;
+ condition_variable->mutex_addr = 0;
// Condition variables are referenced by guest address, so track this in the kernel
g_object_address_table.Insert(guest_addr, condition_variable);
@@ -43,7 +42,7 @@ void ConditionVariable::Acquire(Thread* thread) {
ResultCode ConditionVariable::Release(s32 target) {
if (target == -1) {
// When -1, wake up all waiting threads
- SetAvailableCount(GetWaitingThreads().size());
+ SetAvailableCount(static_cast<s32>(GetWaitingThreads().size()));
WakeupAllWaitingThreads();
} else {
// Otherwise, wake up just a single thread
diff --git a/src/core/hle/kernel/condition_variable.h b/src/core/hle/kernel/condition_variable.h
index 0610a284f..1c9f06769 100644
--- a/src/core/hle/kernel/condition_variable.h
+++ b/src/core/hle/kernel/condition_variable.h
@@ -4,8 +4,8 @@
#pragma once
-#include <queue>
#include <string>
+#include <queue>
#include "common/common_types.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/wait_object.h"
@@ -19,12 +19,10 @@ public:
* Creates a condition variable.
* @param guest_addr Address of the object tracking the condition variable in guest memory. If
* specified, this condition variable will update the guest object when its state changes.
- * @param mutex_addr Optional address of a guest mutex associated with this condition variable,
- * used by the OS for implementing events.
* @param name Optional name of condition variable.
* @return The created condition variable.
*/
- static ResultVal<SharedPtr<ConditionVariable>> Create(VAddr guest_addr, VAddr mutex_addr = 0,
+ static ResultVal<SharedPtr<ConditionVariable>> Create(VAddr guest_addr,
std::string name = "Unknown");
std::string GetTypeName() const override {
diff --git a/src/core/hle/kernel/domain.cpp b/src/core/hle/kernel/domain.cpp
deleted file mode 100644
index 7ebb4b9c7..000000000
--- a/src/core/hle/kernel/domain.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "core/hle/kernel/client_port.h"
-#include "core/hle/kernel/domain.h"
-#include "core/hle/kernel/handle_table.h"
-#include "core/hle/kernel/hle_ipc.h"
-#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/session.h"
-#include "core/hle/kernel/thread.h"
-
-namespace Kernel {
-
-ResultVal<SharedPtr<Domain>> Domain::Create(std::string name) {
- SharedPtr<Domain> domain(new Domain);
- domain->name = std::move(name);
- return MakeResult(std::move(domain));
-}
-
-ResultVal<SharedPtr<Domain>> Domain::CreateFromSession(const Session& session) {
- auto res = Create(session.port->GetName() + "_Domain");
- auto& domain = res.Unwrap();
- domain->request_handlers.push_back(std::move(session.server->hle_handler));
- Kernel::g_handle_table.ConvertSessionToDomain(session, domain);
- return res;
-}
-
-ResultCode Domain::SendSyncRequest(SharedPtr<Thread> thread) {
- Kernel::HLERequestContext context(this);
- u32* cmd_buf = (u32*)Memory::GetPointer(Kernel::GetCurrentThread()->GetTLSAddress());
- context.PopulateFromIncomingCommandBuffer(cmd_buf, *Kernel::g_current_process,
- Kernel::g_handle_table);
-
- auto& domain_message_header = context.GetDomainMessageHeader();
- if (domain_message_header) {
- // If there is a DomainMessageHeader, then this is CommandType "Request"
- const u32 object_id{context.GetDomainMessageHeader()->object_id};
- return request_handlers[object_id - 1]->HandleSyncRequest(context);
- }
- return request_handlers.front()->HandleSyncRequest(context);
-}
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/domain.h b/src/core/hle/kernel/domain.h
deleted file mode 100644
index 3fec3b0b2..000000000
--- a/src/core/hle/kernel/domain.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-#include <string>
-#include <vector>
-#include "core/hle/kernel/sync_object.h"
-#include "core/hle/result.h"
-
-namespace Kernel {
-
-class Session;
-class SessionRequestHandler;
-
-class Domain final : public SyncObject {
-public:
- std::string GetTypeName() const override {
- return "Domain";
- }
-
- static const HandleType HANDLE_TYPE = HandleType::Domain;
- HandleType GetHandleType() const override {
- return HANDLE_TYPE;
- }
-
- static ResultVal<SharedPtr<Domain>> CreateFromSession(const Session& server);
-
- ResultCode SendSyncRequest(SharedPtr<Thread> thread) override;
-
- /// The name of this domain (optional)
- std::string name;
-
- std::vector<std::shared_ptr<SessionRequestHandler>> request_handlers;
-
-private:
- Domain() = default;
- ~Domain() override = default;
-
- static ResultVal<SharedPtr<Domain>> Create(std::string name = "Unknown");
-};
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/event.cpp b/src/core/hle/kernel/event.cpp
index 23f9df0d6..9cae2369f 100644
--- a/src/core/hle/kernel/event.cpp
+++ b/src/core/hle/kernel/event.cpp
@@ -52,4 +52,4 @@ void Event::WakeupAllWaitingThreads() {
signaled = false;
}
-} // namespace
+} // namespace Kernel
diff --git a/src/core/hle/kernel/event.h b/src/core/hle/kernel/event.h
index cc41abb85..e5c924a75 100644
--- a/src/core/hle/kernel/event.h
+++ b/src/core/hle/kernel/event.h
@@ -49,4 +49,4 @@ private:
~Event() override;
};
-} // namespace
+} // namespace Kernel
diff --git a/src/core/hle/kernel/handle_table.cpp b/src/core/hle/kernel/handle_table.cpp
index 12506e64c..822449cd5 100644
--- a/src/core/hle/kernel/handle_table.cpp
+++ b/src/core/hle/kernel/handle_table.cpp
@@ -5,12 +5,11 @@
#include <utility>
#include "common/assert.h"
#include "common/logging/log.h"
-#include "core/hle/kernel/client_session.h"
+#include "core/core.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/session.h"
#include "core/hle/kernel/thread.h"
namespace Kernel {
@@ -55,14 +54,6 @@ ResultVal<Handle> HandleTable::Duplicate(Handle handle) {
return Create(std::move(object));
}
-void HandleTable::ConvertSessionToDomain(const Session& session, SharedPtr<Object> domain) {
- for (auto& object : objects) {
- if (DynamicObjectCast<ClientSession>(object) == session.client) {
- object = domain;
- }
- }
-}
-
ResultCode HandleTable::Close(Handle handle) {
if (!IsValid(handle))
return ERR_INVALID_HANDLE;
@@ -87,7 +78,7 @@ SharedPtr<Object> HandleTable::GetGeneric(Handle handle) const {
if (handle == CurrentThread) {
return GetCurrentThread();
} else if (handle == CurrentProcess) {
- return g_current_process;
+ return Core::CurrentProcess();
}
if (!IsValid(handle)) {
@@ -104,4 +95,4 @@ void HandleTable::Clear() {
next_free_slot = 0;
}
-} // namespace
+} // namespace Kernel
diff --git a/src/core/hle/kernel/handle_table.h b/src/core/hle/kernel/handle_table.h
index dba5573a8..ba968c666 100644
--- a/src/core/hle/kernel/handle_table.h
+++ b/src/core/hle/kernel/handle_table.h
@@ -17,8 +17,6 @@ enum KernelHandle : Handle {
CurrentProcess = 0xFFFF8001,
};
-class Session;
-
/**
* This class allows the creation of Handles, which are references to objects that can be tested
* for validity and looked up. Here they are used to pass references to kernel objects to/from the
@@ -62,11 +60,6 @@ public:
ResultVal<Handle> Duplicate(Handle handle);
/**
- * Convert all handles of the specified Session to the specified Domain.
- */
- void ConvertSessionToDomain(const Session& session, SharedPtr<Object> domain);
-
- /**
* Closes a handle, removing it from the table and decreasing the object's ref-count.
* @return `RESULT_SUCCESS` or one of the following errors:
* - `ERR_INVALID_HANDLE`: an invalid handle was passed in.
@@ -130,4 +123,4 @@ private:
extern HandleTable g_handle_table;
-} // namespace
+} // namespace Kernel
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index afa09b404..bef4f15f5 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -7,12 +7,13 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/hle/ipc_helpers.h"
-#include "core/hle/kernel/domain.h"
+#include "core/hle/kernel/event.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/server_session.h"
+#include "core/memory.h"
namespace Kernel {
@@ -26,8 +27,30 @@ void SessionRequestHandler::ClientDisconnected(SharedPtr<ServerSession> server_s
boost::range::remove_erase(connected_sessions, server_session);
}
-HLERequestContext::HLERequestContext(SharedPtr<Kernel::Domain> domain) : domain(std::move(domain)) {
- cmd_buf[0] = 0;
+SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread,
+ const std::string& reason, u64 timeout,
+ WakeupCallback&& callback) {
+
+ // Put the client thread to sleep until the wait event is signaled or the timeout expires.
+ thread->wakeup_callback =
+ [context = *this, callback](ThreadWakeupReason reason, SharedPtr<Thread> thread,
+ SharedPtr<WaitObject> object, size_t index) mutable -> bool {
+ ASSERT(thread->status == THREADSTATUS_WAIT_HLE_EVENT);
+ callback(thread, context, reason);
+ context.WriteToOutgoingCommandBuffer(*thread);
+ return true;
+ };
+
+ auto event = Kernel::Event::Create(Kernel::ResetType::OneShot, "HLE Pause Event: " + reason);
+ thread->status = THREADSTATUS_WAIT_HLE_EVENT;
+ thread->wait_objects = {event};
+ event->AddWaitingThread(thread);
+
+ if (timeout > 0) {
+ thread->WakeAfterDelay(timeout);
+ }
+
+ return event;
}
HLERequestContext::HLERequestContext(SharedPtr<Kernel::ServerSession> server_session)
@@ -39,7 +62,7 @@ HLERequestContext::~HLERequestContext() = default;
void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
IPC::RequestParser rp(src_cmdbuf);
- command_header = std::make_unique<IPC::CommandHeader>(rp.PopRaw<IPC::CommandHeader>());
+ command_header = std::make_shared<IPC::CommandHeader>(rp.PopRaw<IPC::CommandHeader>());
if (command_header->type == IPC::CommandType::Close) {
// Close does not populate the rest of the IPC header
@@ -49,7 +72,7 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
// If handle descriptor is present, add size of it
if (command_header->enable_handle_descriptor) {
handle_descriptor_header =
- std::make_unique<IPC::HandleDescriptorHeader>(rp.PopRaw<IPC::HandleDescriptorHeader>());
+ std::make_shared<IPC::HandleDescriptorHeader>(rp.PopRaw<IPC::HandleDescriptorHeader>());
if (handle_descriptor_header->send_current_pid) {
rp.Skip(2, false);
}
@@ -81,26 +104,34 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
for (unsigned i = 0; i < command_header->num_buf_w_descriptors; ++i) {
buffer_w_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>());
}
- if (command_header->buf_c_descriptor_flags !=
- IPC::CommandHeader::BufferDescriptorCFlag::Disabled) {
- if (command_header->buf_c_descriptor_flags !=
- IPC::CommandHeader::BufferDescriptorCFlag::OneDescriptor) {
- UNIMPLEMENTED();
- }
- }
+
+ buffer_c_offset = rp.GetCurrentOffset() + command_header->data_size;
// Padding to align to 16 bytes
rp.AlignWithPadding();
- if (IsDomain() && (command_header->type == IPC::CommandType::Request || !incoming)) {
+ if (Session()->IsDomain() && (command_header->type == IPC::CommandType::Request || !incoming)) {
// If this is an incoming message, only CommandType "Request" has a domain header
- // All outgoing domain messages have the domain header
- domain_message_header =
- std::make_unique<IPC::DomainMessageHeader>(rp.PopRaw<IPC::DomainMessageHeader>());
+ // All outgoing domain messages have the domain header, if only incoming has it
+ if (incoming || domain_message_header) {
+ domain_message_header =
+ std::make_shared<IPC::DomainMessageHeader>(rp.PopRaw<IPC::DomainMessageHeader>());
+ } else {
+ if (Session()->IsDomain())
+ LOG_WARNING(IPC, "Domain request has no DomainMessageHeader!");
+ }
}
data_payload_header =
- std::make_unique<IPC::DataPayloadHeader>(rp.PopRaw<IPC::DataPayloadHeader>());
+ std::make_shared<IPC::DataPayloadHeader>(rp.PopRaw<IPC::DataPayloadHeader>());
+
+ data_payload_offset = rp.GetCurrentOffset();
+
+ if (domain_message_header && domain_message_header->command ==
+ IPC::DomainMessageHeader::CommandType::CloseVirtualHandle) {
+ // CloseVirtualHandle command does not have SFC* or any data
+ return;
+ }
if (incoming) {
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'I'));
@@ -108,7 +139,31 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'O'));
}
- data_payload_offset = rp.GetCurrentOffset();
+ rp.SetCurrentOffset(buffer_c_offset);
+
+ // For Inline buffers, the response data is written directly to buffer_c_offset
+ // and in this case we don't have any BufferDescriptorC on the request.
+ if (command_header->buf_c_descriptor_flags >
+ IPC::CommandHeader::BufferDescriptorCFlag::InlineDescriptor) {
+ if (command_header->buf_c_descriptor_flags ==
+ IPC::CommandHeader::BufferDescriptorCFlag::OneDescriptor) {
+ buffer_c_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorC>());
+ } else {
+ unsigned num_buf_c_descriptors =
+ static_cast<unsigned>(command_header->buf_c_descriptor_flags.Value()) - 2;
+
+ // This is used to detect possible underflows, in case something is broken
+ // with the two ifs above and the flags value is == 0 || == 1.
+ ASSERT(num_buf_c_descriptors < 14);
+
+ for (unsigned i = 0; i < num_buf_c_descriptors; ++i) {
+ buffer_c_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorC>());
+ }
+ }
+ }
+
+ rp.SetCurrentOffset(data_payload_offset);
+
command = rp.Pop<u32_le>();
rp.Skip(1, false); // The command is actually an u64, but we don't use the high part.
}
@@ -131,8 +186,11 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdb
return RESULT_SUCCESS;
}
-ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process,
- HandleTable& dst_table) {
+ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) {
+ std::array<u32, IPC::COMMAND_BUFFER_LENGTH> dst_cmdbuf;
+ Memory::ReadBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(),
+ dst_cmdbuf.size() * sizeof(u32));
+
// The header was already built in the internal command buffer. Attempt to parse it to verify
// the integrity and then copy it over to the target command buffer.
ParseCommandBuffer(cmd_buf.data(), false);
@@ -143,7 +201,7 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, P
if (domain_message_header)
size -= sizeof(IPC::DomainMessageHeader) / sizeof(u32);
- std::copy_n(cmd_buf.begin(), size, dst_cmdbuf);
+ std::copy_n(cmd_buf.begin(), size, dst_cmdbuf.data());
if (command_header->enable_handle_descriptor) {
ASSERT_MSG(!move_objects.empty() || !copy_objects.empty(),
@@ -173,19 +231,121 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, P
// TODO(Subv): Translate the X/A/B/W buffers.
- if (IsDomain()) {
+ if (Session()->IsDomain() && domain_message_header) {
ASSERT(domain_message_header->num_objects == domain_objects.size());
// Write the domain objects to the command buffer, these go after the raw untranslated data.
// TODO(Subv): This completely ignores C buffers.
size_t domain_offset = size - domain_message_header->num_objects;
- auto& request_handlers = domain->request_handlers;
+ auto& request_handlers = server_session->domain_request_handlers;
for (auto& object : domain_objects) {
request_handlers.emplace_back(object);
- dst_cmdbuf[domain_offset++] = request_handlers.size();
+ dst_cmdbuf[domain_offset++] = static_cast<u32_le>(request_handlers.size());
}
}
+
+ // Copy the translated command buffer back into the thread's command buffer area.
+ Memory::WriteBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(),
+ dst_cmdbuf.size() * sizeof(u32));
+
return RESULT_SUCCESS;
}
+std::vector<u8> HLERequestContext::ReadBuffer() const {
+ std::vector<u8> buffer;
+ const bool is_buffer_a{BufferDescriptorA().size() && BufferDescriptorA()[0].Size()};
+
+ if (is_buffer_a) {
+ buffer.resize(BufferDescriptorA()[0].Size());
+ Memory::ReadBlock(BufferDescriptorA()[0].Address(), buffer.data(), buffer.size());
+ } else {
+ buffer.resize(BufferDescriptorX()[0].Size());
+ Memory::ReadBlock(BufferDescriptorX()[0].Address(), buffer.data(), buffer.size());
+ }
+
+ return buffer;
+}
+
+size_t HLERequestContext::WriteBuffer(const void* buffer, size_t size) const {
+ const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[0].Size()};
+ const size_t buffer_size{GetWriteBufferSize()};
+ if (size > buffer_size) {
+ LOG_CRITICAL(Core, "size (%016zx) is greater than buffer_size (%016zx)", size, buffer_size);
+ size = buffer_size; // TODO(bunnei): This needs to be HW tested
+ }
+
+ if (is_buffer_b) {
+ Memory::WriteBlock(BufferDescriptorB()[0].Address(), buffer, size);
+ } else {
+ Memory::WriteBlock(BufferDescriptorC()[0].Address(), buffer, size);
+ }
+
+ return size;
+}
+
+size_t HLERequestContext::WriteBuffer(const std::vector<u8>& buffer) const {
+ return WriteBuffer(buffer.data(), buffer.size());
+}
+
+size_t HLERequestContext::GetReadBufferSize() const {
+ const bool is_buffer_a{BufferDescriptorA().size() && BufferDescriptorA()[0].Size()};
+ return is_buffer_a ? BufferDescriptorA()[0].Size() : BufferDescriptorX()[0].Size();
+}
+
+size_t HLERequestContext::GetWriteBufferSize() const {
+ const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[0].Size()};
+ return is_buffer_b ? BufferDescriptorB()[0].Size() : BufferDescriptorC()[0].Size();
+}
+
+std::string HLERequestContext::Description() const {
+ if (!command_header) {
+ return "No command header available";
+ }
+ std::ostringstream s;
+ s << "IPC::CommandHeader: Type:" << static_cast<u32>(command_header->type.Value());
+ s << ", X(Pointer):" << command_header->num_buf_x_descriptors;
+ if (command_header->num_buf_x_descriptors) {
+ s << '[';
+ for (u64 i = 0; i < command_header->num_buf_x_descriptors; ++i) {
+ s << "0x" << std::hex << BufferDescriptorX()[i].Size();
+ if (i < command_header->num_buf_x_descriptors - 1)
+ s << ", ";
+ }
+ s << ']';
+ }
+ s << ", A(Send):" << command_header->num_buf_a_descriptors;
+ if (command_header->num_buf_a_descriptors) {
+ s << '[';
+ for (u64 i = 0; i < command_header->num_buf_a_descriptors; ++i) {
+ s << "0x" << std::hex << BufferDescriptorA()[i].Size();
+ if (i < command_header->num_buf_a_descriptors - 1)
+ s << ", ";
+ }
+ s << ']';
+ }
+ s << ", B(Receive):" << command_header->num_buf_b_descriptors;
+ if (command_header->num_buf_b_descriptors) {
+ s << '[';
+ for (u64 i = 0; i < command_header->num_buf_b_descriptors; ++i) {
+ s << "0x" << std::hex << BufferDescriptorB()[i].Size();
+ if (i < command_header->num_buf_b_descriptors - 1)
+ s << ", ";
+ }
+ s << ']';
+ }
+ s << ", C(ReceiveList):" << BufferDescriptorC().size();
+ if (!BufferDescriptorC().empty()) {
+ s << '[';
+ for (u64 i = 0; i < BufferDescriptorC().size(); ++i) {
+ s << "0x" << std::hex << BufferDescriptorC()[i].Size();
+ if (i < BufferDescriptorC().size() - 1)
+ s << ", ";
+ }
+ s << ']';
+ }
+ s << ", data_size:" << command_header->data_size.Value();
+
+ return s.str();
+}
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index 6dceb766d..8b35da4c9 100644
--- a/src/core/hle/kernel/hle_ipc.h
+++ b/src/core/hle/kernel/hle_ipc.h
@@ -6,6 +6,7 @@
#include <array>
#include <memory>
+#include <string>
#include <vector>
#include <boost/container/small_vector.hpp>
#include "common/common_types.h"
@@ -13,6 +14,7 @@
#include "core/hle/ipc.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/server_session.h"
+#include "core/hle/kernel/thread.h"
namespace Service {
class ServiceFrameworkBase;
@@ -24,6 +26,7 @@ class Domain;
class HandleTable;
class HLERequestContext;
class Process;
+class Event;
/**
* Interface implemented by HLE Session handlers.
@@ -86,7 +89,6 @@ protected:
*/
class HLERequestContext {
public:
- HLERequestContext(SharedPtr<Kernel::Domain> domain);
HLERequestContext(SharedPtr<Kernel::ServerSession> session);
~HLERequestContext();
@@ -96,28 +98,38 @@ public:
}
/**
- * Returns the domain through which this request was made.
- */
- const SharedPtr<Kernel::Domain>& Domain() const {
- return domain;
- }
-
- /**
* Returns the session through which this request was made. This can be used as a map key to
* access per-client data on services.
*/
- const SharedPtr<Kernel::ServerSession>& ServerSession() const {
+ const SharedPtr<Kernel::ServerSession>& Session() const {
return server_session;
}
+ using WakeupCallback = std::function<void(SharedPtr<Thread> thread, HLERequestContext& context,
+ ThreadWakeupReason reason)>;
+
+ /**
+ * Puts the specified guest thread to sleep until the returned event is signaled or until the
+ * specified timeout expires.
+ * @param thread Thread to be put to sleep.
+ * @param reason Reason for pausing the thread, to be used for debugging purposes.
+ * @param timeout Timeout in nanoseconds after which the thread will be awoken and the callback
+ * invoked with a Timeout reason.
+ * @param callback Callback to be invoked when the thread is resumed. This callback must write
+ * the entire command response once again, regardless of the state of it before this function
+ * was called.
+ * @returns Event that when signaled will resume the thread and call the callback function.
+ */
+ SharedPtr<Event> SleepClientThread(SharedPtr<Thread> thread, const std::string& reason,
+ u64 timeout, WakeupCallback&& callback);
+
void ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming);
/// Populates this context with data from the requesting process/thread.
ResultCode PopulateFromIncomingCommandBuffer(u32_le* src_cmdbuf, Process& src_process,
HandleTable& src_table);
/// Writes data from this context back to the requesting process/thread.
- ResultCode WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process,
- HandleTable& dst_table);
+ ResultCode WriteToOutgoingCommandBuffer(Thread& thread);
u32_le GetCommand() const {
return command;
@@ -143,14 +155,29 @@ public:
return buffer_b_desciptors;
}
- const std::unique_ptr<IPC::DomainMessageHeader>& GetDomainMessageHeader() const {
- return domain_message_header;
+ const std::vector<IPC::BufferDescriptorC>& BufferDescriptorC() const {
+ return buffer_c_desciptors;
}
- bool IsDomain() const {
- return domain != nullptr;
+ const std::shared_ptr<IPC::DomainMessageHeader>& GetDomainMessageHeader() const {
+ return domain_message_header;
}
+ /// Helper function to read a buffer using the appropriate buffer descriptor
+ std::vector<u8> ReadBuffer() const;
+
+ /// Helper function to write a buffer using the appropriate buffer descriptor
+ size_t WriteBuffer(const void* buffer, size_t size) const;
+
+ /// Helper function to write a buffer using the appropriate buffer descriptor
+ size_t WriteBuffer(const std::vector<u8>& buffer) const;
+
+ /// Helper function to get the size of the input buffer
+ size_t GetReadBufferSize() const;
+
+ /// Helper function to get the size of the output buffer
+ size_t GetWriteBufferSize() const;
+
template <typename T>
SharedPtr<T> GetCopyObject(size_t index) {
ASSERT(index < copy_objects.size());
@@ -183,25 +210,40 @@ public:
domain_objects.clear();
}
+ size_t NumMoveObjects() const {
+ return move_objects.size();
+ }
+
+ size_t NumCopyObjects() const {
+ return copy_objects.size();
+ }
+
+ size_t NumDomainObjects() const {
+ return domain_objects.size();
+ }
+
+ std::string Description() const;
+
private:
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
- SharedPtr<Kernel::Domain> domain;
SharedPtr<Kernel::ServerSession> server_session;
// TODO(yuriks): Check common usage of this and optimize size accordingly
boost::container::small_vector<SharedPtr<Object>, 8> move_objects;
boost::container::small_vector<SharedPtr<Object>, 8> copy_objects;
boost::container::small_vector<std::shared_ptr<SessionRequestHandler>, 8> domain_objects;
- std::unique_ptr<IPC::CommandHeader> command_header;
- std::unique_ptr<IPC::HandleDescriptorHeader> handle_descriptor_header;
- std::unique_ptr<IPC::DataPayloadHeader> data_payload_header;
- std::unique_ptr<IPC::DomainMessageHeader> domain_message_header;
+ std::shared_ptr<IPC::CommandHeader> command_header;
+ std::shared_ptr<IPC::HandleDescriptorHeader> handle_descriptor_header;
+ std::shared_ptr<IPC::DataPayloadHeader> data_payload_header;
+ std::shared_ptr<IPC::DomainMessageHeader> domain_message_header;
std::vector<IPC::BufferDescriptorX> buffer_x_desciptors;
std::vector<IPC::BufferDescriptorABW> buffer_a_desciptors;
std::vector<IPC::BufferDescriptorABW> buffer_b_desciptors;
std::vector<IPC::BufferDescriptorABW> buffer_w_desciptors;
+ std::vector<IPC::BufferDescriptorC> buffer_c_desciptors;
unsigned data_payload_offset{};
+ unsigned buffer_c_offset{};
u32_le command{};
};
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index b0c3f4ae1..b325b879b 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -41,7 +41,6 @@ void Shutdown() {
g_object_address_table.Clear();
Kernel::ThreadingShutdown();
- g_current_process = nullptr;
Kernel::TimersShutdown();
Kernel::ResourceLimitsShutdown();
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 4d9549e45..053bf4e17 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -31,11 +31,6 @@ enum class HandleType : u32 {
ServerPort,
ClientSession,
ServerSession,
- Domain,
-};
-
-enum {
- DEFAULT_STACK_SIZE = 0x10000,
};
enum class ResetType {
@@ -84,27 +79,12 @@ public:
case HandleType::CodeSet:
case HandleType::ClientPort:
case HandleType::ClientSession:
- case HandleType::Domain:
return false;
}
UNREACHABLE();
}
- /**
- * Check if svcSendSyncRequest can be called on the object
- * @return True svcSendSyncRequest can be called on the object, otherwise false
- */
- bool IsSyncable() const {
- switch (GetHandleType()) {
- case HandleType::ClientSession:
- case HandleType::Domain:
- return true;
- }
-
- UNREACHABLE();
- }
-
public:
static unsigned int next_object_id;
diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp
index 4e86eb918..0b9dc700c 100644
--- a/src/core/hle/kernel/mutex.cpp
+++ b/src/core/hle/kernel/mutex.cpp
@@ -70,6 +70,7 @@ ResultCode Mutex::Release(Thread* thread) {
holding_thread->held_mutexes.erase(this);
holding_thread->UpdatePriority();
SetHoldingThread(nullptr);
+ SetHasWaiters(!GetWaitingThreads().empty());
WakeupAllWaitingThreads();
Core::System::GetInstance().PrepareReschedule();
diff --git a/src/core/hle/kernel/object_address_table.cpp b/src/core/hle/kernel/object_address_table.cpp
index 434c16add..cd286f85d 100644
--- a/src/core/hle/kernel/object_address_table.cpp
+++ b/src/core/hle/kernel/object_address_table.cpp
@@ -10,12 +10,12 @@ namespace Kernel {
ObjectAddressTable g_object_address_table;
void ObjectAddressTable::Insert(VAddr addr, SharedPtr<Object> obj) {
- ASSERT_MSG(objects.find(addr) == objects.end(), "Object already exists with addr=0x%llx", addr);
+ ASSERT_MSG(objects.find(addr) == objects.end(), "Object already exists with addr=0x%lx", addr);
objects[addr] = obj;
}
void ObjectAddressTable::Close(VAddr addr) {
- ASSERT_MSG(objects.find(addr) != objects.end(), "Object does not exist with addr=0x%llx", addr);
+ ASSERT_MSG(objects.find(addr) != objects.end(), "Object does not exist with addr=0x%lx", addr);
objects.erase(addr);
}
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index 8e74059ea..2cffec198 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -20,12 +20,9 @@ namespace Kernel {
// Lists all processes that exist in the current session.
static std::vector<SharedPtr<Process>> process_list;
-SharedPtr<CodeSet> CodeSet::Create(std::string name, u64 program_id) {
+SharedPtr<CodeSet> CodeSet::Create(std::string name) {
SharedPtr<CodeSet> codeset(new CodeSet);
-
codeset->name = std::move(name);
- codeset->program_id = program_id;
-
return codeset;
}
@@ -41,6 +38,7 @@ SharedPtr<Process> Process::Create(std::string&& name) {
process->flags.raw = 0;
process->flags.memory_region.Assign(MemoryRegion::APPLICATION);
process->status = ProcessStatus::Created;
+ process->program_id = 0;
process_list.push_back(process);
return process;
@@ -119,11 +117,13 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) {
}
void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) {
- // Allocate and map stack
+ // Allocate and map the main thread stack
+ // TODO(bunnei): This is heap area that should be allocated by the kernel and not mapped as part
+ // of the user address space.
vm_manager
- .MapMemoryBlock(Memory::HEAP_VADDR_END - stack_size,
+ .MapMemoryBlock(Memory::STACK_AREA_VADDR_END - stack_size,
std::make_shared<std::vector<u8>>(stack_size, 0), 0, stack_size,
- MemoryState::Heap)
+ MemoryState::Mapped)
.Unwrap();
misc_memory_used += stack_size;
memory_region->used += stack_size;
@@ -155,9 +155,9 @@ void Process::LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr) {
};
// Map CodeSet segments
- MapSegment(module_->code, VMAPermission::ReadExecute, MemoryState::Code);
- MapSegment(module_->rodata, VMAPermission::Read, MemoryState::Static);
- MapSegment(module_->data, VMAPermission::ReadWrite, MemoryState::Static);
+ MapSegment(module_->code, VMAPermission::ReadExecute, MemoryState::CodeStatic);
+ MapSegment(module_->rodata, VMAPermission::Read, MemoryState::CodeMutable);
+ MapSegment(module_->data, VMAPermission::ReadWrite, MemoryState::CodeMutable);
}
VAddr Process::GetLinearHeapAreaAddress() const {
@@ -184,6 +184,8 @@ ResultVal<VAddr> Process::HeapAllocate(VAddr target, u64 size, VMAPermission per
// Initialize heap
heap_memory = std::make_shared<std::vector<u8>>();
heap_start = heap_end = target;
+ } else {
+ vm_manager.UnmapRange(heap_start, heap_end - heap_start);
}
// If necessary, expand backing vector to cover new heap extents.
@@ -203,7 +205,7 @@ ResultVal<VAddr> Process::HeapAllocate(VAddr target, u64 size, VMAPermission per
size, MemoryState::Heap));
vm_manager.Reprotect(vma, perms);
- heap_used += size;
+ heap_used = size;
memory_region->used += size;
return MakeResult<VAddr>(heap_end - size);
@@ -290,7 +292,7 @@ ResultCode Process::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
CASCADE_RESULT(auto new_vma,
vm_manager.MapMemoryBlock(dst_addr, backing_block, backing_block_offset, size,
- vma->second.meminfo_state));
+ MemoryState::Mapped));
// Protect mirror with permissions from old region
vm_manager.Reprotect(new_vma, vma->second.permissions);
// Remove permissions from old region
@@ -321,5 +323,4 @@ SharedPtr<Process> GetProcessById(u32 process_id) {
return *itr;
}
-SharedPtr<Process> g_current_process;
} // namespace Kernel
diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h
index add98472f..68e77a4d1 100644
--- a/src/core/hle/kernel/process.h
+++ b/src/core/hle/kernel/process.h
@@ -56,7 +56,7 @@ class ResourceLimit;
struct MemoryRegionInfo;
struct CodeSet final : public Object {
- static SharedPtr<CodeSet> Create(std::string name, u64 program_id);
+ static SharedPtr<CodeSet> Create(std::string name);
std::string GetTypeName() const override {
return "CodeSet";
@@ -72,8 +72,6 @@ struct CodeSet final : public Object {
/// Name of the process
std::string name;
- /// Title ID corresponding to the process
- u64 program_id;
std::shared_ptr<std::vector<u8>> memory;
@@ -113,6 +111,9 @@ public:
static u32 next_process_id;
+ /// Title ID corresponding to the process
+ u64 program_id;
+
/// Resource limit descriptor for this process
SharedPtr<ResourceLimit> resource_limit;
@@ -202,5 +203,4 @@ void ClearProcessList();
/// Retrieves a process from the current list of processes.
SharedPtr<Process> GetProcessById(u32 process_id);
-extern SharedPtr<Process> g_current_process;
} // namespace Kernel
diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/resource_limit.cpp
index 517dc47a8..0149a3ed6 100644
--- a/src/core/hle/kernel/resource_limit.cpp
+++ b/src/core/hle/kernel/resource_limit.cpp
@@ -151,4 +151,4 @@ void ResourceLimitsInit() {
void ResourceLimitsShutdown() {}
-} // namespace
+} // namespace Kernel
diff --git a/src/core/hle/kernel/resource_limit.h b/src/core/hle/kernel/resource_limit.h
index 42874eb8d..1a0ca11f1 100644
--- a/src/core/hle/kernel/resource_limit.h
+++ b/src/core/hle/kernel/resource_limit.h
@@ -123,4 +123,4 @@ void ResourceLimitsInit();
// Destroys the resource limits
void ResourceLimitsShutdown();
-} // namespace
+} // namespace Kernel
diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
new file mode 100644
index 000000000..921f27efb
--- /dev/null
+++ b/src/core/hle/kernel/scheduler.cpp
@@ -0,0 +1,135 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/scheduler.h"
+
+namespace Kernel {
+
+Scheduler::Scheduler(ARM_Interface* cpu_core) : cpu_core(cpu_core) {}
+
+Scheduler::~Scheduler() {
+ for (auto& thread : thread_list) {
+ thread->Stop();
+ }
+}
+
+bool Scheduler::HaveReadyThreads() {
+ return ready_queue.get_first() != nullptr;
+}
+
+Thread* Scheduler::GetCurrentThread() const {
+ return current_thread.get();
+}
+
+Thread* Scheduler::PopNextReadyThread() {
+ Thread* next = nullptr;
+ Thread* thread = GetCurrentThread();
+
+ if (thread && thread->status == THREADSTATUS_RUNNING) {
+ // We have to do better than the current thread.
+ // This call returns null when that's not possible.
+ next = ready_queue.pop_first_better(thread->current_priority);
+ if (!next) {
+ // Otherwise just keep going with the current thread
+ next = thread;
+ }
+ } else {
+ next = ready_queue.pop_first();
+ }
+
+ return next;
+}
+
+void Scheduler::SwitchContext(Thread* new_thread) {
+ Thread* previous_thread = GetCurrentThread();
+
+ // Save context for previous thread
+ if (previous_thread) {
+ previous_thread->last_running_ticks = CoreTiming::GetTicks();
+ cpu_core->SaveContext(previous_thread->context);
+
+ if (previous_thread->status == THREADSTATUS_RUNNING) {
+ // This is only the case when a reschedule is triggered without the current thread
+ // yielding execution (i.e. an event triggered, system core time-sliced, etc)
+ ready_queue.push_front(previous_thread->current_priority, previous_thread);
+ previous_thread->status = THREADSTATUS_READY;
+ }
+ }
+
+ // Load context of new thread
+ if (new_thread) {
+ ASSERT_MSG(new_thread->status == THREADSTATUS_READY,
+ "Thread must be ready to become running.");
+
+ // Cancel any outstanding wakeup events for this thread
+ new_thread->CancelWakeupTimer();
+
+ auto previous_process = Core::CurrentProcess();
+
+ current_thread = new_thread;
+
+ ready_queue.remove(new_thread->current_priority, new_thread);
+ new_thread->status = THREADSTATUS_RUNNING;
+
+ if (previous_process != current_thread->owner_process) {
+ Core::CurrentProcess() = current_thread->owner_process;
+ SetCurrentPageTable(&Core::CurrentProcess()->vm_manager.page_table);
+ }
+
+ cpu_core->LoadContext(new_thread->context);
+ cpu_core->SetTlsAddress(new_thread->GetTLSAddress());
+ } else {
+ current_thread = nullptr;
+ // Note: We do not reset the current process and current page table when idling because
+ // technically we haven't changed processes, our threads are just paused.
+ }
+}
+
+void Scheduler::Reschedule() {
+ Thread* cur = GetCurrentThread();
+ Thread* next = PopNextReadyThread();
+
+ if (cur && next) {
+ LOG_TRACE(Kernel, "context switch %u -> %u", cur->GetObjectId(), next->GetObjectId());
+ } else if (cur) {
+ LOG_TRACE(Kernel, "context switch %u -> idle", cur->GetObjectId());
+ } else if (next) {
+ LOG_TRACE(Kernel, "context switch idle -> %u", next->GetObjectId());
+ }
+
+ SwitchContext(next);
+}
+
+void Scheduler::AddThread(SharedPtr<Thread> thread, u32 priority) {
+ thread_list.push_back(thread);
+ ready_queue.prepare(priority);
+}
+
+void Scheduler::RemoveThread(Thread* thread) {
+ thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread),
+ thread_list.end());
+}
+
+void Scheduler::ScheduleThread(Thread* thread, u32 priority) {
+ ASSERT(thread->status == THREADSTATUS_READY);
+ ready_queue.push_back(priority, thread);
+}
+
+void Scheduler::UnscheduleThread(Thread* thread, u32 priority) {
+ ASSERT(thread->status == THREADSTATUS_READY);
+ ready_queue.remove(priority, thread);
+}
+
+void Scheduler::SetThreadPriority(Thread* thread, u32 priority) {
+ // If thread was ready, adjust queues
+ if (thread->status == THREADSTATUS_READY)
+ ready_queue.move(thread, thread->current_priority, priority);
+ else
+ ready_queue.prepare(priority);
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h
new file mode 100644
index 000000000..27d0247d6
--- /dev/null
+++ b/src/core/hle/kernel/scheduler.h
@@ -0,0 +1,73 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include "common/common_types.h"
+#include "common/thread_queue_list.h"
+#include "core/arm/arm_interface.h"
+#include "core/hle/kernel/thread.h"
+
+namespace Kernel {
+
+class Scheduler final {
+public:
+ explicit Scheduler(ARM_Interface* cpu_core);
+ ~Scheduler();
+
+ /// Returns whether there are any threads that are ready to run.
+ bool HaveReadyThreads();
+
+ /// Reschedules to the next available thread (call after current thread is suspended)
+ void Reschedule();
+
+ /// Gets the current running thread
+ Thread* GetCurrentThread() const;
+
+ /// Adds a new thread to the scheduler
+ void AddThread(SharedPtr<Thread> thread, u32 priority);
+
+ /// Removes a thread from the scheduler
+ void RemoveThread(Thread* thread);
+
+ /// Schedules a thread that has become "ready"
+ void ScheduleThread(Thread* thread, u32 priority);
+
+ /// Unschedules a thread that was already scheduled
+ void UnscheduleThread(Thread* thread, u32 priority);
+
+ /// Sets the priority of a thread in the scheduler
+ void SetThreadPriority(Thread* thread, u32 priority);
+
+ /// Returns a list of all threads managed by the scheduler
+ const std::vector<SharedPtr<Thread>>& GetThreadList() const {
+ return thread_list;
+ }
+
+private:
+ /**
+ * Pops and returns the next thread from the thread queue
+ * @return A pointer to the next ready thread
+ */
+ Thread* PopNextReadyThread();
+
+ /**
+ * Switches the CPU's active thread context to that of the specified thread
+ * @param new_thread The thread to switch to
+ */
+ void SwitchContext(Thread* new_thread);
+
+ /// Lists all thread ids that aren't deleted/etc.
+ std::vector<SharedPtr<Thread>> thread_list;
+
+ /// Lists only ready thread ids.
+ Common::ThreadQueueList<Thread*, THREADPRIO_LOWEST + 1> ready_queue;
+
+ SharedPtr<Thread> current_thread = nullptr;
+
+ ARM_Interface* cpu_core;
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp
index 49a9cdfa3..0b7061403 100644
--- a/src/core/hle/kernel/server_port.cpp
+++ b/src/core/hle/kernel/server_port.cpp
@@ -50,4 +50,4 @@ std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> ServerPort::CreatePortP
return std::make_tuple(std::move(server_port), std::move(client_port));
}
-} // namespace
+} // namespace Kernel
diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h
index 6fe7c7f2f..9ef4ecc35 100644
--- a/src/core/hle/kernel/server_port.h
+++ b/src/core/hle/kernel/server_port.h
@@ -72,4 +72,4 @@ private:
~ServerPort() override;
};
-} // namespace
+} // namespace Kernel
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index 09d02a691..33397d84f 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -4,6 +4,8 @@
#include <tuple>
+#include "core/core.h"
+#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
#include "core/hle/kernel/handle_table.h"
@@ -56,34 +58,91 @@ void ServerSession::Acquire(Thread* thread) {
pending_requesting_threads.pop_back();
}
+ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) {
+ auto& domain_message_header = context.GetDomainMessageHeader();
+ if (domain_message_header) {
+ // If there is a DomainMessageHeader, then this is CommandType "Request"
+ const u32 object_id{context.GetDomainMessageHeader()->object_id};
+ switch (domain_message_header->command) {
+ case IPC::DomainMessageHeader::CommandType::SendMessage:
+ return domain_request_handlers[object_id - 1]->HandleSyncRequest(context);
+
+ case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: {
+ LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x%08X", object_id);
+
+ domain_request_handlers[object_id - 1] = nullptr;
+
+ IPC::ResponseBuilder rb{context, 2};
+ rb.Push(RESULT_SUCCESS);
+ return RESULT_SUCCESS;
+ }
+ }
+
+ LOG_CRITICAL(IPC, "Unknown domain command=%d",
+ static_cast<int>(domain_message_header->command.Value()));
+ ASSERT(false);
+ }
+
+ return RESULT_SUCCESS;
+}
+
ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
// The ServerSession received a sync request, this means that there's new data available
// from its ClientSession, so wake up any threads that may be waiting on a svcReplyAndReceive or
// similar.
- // If this ServerSession has an associated HLE handler, forward the request to it.
- ResultCode result{RESULT_SUCCESS};
- if (hle_handler != nullptr) {
- // Attempt to translate the incoming request's command buffer.
- ResultCode translate_result = TranslateHLERequest(this);
- if (translate_result.IsError())
- return translate_result;
-
- Kernel::HLERequestContext context(this);
- u32* cmd_buf = (u32*)Memory::GetPointer(Kernel::GetCurrentThread()->GetTLSAddress());
- context.PopulateFromIncomingCommandBuffer(cmd_buf, *Kernel::g_current_process,
- Kernel::g_handle_table);
-
+ Kernel::HLERequestContext context(this);
+ u32* cmd_buf = (u32*)Memory::GetPointer(thread->GetTLSAddress());
+ context.PopulateFromIncomingCommandBuffer(cmd_buf, *Core::CurrentProcess(),
+ Kernel::g_handle_table);
+
+ ResultCode result = RESULT_SUCCESS;
+ // If the session has been converted to a domain, handle the domain request
+ if (IsDomain() && context.GetDomainMessageHeader()) {
+ result = HandleDomainSyncRequest(context);
+ // If there is no domain header, the regular session handler is used
+ } else if (hle_handler != nullptr) {
+ // If this ServerSession has an associated HLE handler, forward the request to it.
result = hle_handler->HandleSyncRequest(context);
- } else {
- // Add the thread to the list of threads that have issued a sync request with this
- // server.
- pending_requesting_threads.push_back(std::move(thread));
+ }
+
+ if (thread->status == THREADSTATUS_RUNNING) {
+ // Put the thread to sleep until the server replies, it will be awoken in
+ // svcReplyAndReceive for LLE servers.
+ thread->status = THREADSTATUS_WAIT_IPC;
+
+ if (hle_handler != nullptr) {
+ // For HLE services, we put the request threads to sleep for a short duration to
+ // simulate IPC overhead, but only if the HLE handler didn't put the thread to sleep for
+ // other reasons like an async callback. The IPC overhead is needed to prevent
+ // starvation when a thread only does sync requests to HLE services while a
+ // lower-priority thread is waiting to run.
+
+ // This delay was approximated in a homebrew application by measuring the average time
+ // it takes for svcSendSyncRequest to return when performing the SetLcdForceBlack IPC
+ // request to the GSP:GPU service in a n3DS with firmware 11.6. The measured values have
+ // a high variance and vary between models.
+ static constexpr u64 IPCDelayNanoseconds = 39000;
+ thread->WakeAfterDelay(IPCDelayNanoseconds);
+ } else {
+ // Add the thread to the list of threads that have issued a sync request with this
+ // server.
+ pending_requesting_threads.push_back(std::move(thread));
+ }
}
// If this ServerSession does not have an HLE implementation, just wake up the threads waiting
// on it.
WakeupAllWaitingThreads();
+
+ // Handle scenario when ConvertToDomain command was issued, as we must do the conversion at the
+ // end of the command such that only commands following this one are handled as domains
+ if (convert_to_domain) {
+ ASSERT_MSG(domain_request_handlers.empty(), "already a domain");
+ domain_request_handlers = {hle_handler};
+ convert_to_domain = false;
+ }
+
return result;
}
@@ -103,9 +162,4 @@ ServerSession::SessionPair ServerSession::CreateSessionPair(const std::string& n
return std::make_tuple(std::move(server_session), std::move(client_session));
}
-
-ResultCode TranslateHLERequest(ServerSession* server_session) {
- // TODO(Subv): Implement this function once multiple concurrent processes are supported.
- return RESULT_SUCCESS;
-}
} // namespace Kernel
diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h
index f4360ddf3..2da807042 100644
--- a/src/core/hle/kernel/server_session.h
+++ b/src/core/hle/kernel/server_session.h
@@ -21,6 +21,7 @@ class ServerSession;
class Session;
class SessionRequestHandler;
class Thread;
+class HLERequestContext;
/**
* Kernel object representing the server endpoint of an IPC session. Sessions are the basic CTR-OS
@@ -79,7 +80,10 @@ public:
std::string name; ///< The name of this session (optional)
std::shared_ptr<Session> parent; ///< The parent session, which links to the client endpoint.
std::shared_ptr<SessionRequestHandler>
- hle_handler; ///< This session's HLE request handler (optional)
+ hle_handler; ///< This session's HLE request handler (applicable when not a domain)
+
+ /// This is the list of domain request handlers (after conversion to a domain)
+ std::vector<std::shared_ptr<SessionRequestHandler>> domain_request_handlers;
/// List of threads that are pending a response after a sync request. This list is processed in
/// a LIFO manner, thus, the last request will be dispatched first.
@@ -91,6 +95,16 @@ public:
/// TODO(Subv): Find a better name for this.
SharedPtr<Thread> currently_handling;
+ /// Returns true if the session has been converted to a domain, otherwise False
+ bool IsDomain() const {
+ return !domain_request_handlers.empty();
+ }
+
+ /// Converts the session to a domain at the end of the current command
+ void ConvertToDomain() {
+ convert_to_domain = true;
+ }
+
private:
ServerSession();
~ServerSession() override;
@@ -102,15 +116,13 @@ private:
* @return The created server session
*/
static ResultVal<SharedPtr<ServerSession>> Create(std::string name = "Unknown");
+
+ /// Handles a SyncRequest to a domain, forwarding the request to the proper object or closing an
+ /// object handle.
+ ResultCode HandleDomainSyncRequest(Kernel::HLERequestContext& context);
+
+ /// When set to True, converts the session to a domain at the end of the command
+ bool convert_to_domain{};
};
-/**
- * Performs command buffer translation for an HLE IPC request.
- * The command buffer from the ServerSession thread's TLS is copied into a
- * buffer and all descriptors in the buffer are processed.
- * TODO(Subv): Implement this function, currently we do not support multiple processes running at
- * once, but once that is implemented we'll need to properly translate all descriptors
- * in the command buffer.
- */
-ResultCode TranslateHLERequest(ServerSession* server_session);
-}
+} // namespace Kernel
diff --git a/src/core/hle/kernel/session.cpp b/src/core/hle/kernel/session.cpp
index 8a2a7e3fd..642914744 100644
--- a/src/core/hle/kernel/session.cpp
+++ b/src/core/hle/kernel/session.cpp
@@ -9,4 +9,4 @@ namespace Kernel {
Session::Session() {}
Session::~Session() {}
-}
+} // namespace Kernel
diff --git a/src/core/hle/kernel/session.h b/src/core/hle/kernel/session.h
index 2cf319e99..e69b034a7 100644
--- a/src/core/hle/kernel/session.h
+++ b/src/core/hle/kernel/session.h
@@ -24,4 +24,4 @@ public:
ServerSession* server = nullptr; ///< The server endpoint of the session.
SharedPtr<ClientPort> port; ///< The port that this session is associated with (optional).
};
-}
+} // namespace Kernel
diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp
index 7279366ec..88230bdd9 100644
--- a/src/core/hle/kernel/shared_memory.cpp
+++ b/src/core/hle/kernel/shared_memory.cpp
@@ -4,6 +4,7 @@
#include <cstring>
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/memory.h"
#include "core/hle/kernel/shared_memory.h"
@@ -14,7 +15,7 @@ namespace Kernel {
SharedMemory::SharedMemory() {}
SharedMemory::~SharedMemory() {}
-SharedPtr<SharedMemory> SharedMemory::Create(SharedPtr<Process> owner_process, u32 size,
+SharedPtr<SharedMemory> SharedMemory::Create(SharedPtr<Process> owner_process, u64 size,
MemoryPermission permissions,
MemoryPermission other_permissions, VAddr address,
MemoryRegion region, std::string name) {
@@ -51,8 +52,8 @@ SharedPtr<SharedMemory> SharedMemory::Create(SharedPtr<Process> owner_process, u
}
// Refresh the address mappings for the current process.
- if (Kernel::g_current_process != nullptr) {
- Kernel::g_current_process->vm_manager.RefreshMemoryBlockMappings(linheap_memory.get());
+ if (Core::CurrentProcess() != nullptr) {
+ Core::CurrentProcess()->vm_manager.RefreshMemoryBlockMappings(linheap_memory.get());
}
} else {
auto& vm_manager = shared_memory->owner_process->vm_manager;
@@ -106,14 +107,7 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi
// Error out if the requested permissions don't match what the creator process allows.
if (static_cast<u32>(permissions) & ~static_cast<u32>(own_other_permissions)) {
- LOG_ERROR(Kernel, "cannot map id=%u, address=0x%llx name=%s, permissions don't match",
- GetObjectId(), address, name.c_str());
- return ERR_INVALID_COMBINATION;
- }
-
- // Heap-backed memory blocks can not be mapped with other_permissions = DontCare
- if (base_address != 0 && other_permissions == MemoryPermission::DontCare) {
- LOG_ERROR(Kernel, "cannot map id=%u, address=0x%llx name=%s, permissions don't match",
+ LOG_ERROR(Kernel, "cannot map id=%u, address=0x%lx name=%s, permissions don't match",
GetObjectId(), address, name.c_str());
return ERR_INVALID_COMBINATION;
}
@@ -121,23 +115,18 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi
// Error out if the provided permissions are not compatible with what the creator process needs.
if (other_permissions != MemoryPermission::DontCare &&
static_cast<u32>(this->permissions) & ~static_cast<u32>(other_permissions)) {
- LOG_ERROR(Kernel, "cannot map id=%u, address=0x%llx name=%s, permissions don't match",
+ LOG_ERROR(Kernel, "cannot map id=%u, address=0x%lx name=%s, permissions don't match",
GetObjectId(), address, name.c_str());
return ERR_WRONG_PERMISSION;
}
- // TODO(Subv): Check for the Shared Device Mem flag in the creator process.
- /*if (was_created_with_shared_device_mem && address != 0) {
- return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS,
- ErrorSummary::InvalidArgument, ErrorLevel::Usage);
- }*/
-
// TODO(Subv): The same process that created a SharedMemory object
// can not map it in its own address space unless it was created with addr=0, result 0xD900182C.
if (address != 0) {
- if (address < Memory::HEAP_VADDR) {
- LOG_ERROR(Kernel, "cannot map id=%u, address=0x%llx name=%s, invalid address",
+ // TODO(shinyquagsire23): Check for virtual/mappable memory here too?
+ if (address >= Memory::HEAP_VADDR && address < Memory::HEAP_VADDR_END) {
+ LOG_ERROR(Kernel, "cannot map id=%u, address=0x%lx name=%s, invalid address",
GetObjectId(), address, name.c_str());
return ERR_INVALID_ADDRESS;
}
@@ -154,10 +143,9 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi
auto result = target_process->vm_manager.MapMemoryBlock(
target_address, backing_block, backing_block_offset, size, MemoryState::Shared);
if (result.Failed()) {
- LOG_ERROR(
- Kernel,
- "cannot map id=%u, target_address=0x%llx name=%s, error mapping to virtual memory",
- GetObjectId(), target_address, name.c_str());
+ LOG_ERROR(Kernel,
+ "cannot map id=%u, target_address=0x%lx name=%s, error mapping to virtual memory",
+ GetObjectId(), target_address, name.c_str());
return result.Code();
}
diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h
index 93a6f2182..86f818e90 100644
--- a/src/core/hle/kernel/shared_memory.h
+++ b/src/core/hle/kernel/shared_memory.h
@@ -39,7 +39,7 @@ public:
* linear heap.
* @param name Optional object name, used for debugging purposes.
*/
- static SharedPtr<SharedMemory> Create(SharedPtr<Process> owner_process, u32 size,
+ static SharedPtr<SharedMemory> Create(SharedPtr<Process> owner_process, u64 size,
MemoryPermission permissions,
MemoryPermission other_permissions, VAddr address = 0,
MemoryRegion region = MemoryRegion::BASE,
@@ -98,10 +98,10 @@ public:
ResultCode Unmap(Process* target_process, VAddr address);
/**
- * Gets a pointer to the shared memory block
- * @param offset Offset from the start of the shared memory block to get pointer
- * @return Pointer to the shared memory block from the specified offset
- */
+ * Gets a pointer to the shared memory block
+ * @param offset Offset from the start of the shared memory block to get pointer
+ * @return Pointer to the shared memory block from the specified offset
+ */
u8* GetPointer(u32 offset = 0);
/// Process that created this shared memory block.
@@ -116,7 +116,7 @@ public:
/// Offset into the backing block for this shared memory.
size_t backing_block_offset;
/// Size of the memory block. Page-aligned.
- u32 size;
+ u64 size;
/// Permission restrictions applied to the process which created the block.
MemoryPermission permissions;
/// Permission restrictions applied to other processes mapping the block.
@@ -129,4 +129,4 @@ private:
~SharedMemory() override;
};
-} // namespace
+} // namespace Kernel
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 088058ebc..171bbd956 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -3,10 +3,12 @@
// Refer to the license.txt file included.
#include <algorithm>
+#include <cinttypes>
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/string_util.h"
+#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
@@ -20,7 +22,6 @@
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/kernel/svc.h"
#include "core/hle/kernel/svc_wrap.h"
-#include "core/hle/kernel/sync_object.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/lock.h"
#include "core/hle/result.h"
@@ -31,14 +32,14 @@ namespace Kernel {
/// Set the process heap to a given Size. It can both extend and shrink the heap.
static ResultCode SetHeapSize(VAddr* heap_addr, u64 heap_size) {
LOG_TRACE(Kernel_SVC, "called, heap_size=0x%llx", heap_size);
- auto& process = *g_current_process;
+ auto& process = *Core::CurrentProcess();
CASCADE_RESULT(*heap_addr,
process.HeapAllocate(Memory::HEAP_VADDR, heap_size, VMAPermission::ReadWrite));
return RESULT_SUCCESS;
}
static ResultCode SetMemoryAttribute(VAddr addr, u64 size, u32 state0, u32 state1) {
- LOG_WARNING(Kernel_SVC, "(STUBBED) called, addr=0x%llx", addr);
+ LOG_WARNING(Kernel_SVC, "(STUBBED) called, addr=0x%lx", addr);
return RESULT_SUCCESS;
}
@@ -46,18 +47,18 @@ static ResultCode SetMemoryAttribute(VAddr addr, u64 size, u32 state0, u32 state
static ResultCode MapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x%llx, src_addr=0x%llx, size=0x%llx", dst_addr,
src_addr, size);
- return g_current_process->MirrorMemory(dst_addr, src_addr, size);
+ return Core::CurrentProcess()->MirrorMemory(dst_addr, src_addr, size);
}
/// Unmaps a region that was previously mapped with svcMapMemory
static ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x%llx, src_addr=0x%llx, size=0x%llx", dst_addr,
src_addr, size);
- return g_current_process->UnmapMemory(dst_addr, src_addr, size);
+ return Core::CurrentProcess()->UnmapMemory(dst_addr, src_addr, size);
}
/// Connect to an OS service given the port name, returns the handle to the port to out
-static ResultCode ConnectToPort(Handle* out_handle, VAddr port_name_address) {
+static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address) {
if (!Memory::IsValidVirtualAddress(port_name_address))
return ERR_NOT_FOUND;
@@ -87,7 +88,7 @@ static ResultCode ConnectToPort(Handle* out_handle, VAddr port_name_address) {
/// Makes a blocking IPC call to an OS service.
static ResultCode SendSyncRequest(Handle handle) {
- SharedPtr<SyncObject> session = g_handle_table.Get<SyncObject>(handle);
+ SharedPtr<ClientSession> session = g_handle_table.Get<ClientSession>(handle);
if (!session) {
LOG_ERROR(Kernel_SVC, "called with invalid handle=0x%08X", handle);
return ERR_INVALID_HANDLE;
@@ -253,8 +254,8 @@ static ResultCode CancelSynchronization(Handle thread_handle) {
}
/// Attempts to locks a mutex, creating it if it does not already exist
-static ResultCode LockMutex(Handle holding_thread_handle, VAddr mutex_addr,
- Handle requesting_thread_handle) {
+static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr,
+ Handle requesting_thread_handle) {
LOG_TRACE(Kernel_SVC,
"called holding_thread_handle=0x%08X, mutex_addr=0x%llx, "
"requesting_current_thread_handle=0x%08X",
@@ -264,6 +265,7 @@ static ResultCode LockMutex(Handle holding_thread_handle, VAddr mutex_addr,
SharedPtr<Thread> requesting_thread = g_handle_table.Get<Thread>(requesting_thread_handle);
ASSERT(requesting_thread);
+ ASSERT(requesting_thread == GetCurrentThread());
SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(mutex_addr);
if (!mutex) {
@@ -278,7 +280,7 @@ static ResultCode LockMutex(Handle holding_thread_handle, VAddr mutex_addr,
}
/// Unlock a mutex
-static ResultCode UnlockMutex(VAddr mutex_addr) {
+static ResultCode ArbitrateUnlock(VAddr mutex_addr) {
LOG_TRACE(Kernel_SVC, "called mutex_addr=0x%llx", mutex_addr);
SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(mutex_addr);
@@ -305,23 +307,23 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
LOG_TRACE(Kernel_SVC, "called info_id=0x%X, info_sub_id=0x%X, handle=0x%08X", info_id,
info_sub_id, handle);
- auto& vm_manager = g_current_process->vm_manager;
+ auto& vm_manager = Core::CurrentProcess()->vm_manager;
switch (static_cast<GetInfoType>(info_id)) {
case GetInfoType::AllowedCpuIdBitmask:
- *result = g_current_process->allowed_processor_mask;
+ *result = Core::CurrentProcess()->allowed_processor_mask;
break;
case GetInfoType::AllowedThreadPrioBitmask:
- *result = g_current_process->allowed_thread_priority_mask;
+ *result = Core::CurrentProcess()->allowed_thread_priority_mask;
break;
case GetInfoType::MapRegionBaseAddr:
- *result = vm_manager.GetAddressSpaceBaseAddr();
+ *result = Memory::MAP_REGION_VADDR;
break;
case GetInfoType::MapRegionSize:
- *result = vm_manager.GetAddressSpaceSize();
+ *result = Memory::MAP_REGION_SIZE;
break;
case GetInfoType::HeapRegionBaseAddr:
- *result = vm_manager.GetNewMapRegionBaseAddr() + vm_manager.GetNewMapRegionSize();
+ *result = Memory::HEAP_VADDR;
break;
case GetInfoType::HeapRegionSize:
*result = Memory::HEAP_SIZE;
@@ -332,6 +334,9 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
case GetInfoType::TotalHeapUsage:
*result = vm_manager.GetTotalHeapUsage();
break;
+ case GetInfoType::IsCurrentProcessBeingDebugged:
+ *result = 0;
+ break;
case GetInfoType::RandomEntropy:
*result = 0;
break;
@@ -342,13 +347,13 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
*result = vm_manager.GetAddressSpaceSize();
break;
case GetInfoType::NewMapRegionBaseAddr:
- *result = vm_manager.GetNewMapRegionBaseAddr();
+ *result = Memory::NEW_MAP_REGION_VADDR;
break;
case GetInfoType::NewMapRegionSize:
- *result = vm_manager.GetNewMapRegionSize();
+ *result = Memory::NEW_MAP_REGION_SIZE;
break;
case GetInfoType::IsVirtualAddressMemoryEnabled:
- *result = g_current_process->is_virtual_address_memory_enabled;
+ *result = Core::CurrentProcess()->is_virtual_address_memory_enabled;
break;
case GetInfoType::TitleId:
LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query titleid, returned 0");
@@ -388,7 +393,7 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
// Note: The kernel uses the current process's resource limit instead of
// the one from the thread owner's resource limit.
- SharedPtr<ResourceLimit>& resource_limit = g_current_process->resource_limit;
+ SharedPtr<ResourceLimit>& resource_limit = Core::CurrentProcess()->resource_limit;
if (resource_limit->GetMaxResourceValue(ResourceTypes::PRIORITY) > priority) {
return ERR_NOT_AUTHORIZED;
}
@@ -416,8 +421,7 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s
"called, shared_memory_handle=0x%08X, addr=0x%llx, size=0x%llx, permissions=0x%08X",
shared_memory_handle, addr, size, permissions);
- SharedPtr<SharedMemory> shared_memory =
- Kernel::g_handle_table.Get<SharedMemory>(shared_memory_handle);
+ SharedPtr<SharedMemory> shared_memory = g_handle_table.Get<SharedMemory>(shared_memory_handle);
if (!shared_memory) {
return ERR_INVALID_HANDLE;
}
@@ -432,7 +436,7 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s
case MemoryPermission::WriteExecute:
case MemoryPermission::ReadWriteExecute:
case MemoryPermission::DontCare:
- return shared_memory->Map(Kernel::g_current_process.get(), addr, permissions_type,
+ return shared_memory->Map(Core::CurrentProcess().get(), addr, permissions_type,
MemoryPermission::DontCare);
default:
LOG_ERROR(Kernel_SVC, "unknown permissions=0x%08X", permissions);
@@ -441,6 +445,16 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s
return RESULT_SUCCESS;
}
+static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size) {
+ LOG_WARNING(Kernel_SVC,
+ "called, shared_memory_handle=0x%08X, addr=0x%" PRIx64 ", size=0x%" PRIx64 "",
+ shared_memory_handle, addr, size);
+
+ SharedPtr<SharedMemory> shared_memory = g_handle_table.Get<SharedMemory>(shared_memory_handle);
+
+ return shared_memory->Unmap(Core::CurrentProcess().get(), addr);
+}
+
/// Query process memory
static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_info*/,
Handle process_handle, u64 addr) {
@@ -450,11 +464,11 @@ static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_i
}
auto vma = process->vm_manager.FindVMA(addr);
memory_info->attributes = 0;
- if (vma == g_current_process->vm_manager.vma_map.end()) {
+ if (vma == Core::CurrentProcess()->vm_manager.vma_map.end()) {
memory_info->base_address = 0;
memory_info->permission = static_cast<u32>(VMAPermission::None);
memory_info->size = 0;
- memory_info->type = static_cast<u32>(MemoryState::Free);
+ memory_info->type = static_cast<u32>(MemoryState::Unmapped);
} else {
memory_info->base_address = vma->second.base;
memory_info->permission = static_cast<u32>(vma->second.permissions);
@@ -474,16 +488,17 @@ static ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, VAdd
/// Exits the current process
static void ExitProcess() {
- LOG_INFO(Kernel_SVC, "Process %u exiting", g_current_process->process_id);
+ LOG_INFO(Kernel_SVC, "Process %u exiting", Core::CurrentProcess()->process_id);
- ASSERT_MSG(g_current_process->status == ProcessStatus::Running, "Process has already exited");
+ ASSERT_MSG(Core::CurrentProcess()->status == ProcessStatus::Running,
+ "Process has already exited");
- g_current_process->status = ProcessStatus::Exited;
+ Core::CurrentProcess()->status = ProcessStatus::Exited;
// Stop all the process threads that are currently waiting for objects.
- auto& thread_list = GetThreadList();
+ auto& thread_list = Core::System::GetInstance().Scheduler().GetThreadList();
for (auto& thread : thread_list) {
- if (thread->owner_process != g_current_process)
+ if (thread->owner_process != Core::CurrentProcess())
continue;
if (thread == GetCurrentThread())
@@ -512,14 +527,14 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
return ERR_OUT_OF_RANGE;
}
- SharedPtr<ResourceLimit>& resource_limit = g_current_process->resource_limit;
+ SharedPtr<ResourceLimit>& resource_limit = Core::CurrentProcess()->resource_limit;
if (resource_limit->GetMaxResourceValue(ResourceTypes::PRIORITY) > priority) {
return ERR_NOT_AUTHORIZED;
}
if (processor_id == THREADPROCESSORID_DEFAULT) {
// Set the target CPU to the one specified in the process' exheader.
- processor_id = g_current_process->ideal_processor;
+ processor_id = Core::CurrentProcess()->ideal_processor;
ASSERT(processor_id != THREADPROCESSORID_DEFAULT);
}
@@ -541,7 +556,7 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
CASCADE_RESULT(SharedPtr<Thread> thread,
Thread::Create(name, entry_point, priority, arg, processor_id, stack_top,
- g_current_process));
+ Core::CurrentProcess()));
CASCADE_RESULT(thread->guest_handle, g_handle_table.Create(thread));
*out_handle = thread->guest_handle;
@@ -583,7 +598,7 @@ static void SleepThread(s64 nanoseconds) {
// Don't attempt to yield execution if there are no available threads to run,
// this way we avoid a useless reschedule to the idle thread.
- if (nanoseconds == 0 && !HaveReadyThreads())
+ if (nanoseconds == 0 && !Core::System::GetInstance().Scheduler().HaveReadyThreads())
return;
// Sleep current thread and check for next thread to schedule
@@ -613,20 +628,29 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var
mutex->name = Common::StringFromFormat("mutex-%llx", mutex_addr);
}
- ASSERT(mutex->GetOwnerHandle() == thread_handle);
-
SharedPtr<ConditionVariable> condition_variable =
g_object_address_table.Get<ConditionVariable>(condition_variable_addr);
if (!condition_variable) {
// Create a new condition_variable for the specified address if one does not already exist
- condition_variable =
- ConditionVariable::Create(condition_variable_addr, mutex_addr).Unwrap();
+ condition_variable = ConditionVariable::Create(condition_variable_addr).Unwrap();
condition_variable->name =
Common::StringFromFormat("condition-variable-%llx", condition_variable_addr);
}
- ASSERT(condition_variable->GetAvailableCount() == 0);
- ASSERT(condition_variable->mutex_addr == mutex_addr);
+ if (condition_variable->mutex_addr) {
+ // Previously created the ConditionVariable using WaitProcessWideKeyAtomic, verify
+ // everything is correct
+ ASSERT(condition_variable->mutex_addr == mutex_addr);
+ } else {
+ // Previously created the ConditionVariable using SignalProcessWideKey, set the mutex
+ // associated with it
+ condition_variable->mutex_addr = mutex_addr;
+ }
+
+ if (mutex->GetOwnerHandle()) {
+ // Release the mutex if the current thread is holding it
+ mutex->Release(thread.get());
+ }
auto wakeup_callback = [mutex, nano_seconds](ThreadWakeupReason reason,
SharedPtr<Thread> thread,
@@ -668,8 +692,6 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var
CASCADE_CODE(
WaitSynchronization1(condition_variable, thread.get(), nano_seconds, wakeup_callback));
- mutex->Release(thread.get());
-
return RESULT_SUCCESS;
}
@@ -728,14 +750,45 @@ static ResultCode ResetSignal(Handle handle) {
/// Creates a TransferMemory object
static ResultCode CreateTransferMemory(Handle* handle, VAddr addr, u64 size, u32 permissions) {
- LOG_WARNING(Kernel_SVC, "(STUBBED) called addr=0x%llx, size=0x%llx, perms=%08X", addr, size,
+ LOG_WARNING(Kernel_SVC, "(STUBBED) called addr=0x%lx, size=0x%lx, perms=%08X", addr, size,
permissions);
*handle = 0;
return RESULT_SUCCESS;
}
-static ResultCode SetThreadCoreMask(u64, u64, u64) {
- LOG_WARNING(Kernel_SVC, "(STUBBED) called");
+static ResultCode GetThreadCoreMask(Handle handle, u32* mask, u64* unknown) {
+ LOG_WARNING(Kernel_SVC, "(STUBBED) called, handle=0x%08X", handle);
+ *mask = 0x0;
+ *unknown = 0xf;
+ return RESULT_SUCCESS;
+}
+
+static ResultCode SetThreadCoreMask(Handle handle, u32 mask, u64 unknown) {
+ LOG_WARNING(Kernel_SVC, "(STUBBED) called, handle=0x%08X, mask=0x%08X, unknown=0x%lx", handle,
+ mask, unknown);
+ return RESULT_SUCCESS;
+}
+
+static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permissions,
+ u32 remote_permissions) {
+ LOG_TRACE(Kernel_SVC, "called, size=0x%llx, localPerms=0x%08x, remotePerms=0x%08x", size,
+ local_permissions, remote_permissions);
+ auto sharedMemHandle =
+ SharedMemory::Create(g_handle_table.Get<Process>(KernelHandle::CurrentProcess), size,
+ static_cast<MemoryPermission>(local_permissions),
+ static_cast<MemoryPermission>(remote_permissions));
+
+ CASCADE_RESULT(*handle, g_handle_table.Create(sharedMemHandle));
+ return RESULT_SUCCESS;
+}
+
+static ResultCode ClearEvent(Handle handle) {
+ LOG_TRACE(Kernel_SVC, "called, event=0xX", handle);
+
+ SharedPtr<Event> evt = g_handle_table.Get<Event>(handle);
+ if (evt == nullptr)
+ return ERR_INVALID_HANDLE;
+ evt->Clear();
return RESULT_SUCCESS;
}
@@ -764,24 +817,24 @@ static const FunctionDef SVC_Table[] = {
{0x0B, SvcWrap<SleepThread>, "SleepThread"},
{0x0C, SvcWrap<GetThreadPriority>, "GetThreadPriority"},
{0x0D, SvcWrap<SetThreadPriority>, "SetThreadPriority"},
- {0x0E, nullptr, "GetThreadCoreMask"},
+ {0x0E, SvcWrap<GetThreadCoreMask>, "GetThreadCoreMask"},
{0x0F, SvcWrap<SetThreadCoreMask>, "SetThreadCoreMask"},
{0x10, SvcWrap<GetCurrentProcessorNumber>, "GetCurrentProcessorNumber"},
{0x11, nullptr, "SignalEvent"},
- {0x12, nullptr, "ClearEvent"},
+ {0x12, SvcWrap<ClearEvent>, "ClearEvent"},
{0x13, SvcWrap<MapSharedMemory>, "MapSharedMemory"},
- {0x14, nullptr, "UnmapSharedMemory"},
+ {0x14, SvcWrap<UnmapSharedMemory>, "UnmapSharedMemory"},
{0x15, SvcWrap<CreateTransferMemory>, "CreateTransferMemory"},
{0x16, SvcWrap<CloseHandle>, "CloseHandle"},
{0x17, SvcWrap<ResetSignal>, "ResetSignal"},
{0x18, SvcWrap<WaitSynchronization>, "WaitSynchronization"},
{0x19, SvcWrap<CancelSynchronization>, "CancelSynchronization"},
- {0x1A, SvcWrap<LockMutex>, "LockMutex"},
- {0x1B, SvcWrap<UnlockMutex>, "UnlockMutex"},
+ {0x1A, SvcWrap<ArbitrateLock>, "ArbitrateLock"},
+ {0x1B, SvcWrap<ArbitrateUnlock>, "ArbitrateUnlock"},
{0x1C, SvcWrap<WaitProcessWideKeyAtomic>, "WaitProcessWideKeyAtomic"},
{0x1D, SvcWrap<SignalProcessWideKey>, "SignalProcessWideKey"},
{0x1E, SvcWrap<GetSystemTick>, "GetSystemTick"},
- {0x1F, SvcWrap<ConnectToPort>, "ConnectToPort"},
+ {0x1F, SvcWrap<ConnectToNamedPort>, "ConnectToNamedPort"},
{0x20, nullptr, "SendSyncRequestLight"},
{0x21, SvcWrap<SendSyncRequest>, "SendSyncRequest"},
{0x22, nullptr, "SendSyncRequestWithUserBuffer"},
@@ -825,12 +878,12 @@ static const FunctionDef SVC_Table[] = {
{0x48, nullptr, "Unknown"},
{0x49, nullptr, "Unknown"},
{0x4A, nullptr, "Unknown"},
- {0x4B, nullptr, "Unknown"},
- {0x4C, nullptr, "Unknown"},
+ {0x4B, nullptr, "CreateJitMemory"},
+ {0x4C, nullptr, "MapJitMemory"},
{0x4D, nullptr, "SleepSystem"},
{0x4E, nullptr, "ReadWriteRegister"},
{0x4F, nullptr, "SetProcessActivity"},
- {0x50, nullptr, "CreateSharedMemory"},
+ {0x50, SvcWrap<CreateSharedMemory>, "CreateSharedMemory"},
{0x51, nullptr, "MapTransferMemory"},
{0x52, nullptr, "UnmapTransferMemory"},
{0x53, nullptr, "CreateInterruptEvent"},
diff --git a/src/core/hle/kernel/svc.h b/src/core/hle/kernel/svc.h
index 42cc41da3..bc471d01e 100644
--- a/src/core/hle/kernel/svc.h
+++ b/src/core/hle/kernel/svc.h
@@ -14,7 +14,11 @@ struct MemoryInfo {
u32 type;
u32 attributes;
u32 permission;
+ u32 device_refcount;
+ u32 ipc_refcount;
+ INSERT_PADDING_WORDS(1);
};
+static_assert(sizeof(MemoryInfo) == 0x28, "MemoryInfo has incorrect size.");
struct PageInfo {
u64 flags;
diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h
index fd7054bbd..5da4f5269 100644
--- a/src/core/hle/kernel/svc_wrap.h
+++ b/src/core/hle/kernel/svc_wrap.h
@@ -70,6 +70,21 @@ void SvcWrap() {
FuncReturn(retval);
}
+template <ResultCode func(u32, u32, u64)>
+void SvcWrap() {
+ FuncReturn(func((u32)(PARAM(0) & 0xFFFFFFFF), (u32)(PARAM(1) & 0xFFFFFFFF), PARAM(2)).raw);
+}
+
+template <ResultCode func(u32, u32*, u64*)>
+void SvcWrap() {
+ u32 param_1 = 0;
+ u64 param_2 = 0;
+ ResultCode retval = func((u32)(PARAM(2) & 0xFFFFFFFF), &param_1, &param_2);
+ Core::CPU().SetReg(1, param_1);
+ Core::CPU().SetReg(2, param_2);
+ FuncReturn(retval.raw);
+}
+
template <ResultCode func(u64, u64, u32, u32)>
void SvcWrap() {
FuncReturn(
@@ -91,6 +106,11 @@ void SvcWrap() {
FuncReturn(func((u32)PARAM(0), PARAM(1), PARAM(2), (u32)PARAM(3)).raw);
}
+template <ResultCode func(u32, u64, u64)>
+void SvcWrap() {
+ FuncReturn(func((u32)PARAM(0), PARAM(1), PARAM(2)).raw);
+}
+
template <ResultCode func(u32*, u64, u64, s64)>
void SvcWrap() {
u32 param_1 = 0;
@@ -145,6 +165,15 @@ void SvcWrap() {
FuncReturn(retval);
}
+template <ResultCode func(Handle*, u64, u32, u32)>
+void SvcWrap() {
+ u32 param_1 = 0;
+ u32 retval =
+ func(&param_1, PARAM(1), (u32)(PARAM(2) & 0xFFFFFFFF), (u32)(PARAM(3) & 0xFFFFFFFF)).raw;
+ Core::CPU().SetReg(1, param_1);
+ FuncReturn(retval);
+}
+
////////////////////////////////////////////////////////////////////////////////////////////////////
// Function wrappers that return type u32
diff --git a/src/core/hle/kernel/sync_object.h b/src/core/hle/kernel/sync_object.h
deleted file mode 100644
index f2befa2ea..000000000
--- a/src/core/hle/kernel/sync_object.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <boost/smart_ptr/intrusive_ptr.hpp>
-#include "core/hle/kernel/kernel.h"
-#include "core/hle/result.h"
-
-namespace Kernel {
-
-class Thread;
-
-/// Class that represents a Kernel object that svcSendSyncRequest can be called on
-class SyncObject : public Object {
-public:
- /**
- * Handle a sync request from the emulated application.
- * @param thread Thread that initiated the request.
- * @returns ResultCode from the operation.
- */
- virtual ResultCode SendSyncRequest(SharedPtr<Thread> thread) = 0;
-};
-
-// Specialization of DynamicObjectCast for SyncObjects
-template <>
-inline SharedPtr<SyncObject> DynamicObjectCast<SyncObject>(SharedPtr<Object> object) {
- if (object != nullptr && object->IsSyncable()) {
- return boost::static_pointer_cast<SyncObject>(std::move(object));
- }
- return nullptr;
-}
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index 111c496b9..f3a8aa4aa 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <algorithm>
+#include <cinttypes>
#include <list>
#include <vector>
#include "common/assert.h"
@@ -40,14 +41,6 @@ void Thread::Acquire(Thread* thread) {
// us to simply use a pool index or similar.
static Kernel::HandleTable wakeup_callback_handle_table;
-// Lists all thread ids that aren't deleted/etc.
-static std::vector<SharedPtr<Thread>> thread_list;
-
-// Lists only ready thread ids.
-static Common::ThreadQueueList<Thread*, THREADPRIO_LOWEST + 1> ready_queue;
-
-static SharedPtr<Thread> current_thread;
-
// The first available thread id at startup
static u32 next_thread_id;
@@ -62,20 +55,6 @@ inline static u32 const NewThreadId() {
Thread::Thread() {}
Thread::~Thread() {}
-Thread* GetCurrentThread() {
- return current_thread.get();
-}
-
-/**
- * Check if the specified thread is waiting on the specified address to be arbitrated
- * @param thread The thread to test
- * @param wait_address The address to test against
- * @return True if the thread is waiting, false otherwise
- */
-static bool CheckWait_AddressArbiter(const Thread* thread, VAddr wait_address) {
- return thread->status == THREADSTATUS_WAIT_ARB && wait_address == thread->wait_address;
-}
-
void Thread::Stop() {
// Cancel any outstanding wakeup events for this thread
CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle);
@@ -85,7 +64,7 @@ void Thread::Stop() {
// Clean up thread from ready queue
// This is only needed when the thread is termintated forcefully (SVC TerminateProcess)
if (status == THREADSTATUS_READY) {
- ready_queue.remove(current_priority, this);
+ Core::System::GetInstance().Scheduler().UnscheduleThread(this, current_priority);
}
status = THREADSTATUS_DEAD;
@@ -105,113 +84,7 @@ void Thread::Stop() {
u64 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE;
u64 tls_slot =
((tls_address - Memory::TLS_AREA_VADDR) % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE;
- Kernel::g_current_process->tls_slots[tls_page].reset(tls_slot);
-}
-
-Thread* ArbitrateHighestPriorityThread(u32 address) {
- Thread* highest_priority_thread = nullptr;
- u32 priority = THREADPRIO_LOWEST;
-
- // Iterate through threads, find highest priority thread that is waiting to be arbitrated...
- for (auto& thread : thread_list) {
- if (!CheckWait_AddressArbiter(thread.get(), address))
- continue;
-
- if (thread == nullptr)
- continue;
-
- if (thread->current_priority <= priority) {
- highest_priority_thread = thread.get();
- priority = thread->current_priority;
- }
- }
-
- // If a thread was arbitrated, resume it
- if (nullptr != highest_priority_thread) {
- highest_priority_thread->ResumeFromWait();
- }
-
- return highest_priority_thread;
-}
-
-void ArbitrateAllThreads(u32 address) {
- // Resume all threads found to be waiting on the address
- for (auto& thread : thread_list) {
- if (CheckWait_AddressArbiter(thread.get(), address))
- thread->ResumeFromWait();
- }
-}
-
-/**
- * Switches the CPU's active thread context to that of the specified thread
- * @param new_thread The thread to switch to
- */
-static void SwitchContext(Thread* new_thread) {
- Thread* previous_thread = GetCurrentThread();
-
- // Save context for previous thread
- if (previous_thread) {
- previous_thread->last_running_ticks = CoreTiming::GetTicks();
- Core::CPU().SaveContext(previous_thread->context);
-
- if (previous_thread->status == THREADSTATUS_RUNNING) {
- // This is only the case when a reschedule is triggered without the current thread
- // yielding execution (i.e. an event triggered, system core time-sliced, etc)
- ready_queue.push_front(previous_thread->current_priority, previous_thread);
- previous_thread->status = THREADSTATUS_READY;
- }
- }
-
- // Load context of new thread
- if (new_thread) {
- ASSERT_MSG(new_thread->status == THREADSTATUS_READY,
- "Thread must be ready to become running.");
-
- // Cancel any outstanding wakeup events for this thread
- CoreTiming::UnscheduleEvent(ThreadWakeupEventType, new_thread->callback_handle);
-
- auto previous_process = Kernel::g_current_process;
-
- current_thread = new_thread;
-
- ready_queue.remove(new_thread->current_priority, new_thread);
- new_thread->status = THREADSTATUS_RUNNING;
-
- if (previous_process != current_thread->owner_process) {
- Kernel::g_current_process = current_thread->owner_process;
- SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table);
- }
-
- Core::CPU().LoadContext(new_thread->context);
- Core::CPU().SetTlsAddress(new_thread->GetTLSAddress());
- } else {
- current_thread = nullptr;
- // Note: We do not reset the current process and current page table when idling because
- // technically we haven't changed processes, our threads are just paused.
- }
-}
-
-/**
- * Pops and returns the next thread from the thread queue
- * @return A pointer to the next ready thread
- */
-static Thread* PopNextReadyThread() {
- Thread* next;
- Thread* thread = GetCurrentThread();
-
- if (thread && thread->status == THREADSTATUS_RUNNING) {
- // We have to do better than the current thread.
- // This call returns null when that's not possible.
- next = ready_queue.pop_first_better(thread->current_priority);
- if (!next) {
- // Otherwise just keep going with the current thread
- next = thread;
- }
- } else {
- next = ready_queue.pop_first();
- }
-
- return next;
+ Core::CurrentProcess()->tls_slots[tls_page].reset(tls_slot);
}
void WaitCurrentThread_Sleep() {
@@ -219,17 +92,10 @@ void WaitCurrentThread_Sleep() {
thread->status = THREADSTATUS_WAIT_SLEEP;
}
-void WaitCurrentThread_ArbitrateAddress(VAddr wait_address) {
- Thread* thread = GetCurrentThread();
- thread->wait_address = wait_address;
- thread->status = THREADSTATUS_WAIT_ARB;
-}
-
void ExitCurrentThread() {
Thread* thread = GetCurrentThread();
thread->Stop();
- thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread),
- thread_list.end());
+ Core::System::GetInstance().Scheduler().RemoveThread(thread);
}
/**
@@ -247,7 +113,8 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {
bool resume = true;
if (thread->status == THREADSTATUS_WAIT_SYNCH_ANY ||
- thread->status == THREADSTATUS_WAIT_SYNCH_ALL || thread->status == THREADSTATUS_WAIT_ARB) {
+ thread->status == THREADSTATUS_WAIT_SYNCH_ALL ||
+ thread->status == THREADSTATUS_WAIT_HLE_EVENT) {
// Remove the thread from each of its waiting objects' waitlists
for (auto& object : thread->wait_objects)
@@ -281,8 +148,9 @@ void Thread::ResumeFromWait() {
switch (status) {
case THREADSTATUS_WAIT_SYNCH_ALL:
case THREADSTATUS_WAIT_SYNCH_ANY:
- case THREADSTATUS_WAIT_ARB:
+ case THREADSTATUS_WAIT_HLE_EVENT:
case THREADSTATUS_WAIT_SLEEP:
+ case THREADSTATUS_WAIT_IPC:
break;
case THREADSTATUS_READY:
@@ -306,32 +174,12 @@ void Thread::ResumeFromWait() {
wakeup_callback = nullptr;
- ready_queue.push_back(current_priority, this);
status = THREADSTATUS_READY;
+ Core::System::GetInstance().Scheduler().ScheduleThread(this, current_priority);
Core::System::GetInstance().PrepareReschedule();
}
/**
- * Prints the thread queue for debugging purposes
- */
-static void DebugThreadQueue() {
- Thread* thread = GetCurrentThread();
- if (!thread) {
- LOG_DEBUG(Kernel, "Current: NO CURRENT THREAD");
- } else {
- LOG_DEBUG(Kernel, "0x%02X %u (current)", thread->current_priority,
- GetCurrentThread()->GetObjectId());
- }
-
- for (auto& t : thread_list) {
- u32 priority = ready_queue.contains(t.get());
- if (priority != -1) {
- LOG_DEBUG(Kernel, "0x%02X %u", priority, t->GetObjectId());
- }
- }
-}
-
-/**
* Finds a free location for the TLS section of a thread.
* @param tls_slots The TLS page array of the thread's owner process.
* Returns a tuple of (page, slot, alloc_needed) where:
@@ -379,7 +227,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
SharedPtr<Process> owner_process) {
// Check if priority is in ranged. Lowest priority -> highest priority id.
if (priority > THREADPRIO_LOWEST) {
- LOG_ERROR(Kernel_SVC, "Invalid thread priority: %d", priority);
+ LOG_ERROR(Kernel_SVC, "Invalid thread priority: %u", priority);
return ERR_OUT_OF_RANGE;
}
@@ -391,15 +239,14 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
// TODO(yuriks): Other checks, returning 0xD9001BEA
if (!Memory::IsValidVirtualAddress(*owner_process, entry_point)) {
- LOG_ERROR(Kernel_SVC, "(name=%s): invalid entry %08x", name.c_str(), entry_point);
+ LOG_ERROR(Kernel_SVC, "(name=%s): invalid entry %016" PRIx64, name.c_str(), entry_point);
// TODO (bunnei): Find the correct error code to use here
return ResultCode(-1);
}
SharedPtr<Thread> thread(new Thread);
- thread_list.push_back(thread);
- ready_queue.prepare(priority);
+ Core::System::GetInstance().Scheduler().AddThread(thread, priority);
thread->thread_id = NewThreadId();
thread->status = THREADSTATUS_DORMANT;
@@ -452,7 +299,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
// TODO(Subv): Find the correct MemoryState for this region.
vm_manager.MapMemoryBlock(Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE,
linheap_memory, offset, Memory::PAGE_SIZE,
- MemoryState::ThreadLocalStorage);
+ MemoryState::ThreadLocal);
}
// Mark the slot as used
@@ -470,12 +317,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
void Thread::SetPriority(u32 priority) {
ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST,
"Invalid priority value.");
- // If thread was ready, adjust queues
- if (status == THREADSTATUS_READY)
- ready_queue.move(this, current_priority, priority);
- else
- ready_queue.prepare(priority);
-
+ Core::System::GetInstance().Scheduler().SetThreadPriority(this, priority);
nominal_priority = current_priority = priority;
}
@@ -489,22 +331,18 @@ void Thread::UpdatePriority() {
}
void Thread::BoostPriority(u32 priority) {
- // If thread was ready, adjust queues
- if (status == THREADSTATUS_READY)
- ready_queue.move(this, current_priority, priority);
- else
- ready_queue.prepare(priority);
+ Core::System::GetInstance().Scheduler().SetThreadPriority(this, priority);
current_priority = priority;
}
SharedPtr<Thread> SetupMainThread(VAddr entry_point, u32 priority,
SharedPtr<Process> owner_process) {
// Setup page table so we can write to memory
- SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table);
+ SetCurrentPageTable(&Core::CurrentProcess()->vm_manager.page_table);
// Initialize new "main" thread
auto thread_res = Thread::Create("main", entry_point, priority, 0, THREADPROCESSORID_0,
- Memory::HEAP_VADDR_END, owner_process);
+ Memory::STACK_AREA_VADDR_END, owner_process);
SharedPtr<Thread> thread = std::move(thread_res).Unwrap();
@@ -519,25 +357,6 @@ SharedPtr<Thread> SetupMainThread(VAddr entry_point, u32 priority,
return thread;
}
-bool HaveReadyThreads() {
- return ready_queue.get_first() != nullptr;
-}
-
-void Reschedule() {
- Thread* cur = GetCurrentThread();
- Thread* next = PopNextReadyThread();
-
- if (cur && next) {
- LOG_TRACE(Kernel, "context switch %u -> %u", cur->GetObjectId(), next->GetObjectId());
- } else if (cur) {
- LOG_TRACE(Kernel, "context switch %u -> idle", cur->GetObjectId());
- } else if (next) {
- LOG_TRACE(Kernel, "context switch idle -> %u", next->GetObjectId());
- }
-
- SwitchContext(next);
-}
-
void Thread::SetWaitSynchronizationResult(ResultCode result) {
context.cpu_registers[0] = result.raw;
}
@@ -560,25 +379,20 @@ VAddr Thread::GetCommandBufferAddress() const {
////////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Gets the current thread
+ */
+Thread* GetCurrentThread() {
+ return Core::System::GetInstance().Scheduler().GetCurrentThread();
+}
+
void ThreadingInit() {
ThreadWakeupEventType = CoreTiming::RegisterEvent("ThreadWakeupCallback", ThreadWakeupCallback);
-
- current_thread = nullptr;
next_thread_id = 1;
}
void ThreadingShutdown() {
- current_thread = nullptr;
-
- for (auto& t : thread_list) {
- t->Stop();
- }
- thread_list.clear();
- ready_queue.clear();
-}
-
-const std::vector<SharedPtr<Thread>>& GetThreadList() {
- return thread_list;
+ Kernel::ClearProcessList();
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index 0a1ada27d..dbf47e269 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -38,8 +38,9 @@ enum ThreadProcessorId : s32 {
enum ThreadStatus {
THREADSTATUS_RUNNING, ///< Currently running
THREADSTATUS_READY, ///< Ready to run
- THREADSTATUS_WAIT_ARB, ///< Waiting on an address arbiter
+ THREADSTATUS_WAIT_HLE_EVENT, ///< Waiting for hle event to finish
THREADSTATUS_WAIT_SLEEP, ///< Waiting due to a SleepThread SVC
+ THREADSTATUS_WAIT_IPC, ///< Waiting for the reply from an IPC request
THREADSTATUS_WAIT_SYNCH_ANY, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false
THREADSTATUS_WAIT_SYNCH_ALL, ///< Waiting due to WaitSynchronizationN with wait_all = true
THREADSTATUS_DORMANT, ///< Created but not yet made ready
@@ -249,28 +250,6 @@ SharedPtr<Thread> SetupMainThread(VAddr entry_point, u32 priority,
SharedPtr<Process> owner_process);
/**
- * Returns whether there are any threads that are ready to run.
- */
-bool HaveReadyThreads();
-
-/**
- * Reschedules to the next available thread (call after current thread is suspended)
- */
-void Reschedule();
-
-/**
- * Arbitrate the highest priority thread that is waiting
- * @param address The address for which waiting threads should be arbitrated
- */
-Thread* ArbitrateHighestPriorityThread(VAddr address);
-
-/**
- * Arbitrate all threads currently waiting.
- * @param address The address for which waiting threads should be arbitrated
- */
-void ArbitrateAllThreads(VAddr address);
-
-/**
* Gets the current thread
*/
Thread* GetCurrentThread();
@@ -301,9 +280,4 @@ void ThreadingInit();
*/
void ThreadingShutdown();
-/**
- * Get a const reference to the thread list for debug use
- */
-const std::vector<SharedPtr<Thread>>& GetThreadList();
-
} // namespace Kernel
diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp
index a93a6c87a..8da745634 100644
--- a/src/core/hle/kernel/timer.cpp
+++ b/src/core/hle/kernel/timer.cpp
@@ -111,4 +111,4 @@ void TimersInit() {
void TimersShutdown() {}
-} // namespace
+} // namespace Kernel
diff --git a/src/core/hle/kernel/timer.h b/src/core/hle/kernel/timer.h
index 82552372d..82d19cefc 100644
--- a/src/core/hle/kernel/timer.h
+++ b/src/core/hle/kernel/timer.h
@@ -76,4 +76,4 @@ void TimersInit();
/// Tears down the timer variables
void TimersShutdown();
-} // namespace
+} // namespace Kernel
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index bf261699e..1c2f873aa 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cinttypes>
#include <iterator>
#include "common/assert.h"
#include "common/logging/log.h"
@@ -10,15 +11,33 @@
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/memory.h"
+#include "core/memory_hook.h"
#include "core/memory_setup.h"
-#include "core/mmio.h"
namespace Kernel {
static const char* GetMemoryStateName(MemoryState state) {
static const char* names[] = {
- "Free", "Reserved", "IO", "Static", "Code", "Private",
- "Shared", "Continuous", "Aliased", "Alias", "AliasCode", "Locked",
+ "Unmapped",
+ "Io",
+ "Normal",
+ "CodeStatic",
+ "CodeMutable",
+ "Heap",
+ "Shared",
+ "Unknown1"
+ "ModuleCodeStatic",
+ "ModuleCodeMutable",
+ "IpcBuffer0",
+ "Mapped",
+ "ThreadLocal",
+ "TransferMemoryIsolated",
+ "TransferMemory",
+ "ProcessMemory",
+ "Unknown2"
+ "IpcBuffer1",
+ "IpcBuffer3",
+ "KernelStack",
};
return names[(int)state];
@@ -60,8 +79,8 @@ void VMManager::Reset() {
vma_map.emplace(initial_vma.base, initial_vma);
page_table.pointers.fill(nullptr);
+ page_table.special_regions.clear();
page_table.attributes.fill(Memory::PageType::Unmapped);
- page_table.cached_res_count.fill(0);
UpdatePageTableForVMA(initial_vma);
}
@@ -121,7 +140,7 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* me
ResultVal<VMManager::VMAHandle> VMManager::MapMMIO(VAddr target, PAddr paddr, u64 size,
MemoryState state,
- Memory::MMIORegionPointer mmio_handler) {
+ Memory::MemoryHookPointer mmio_handler) {
// This is the appropriately sized VMA that will turn into our allocation.
CASCADE_RESULT(VMAIter vma_handle, CarveVMA(target, size));
VirtualMemoryArea& final_vma = vma_handle->second;
@@ -141,7 +160,7 @@ VMManager::VMAIter VMManager::Unmap(VMAIter vma_handle) {
VirtualMemoryArea& vma = vma_handle->second;
vma.type = VMAType::Free;
vma.permissions = VMAPermission::None;
- vma.meminfo_state = MemoryState::Free;
+ vma.meminfo_state = MemoryState::Unmapped;
vma.backing_block = nullptr;
vma.offset = 0;
@@ -165,6 +184,9 @@ ResultCode VMManager::UnmapRange(VAddr target, u64 size) {
}
ASSERT(FindVMA(target)->second.size >= size);
+
+ Core::CPU().UnmapMemory(target, size);
+
return RESULT_SUCCESS;
}
@@ -206,7 +228,8 @@ void VMManager::RefreshMemoryBlockMappings(const std::vector<u8>* block) {
void VMManager::LogLayout(Log::Level log_level) const {
for (const auto& p : vma_map) {
const VirtualMemoryArea& vma = p.second;
- LOG_GENERIC(Log::Class::Kernel, log_level, "%08X - %08X size: %8X %c%c%c %s", vma.base,
+ LOG_GENERIC(Log::Class::Kernel, log_level,
+ "%016" PRIx64 " - %016" PRIx64 " size: %16" PRIx64 " %c%c%c %s", vma.base,
vma.base + vma.size, vma.size,
(u8)vma.permissions & (u8)VMAPermission::Read ? 'R' : '-',
(u8)vma.permissions & (u8)VMAPermission::Write ? 'W' : '-',
@@ -222,8 +245,8 @@ VMManager::VMAIter VMManager::StripIterConstness(const VMAHandle& iter) {
}
ResultVal<VMManager::VMAIter> VMManager::CarveVMA(VAddr base, u64 size) {
- ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x%8X", size);
- ASSERT_MSG((base & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x%08X", base);
+ ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x%16" PRIx64, size);
+ ASSERT_MSG((base & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x%016" PRIx64, base);
VMAIter vma_handle = StripIterConstness(FindVMA(base));
if (vma_handle == vma_map.end()) {
@@ -258,8 +281,8 @@ ResultVal<VMManager::VMAIter> VMManager::CarveVMA(VAddr base, u64 size) {
}
ResultVal<VMManager::VMAIter> VMManager::CarveVMARange(VAddr target, u64 size) {
- ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x%8X", size);
- ASSERT_MSG((target & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x%08X", target);
+ ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x%16" PRIx64, size);
+ ASSERT_MSG((target & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x%016" PRIx64, target);
VAddr target_end = target + size;
ASSERT(target_end >= target);
@@ -375,14 +398,4 @@ u64 VMManager::GetAddressSpaceSize() {
return MAX_ADDRESS;
}
-VAddr VMManager::GetNewMapRegionBaseAddr() {
- LOG_WARNING(Kernel, "(STUBBED) called");
- return 0x8000000;
-}
-
-u64 VMManager::GetNewMapRegionSize() {
- LOG_WARNING(Kernel, "(STUBBED) called");
- return 0x8000000;
-}
-
} // namespace Kernel
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index 7a7fee54a..4d66146f6 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -10,7 +10,7 @@
#include "common/common_types.h"
#include "core/hle/result.h"
#include "core/memory.h"
-#include "core/mmio.h"
+#include "core/memory_hook.h"
namespace Kernel {
@@ -41,15 +41,24 @@ enum class VMAPermission : u8 {
/// Set of values returned in MemoryInfo.state by svcQueryMemory.
enum class MemoryState : u32 {
- Free = 0,
- IO = 1,
- Normal = 2,
- Code = 3,
- Static = 4,
- Heap = 5,
- Shared = 6,
- Mapped = 6,
- ThreadLocalStorage = 12,
+ Unmapped = 0x0,
+ Io = 0x1,
+ Normal = 0x2,
+ CodeStatic = 0x3,
+ CodeMutable = 0x4,
+ Heap = 0x5,
+ Shared = 0x6,
+ ModuleCodeStatic = 0x8,
+ ModuleCodeMutable = 0x9,
+ IpcBuffer0 = 0xA,
+ Mapped = 0xB,
+ ThreadLocal = 0xC,
+ TransferMemoryIsolated = 0xD,
+ TransferMemory = 0xE,
+ ProcessMemory = 0xF,
+ IpcBuffer1 = 0x11,
+ IpcBuffer3 = 0x12,
+ KernelStack = 0x13,
};
/**
@@ -66,7 +75,7 @@ struct VirtualMemoryArea {
VMAType type = VMAType::Free;
VMAPermission permissions = VMAPermission::None;
/// Tag returned by svcQueryMemory. Not otherwise used.
- MemoryState meminfo_state = MemoryState::Free;
+ MemoryState meminfo_state = MemoryState::Unmapped;
// Settings for type = AllocatedMemoryBlock
/// Memory block backing this VMA.
@@ -81,7 +90,7 @@ struct VirtualMemoryArea {
// Settings for type = MMIO
/// Physical address of the register area this VMA maps to.
PAddr paddr = 0;
- Memory::MMIORegionPointer mmio_handler = nullptr;
+ Memory::MemoryHookPointer mmio_handler = nullptr;
/// Tests if this area can be merged to the right with `next`.
bool CanBeMergedWith(const VirtualMemoryArea& next) const;
@@ -160,7 +169,7 @@ public:
* @param mmio_handler The handler that will implement read and write for this MMIO region.
*/
ResultVal<VMAHandle> MapMMIO(VAddr target, PAddr paddr, u64 size, MemoryState state,
- Memory::MMIORegionPointer mmio_handler);
+ Memory::MemoryHookPointer mmio_handler);
/// Unmaps a range of addresses, splitting VMAs as necessary.
ResultCode UnmapRange(VAddr target, u64 size);
@@ -192,12 +201,6 @@ public:
/// Gets the total address space address size, used by svcGetInfo
u64 GetAddressSpaceSize();
- /// Gets the base address for a new memory region, used by svcGetInfo
- VAddr GetNewMapRegionBaseAddr();
-
- /// Gets the size for a new memory region, used by svcGetInfo
- u64 GetNewMapRegionSize();
-
/// Each VMManager has its own page table, which is set as the main one when the owning process
/// is scheduled.
Memory::PageTable page_table;
diff --git a/src/core/hle/kernel/wait_object.cpp b/src/core/hle/kernel/wait_object.cpp
index ec147b84c..b08ac72c1 100644
--- a/src/core/hle/kernel/wait_object.cpp
+++ b/src/core/hle/kernel/wait_object.cpp
@@ -39,7 +39,8 @@ SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() {
for (const auto& thread : waiting_threads) {
// The list of waiting threads must not contain threads that are not waiting to be awakened.
ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY ||
- thread->status == THREADSTATUS_WAIT_SYNCH_ALL,
+ thread->status == THREADSTATUS_WAIT_SYNCH_ALL ||
+ thread->status == THREADSTATUS_WAIT_HLE_EVENT,
"Inconsistent thread statuses in waiting_threads");
if (thread->current_priority >= candidate_priority)
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 10ddc4feb..052f49979 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -19,6 +19,8 @@
enum class ErrorDescription : u32 {
Success = 0,
RemoteProcessDead = 301,
+ InvalidOffset = 6061,
+ InvalidLength = 6062,
};
/**
@@ -106,11 +108,11 @@ union ResultCode {
}
constexpr bool IsSuccess() const {
- return is_error.ExtractValue(raw) == 0;
+ return raw == 0;
}
constexpr bool IsError() const {
- return is_error.ExtractValue(raw) == 1;
+ return raw != 0;
}
};
@@ -198,6 +200,9 @@ public:
}
ResultVal& operator=(const ResultVal& o) {
+ if (this == &o) {
+ return *this;
+ }
if (!empty()) {
if (!o.empty()) {
object = o.object;
diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp
index 147f4e62e..52c3491d5 100644
--- a/src/core/hle/service/acc/acc_u0.cpp
+++ b/src/core/hle/service/acc/acc_u0.cpp
@@ -9,15 +9,113 @@
namespace Service {
namespace Account {
+using Uid = std::array<u64, 2>;
+static constexpr Uid DEFAULT_USER_ID{0x10ull, 0x20ull};
+
+class IProfile final : public ServiceFramework<IProfile> {
+public:
+ IProfile() : ServiceFramework("IProfile") {
+ static const FunctionInfo functions[] = {
+ {1, &IProfile::GetBase, "GetBase"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ void GetBase(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_ACC, "(STUBBED) called");
+ ProfileBase profile_base{};
+ IPC::ResponseBuilder rb{ctx, 16};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(profile_base);
+ }
+};
+
+class IManagerForApplication final : public ServiceFramework<IManagerForApplication> {
+public:
+ IManagerForApplication() : ServiceFramework("IManagerForApplication") {
+ static const FunctionInfo functions[] = {
+ {0, &IManagerForApplication::CheckAvailability, "CheckAvailability"},
+ {1, &IManagerForApplication::GetAccountId, "GetAccountId"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ void CheckAvailability(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_ACC, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(true); // TODO: Check when this is supposed to return true and when not
+ }
+
+ void GetAccountId(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_ACC, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(0x12345678ABCDEF);
+ }
+};
+
+void ACC_U0::GetUserExistence(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_ACC, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(true); // TODO: Check when this is supposed to return true and when not
+}
+
+void ACC_U0::ListAllUsers(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_ACC, "(STUBBED) called");
+ constexpr std::array<u128, 10> user_ids{DEFAULT_USER_ID};
+ ctx.WriteBuffer(user_ids.data(), user_ids.size());
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void ACC_U0::ListOpenUsers(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_ACC, "(STUBBED) called");
+ constexpr std::array<u128, 10> user_ids{DEFAULT_USER_ID};
+ ctx.WriteBuffer(user_ids.data(), user_ids.size());
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void ACC_U0::GetProfile(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IProfile>();
+ LOG_DEBUG(Service_ACC, "called");
+}
+
void ACC_U0::InitializeApplicationInfo(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
- IPC::RequestBuilder rb{ctx, 2};
+ LOG_WARNING(Service_ACC, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void ACC_U0::GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IManagerForApplication>();
+ LOG_DEBUG(Service_ACC, "called");
+}
+
+void ACC_U0::GetLastOpenedUser(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_ACC, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 6};
rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(DEFAULT_USER_ID);
}
ACC_U0::ACC_U0() : ServiceFramework("acc:u0") {
static const FunctionInfo functions[] = {
+ {1, &ACC_U0::GetUserExistence, "GetUserExistence"},
+ {2, &ACC_U0::ListAllUsers, "ListAllUsers"},
+ {3, &ACC_U0::ListOpenUsers, "ListOpenUsers"},
+ {4, &ACC_U0::GetLastOpenedUser, "GetLastOpenedUser"},
+ {5, &ACC_U0::GetProfile, "GetProfile"},
{100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"},
+ {101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"},
};
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/acc/acc_u0.h b/src/core/hle/service/acc/acc_u0.h
index ac243d5b8..222f37282 100644
--- a/src/core/hle/service/acc/acc_u0.h
+++ b/src/core/hle/service/acc/acc_u0.h
@@ -9,13 +9,31 @@
namespace Service {
namespace Account {
+// TODO: RE this structure
+struct UserData {
+ INSERT_PADDING_BYTES(0x80);
+};
+static_assert(sizeof(UserData) == 0x80, "UserData structure has incorrect size");
+
+// TODO: RE this structure
+struct ProfileBase {
+ INSERT_PADDING_BYTES(0x38);
+};
+static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase structure has incorrect size");
+
class ACC_U0 final : public ServiceFramework<ACC_U0> {
public:
ACC_U0();
~ACC_U0() = default;
private:
+ void GetUserExistence(Kernel::HLERequestContext& ctx);
+ void ListAllUsers(Kernel::HLERequestContext& ctx);
+ void ListOpenUsers(Kernel::HLERequestContext& ctx);
+ void GetLastOpenedUser(Kernel::HLERequestContext& ctx);
+ void GetProfile(Kernel::HLERequestContext& ctx);
void InitializeApplicationInfo(Kernel::HLERequestContext& ctx);
+ void GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx);
};
} // namespace Account
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index a761bea65..bab338205 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -2,14 +2,493 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cinttypes>
+#include "core/file_sys/filesystem.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/event.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/apm/apm.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/nvflinger/nvflinger.h"
+#include "core/settings.h"
namespace Service {
namespace AM {
-void InstallInterfaces(SM::ServiceManager& service_manager) {
- std::make_shared<AppletOE>()->InstallAsService(service_manager);
+IWindowController::IWindowController() : ServiceFramework("IWindowController") {
+ static const FunctionInfo functions[] = {
+ {1, &IWindowController::GetAppletResourceUserId, "GetAppletResourceUserId"},
+ {10, &IWindowController::AcquireForegroundRights, "AcquireForegroundRights"},
+ };
+ RegisterHandlers(functions);
+}
+
+void IWindowController::GetAppletResourceUserId(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(0);
+}
+
+void IWindowController::AcquireForegroundRights(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+IAudioController::IAudioController() : ServiceFramework("IAudioController") {
+ static const FunctionInfo functions[] = {
+ {0, &IAudioController::SetExpectedMasterVolume, "SetExpectedMasterVolume"},
+ {1, &IAudioController::GetMainAppletExpectedMasterVolume,
+ "GetMainAppletExpectedMasterVolume"},
+ {2, &IAudioController::GetLibraryAppletExpectedMasterVolume,
+ "GetLibraryAppletExpectedMasterVolume"},
+ {3, nullptr, "ChangeMainAppletMasterVolume"},
+ {4, nullptr, "SetTransparentVolumeRate"},
+ };
+ RegisterHandlers(functions);
+}
+
+void IAudioController::SetExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void IAudioController::GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(volume);
+}
+
+void IAudioController::GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(volume);
+}
+
+IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") {}
+
+IDebugFunctions::IDebugFunctions() : ServiceFramework("IDebugFunctions") {}
+
+ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
+ : ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger)) {
+ static const FunctionInfo functions[] = {
+ {1, &ISelfController::LockExit, "LockExit"},
+ {2, &ISelfController::UnlockExit, "UnlockExit"},
+ {9, &ISelfController::GetLibraryAppletLaunchableEvent, "GetLibraryAppletLaunchableEvent"},
+ {10, &ISelfController::SetScreenShotPermission, "SetScreenShotPermission"},
+ {11, &ISelfController::SetOperationModeChangedNotification,
+ "SetOperationModeChangedNotification"},
+ {12, &ISelfController::SetPerformanceModeChangedNotification,
+ "SetPerformanceModeChangedNotification"},
+ {13, &ISelfController::SetFocusHandlingMode, "SetFocusHandlingMode"},
+ {14, &ISelfController::SetRestartMessageEnabled, "SetRestartMessageEnabled"},
+ {16, &ISelfController::SetOutOfFocusSuspendingEnabled, "SetOutOfFocusSuspendingEnabled"},
+ {40, &ISelfController::CreateManagedDisplayLayer, "CreateManagedDisplayLayer"},
+ };
+ RegisterHandlers(functions);
+
+ launchable_event =
+ Kernel::Event::Create(Kernel::ResetType::OneShot, "ISelfController:LaunchableEvent");
+}
+
+void ISelfController::SetFocusHandlingMode(Kernel::HLERequestContext& ctx) {
+ // Takes 3 input u8s with each field located immediately after the previous u8, these are
+ // bool flags. No output.
+
+ IPC::RequestParser rp{ctx};
+
+ struct FocusHandlingModeParams {
+ u8 unknown0;
+ u8 unknown1;
+ u8 unknown2;
+ };
+ auto flags = rp.PopRaw<FocusHandlingModeParams>();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+void ISelfController::SetRestartMessageEnabled(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+void ISelfController::SetPerformanceModeChangedNotification(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ bool flag = rp.Pop<bool>();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+
+ LOG_WARNING(Service_AM, "(STUBBED) called flag=%u", static_cast<u32>(flag));
+}
+
+void ISelfController::SetScreenShotPermission(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+void ISelfController::SetOperationModeChangedNotification(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ bool flag = rp.Pop<bool>();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+
+ LOG_WARNING(Service_AM, "(STUBBED) called flag=%u", static_cast<u32>(flag));
+}
+
+void ISelfController::SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext& ctx) {
+ // Takes 3 input u8s with each field located immediately after the previous u8, these are
+ // bool flags. No output.
+ IPC::RequestParser rp{ctx};
+
+ bool enabled = rp.Pop<bool>();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+
+ LOG_WARNING(Service_AM, "(STUBBED) called enabled=%u", static_cast<u32>(enabled));
+}
+
+void ISelfController::LockExit(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+void ISelfController::UnlockExit(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+void ISelfController::GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& ctx) {
+ launchable_event->Signal();
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(launchable_event);
+
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+void ISelfController::CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx) {
+ // TODO(Subv): Find out how AM determines the display to use, for now just create the layer
+ // in the Default display.
+ u64 display_id = nvflinger->OpenDisplay("Default");
+ u64 layer_id = nvflinger->CreateLayer(display_id);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(layer_id);
+
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter") {
+ static const FunctionInfo functions[] = {
+ {0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"},
+ {1, &ICommonStateGetter::ReceiveMessage, "ReceiveMessage"},
+ {5, &ICommonStateGetter::GetOperationMode, "GetOperationMode"},
+ {6, &ICommonStateGetter::GetPerformanceMode, "GetPerformanceMode"},
+ {9, &ICommonStateGetter::GetCurrentFocusState, "GetCurrentFocusState"},
+ };
+ RegisterHandlers(functions);
+
+ event = Kernel::Event::Create(Kernel::ResetType::OneShot, "ICommonStateGetter:Event");
+}
+
+void ICommonStateGetter::GetEventHandle(Kernel::HLERequestContext& ctx) {
+ event->Signal();
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(event);
+
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+void ICommonStateGetter::ReceiveMessage(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(15);
+
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+void ICommonStateGetter::GetCurrentFocusState(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(static_cast<u8>(FocusState::InFocus));
+
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) {
+ const bool use_docked_mode{Settings::values.use_docked_mode};
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(static_cast<u8>(use_docked_mode ? OperationMode::Docked : OperationMode::Handheld));
+
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+void ICommonStateGetter::GetPerformanceMode(Kernel::HLERequestContext& ctx) {
+ const bool use_docked_mode{Settings::values.use_docked_mode};
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(static_cast<u32>(use_docked_mode ? APM::PerformanceMode::Docked
+ : APM::PerformanceMode::Handheld));
+
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+class ILibraryAppletAccessor final : public ServiceFramework<ILibraryAppletAccessor> {
+public:
+ explicit ILibraryAppletAccessor() : ServiceFramework("ILibraryAppletAccessor") {
+ static const FunctionInfo functions[] = {
+ {0, &ILibraryAppletAccessor::GetAppletStateChangedEvent, "GetAppletStateChangedEvent"},
+ {1, nullptr, "IsCompleted"},
+ {10, nullptr, "Start"},
+ {20, nullptr, "RequestExit"},
+ {25, nullptr, "Terminate"},
+ {30, nullptr, "GetResult"},
+ {50, nullptr, "SetOutOfFocusApplicationSuspendingEnabled"},
+ {100, nullptr, "PushInData"},
+ {101, nullptr, "PopOutData"},
+ {102, nullptr, "PushExtraStorage"},
+ {103, nullptr, "PushInteractiveInData"},
+ {104, nullptr, "PopInteractiveOutData"},
+ {105, nullptr, "GetPopOutDataEvent"},
+ {106, nullptr, "GetPopInteractiveOutDataEvent"},
+ {120, nullptr, "NeedsToExitProcess"},
+ {120, nullptr, "GetLibraryAppletInfo"},
+ {150, nullptr, "RequestForAppletToGetForeground"},
+ {160, nullptr, "GetIndirectLayerConsumerHandle"},
+ };
+ RegisterHandlers(functions);
+
+ state_changed_event = Kernel::Event::Create(Kernel::ResetType::OneShot,
+ "ILibraryAppletAccessor:StateChangedEvent");
+ }
+
+private:
+ void GetAppletStateChangedEvent(Kernel::HLERequestContext& ctx) {
+ state_changed_event->Signal();
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(state_changed_event);
+
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+ }
+
+ Kernel::SharedPtr<Kernel::Event> state_changed_event;
+};
+
+ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryAppletCreator") {
+ static const FunctionInfo functions[] = {
+ {0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"},
+ {1, nullptr, "TerminateAllLibraryApplets"},
+ {2, nullptr, "AreAnyLibraryAppletsLeft"},
+ {10, nullptr, "CreateStorage"},
+ {11, nullptr, "CreateTransferMemoryStorage"},
+ {12, nullptr, "CreateHandleStorage"},
+ };
+ RegisterHandlers(functions);
+}
+
+void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<AM::ILibraryAppletAccessor>();
+
+ LOG_DEBUG(Service_AM, "called");
+}
+
+class IStorageAccessor final : public ServiceFramework<IStorageAccessor> {
+public:
+ explicit IStorageAccessor(std::vector<u8> buffer)
+ : ServiceFramework("IStorageAccessor"), buffer(std::move(buffer)) {
+ static const FunctionInfo functions[] = {
+ {0, &IStorageAccessor::GetSize, "GetSize"},
+ {11, &IStorageAccessor::Read, "Read"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ std::vector<u8> buffer;
+
+ void GetSize(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 4};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(static_cast<u64>(buffer.size()));
+
+ LOG_DEBUG(Service_AM, "called");
+ }
+
+ void Read(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ u64 offset = rp.Pop<u64>();
+
+ const size_t size{ctx.GetWriteBufferSize()};
+
+ ASSERT(offset + size <= buffer.size());
+
+ ctx.WriteBuffer(buffer.data() + offset, size);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+
+ rb.Push(RESULT_SUCCESS);
+
+ LOG_DEBUG(Service_AM, "called");
+ }
+};
+
+class IStorage final : public ServiceFramework<IStorage> {
+public:
+ explicit IStorage(std::vector<u8> buffer)
+ : ServiceFramework("IStorage"), buffer(std::move(buffer)) {
+ static const FunctionInfo functions[] = {
+ {0, &IStorage::Open, "Open"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ std::vector<u8> buffer;
+
+ void Open(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<AM::IStorageAccessor>(buffer);
+
+ LOG_DEBUG(Service_AM, "called");
+ }
+};
+
+IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationFunctions") {
+ static const FunctionInfo functions[] = {
+ {1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"},
+ {20, &IApplicationFunctions::EnsureSaveData, "EnsureSaveData"},
+ {21, &IApplicationFunctions::GetDesiredLanguage, "GetDesiredLanguage"},
+ {22, &IApplicationFunctions::SetTerminateResult, "SetTerminateResult"},
+ {66, &IApplicationFunctions::InitializeGamePlayRecording, "InitializeGamePlayRecording"},
+ {67, &IApplicationFunctions::SetGamePlayRecordingState, "SetGamePlayRecordingState"},
+ {40, &IApplicationFunctions::NotifyRunning, "NotifyRunning"},
+ };
+ RegisterHandlers(functions);
+}
+
+void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
+ constexpr u8 data[0x88] = {
+ 0xca, 0x97, 0x94, 0xc7, // Magic
+ 1, 0, 0, 0, // IsAccountSelected (bool)
+ 1, 0, 0, 0, // User Id (word 0)
+ 0, 0, 0, 0, // User Id (word 1)
+ 0, 0, 0, 0, // User Id (word 2)
+ 0, 0, 0, 0 // User Id (word 3)
+ };
+
+ std::vector<u8> buffer(data, data + sizeof(data));
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<AM::IStorage>(buffer);
+
+ LOG_DEBUG(Service_AM, "called");
+}
+
+void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ u128 uid = rp.PopRaw<u128>();
+
+ LOG_WARNING(Service, "(STUBBED) called uid = %016" PRIX64 "%016" PRIX64, uid[1], uid[0]);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+
+ FileSys::Path unused;
+ auto savedata = FileSystem::OpenFileSystem(FileSystem::Type::SaveData, unused);
+ if (savedata.Failed()) {
+ // Create the save data and return an error indicating that the operation was performed.
+ FileSystem::FormatFileSystem(FileSystem::Type::SaveData);
+ // TODO(Subv): Find out the correct error code for this.
+ rb.Push(ResultCode(ErrorModule::FS, 40));
+ } else {
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ rb.Push<u64>(0);
+}
+
+void IApplicationFunctions::SetTerminateResult(Kernel::HLERequestContext& ctx) {
+ // Takes an input u32 Result, no output.
+ // For example, in some cases official apps use this with error 0x2A2 then uses svcBreak.
+
+ IPC::RequestParser rp{ctx};
+ u32 result = rp.Pop<u32>();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+
+ LOG_WARNING(Service_AM, "(STUBBED) called, result=0x%08X", result);
+}
+
+void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(SystemLanguage::English);
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+void IApplicationFunctions::InitializeGamePlayRecording(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+void IApplicationFunctions::SetGamePlayRecordingState(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+void IApplicationFunctions::NotifyRunning(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u8>(0); // Unknown, seems to be ignored by official processes
+
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+void InstallInterfaces(SM::ServiceManager& service_manager,
+ std::shared_ptr<NVFlinger::NVFlinger> nvflinger) {
+ std::make_shared<AppletAE>(nvflinger)->InstallAsService(service_manager);
+ std::make_shared<AppletOE>(nvflinger)->InstallAsService(service_manager);
}
} // namespace AM
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 63d86cd41..27dbd8c95 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -4,13 +4,126 @@
#pragma once
+#include <memory>
#include "core/hle/service/service.h"
+namespace Kernel {
+class Event;
+}
+
namespace Service {
+namespace NVFlinger {
+class NVFlinger;
+}
+
namespace AM {
+// TODO: Add more languages
+enum SystemLanguage {
+ Japanese = 0,
+ English = 1,
+};
+
+class IWindowController final : public ServiceFramework<IWindowController> {
+public:
+ IWindowController();
+
+private:
+ void GetAppletResourceUserId(Kernel::HLERequestContext& ctx);
+ void AcquireForegroundRights(Kernel::HLERequestContext& ctx);
+};
+
+class IAudioController final : public ServiceFramework<IAudioController> {
+public:
+ IAudioController();
+
+private:
+ void SetExpectedMasterVolume(Kernel::HLERequestContext& ctx);
+ void GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx);
+ void GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx);
+
+ u32 volume{100};
+};
+
+class IDisplayController final : public ServiceFramework<IDisplayController> {
+public:
+ IDisplayController();
+};
+
+class IDebugFunctions final : public ServiceFramework<IDebugFunctions> {
+public:
+ IDebugFunctions();
+};
+
+class ISelfController final : public ServiceFramework<ISelfController> {
+public:
+ ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger);
+
+private:
+ void SetFocusHandlingMode(Kernel::HLERequestContext& ctx);
+ void SetRestartMessageEnabled(Kernel::HLERequestContext& ctx);
+ void SetPerformanceModeChangedNotification(Kernel::HLERequestContext& ctx);
+ void SetOperationModeChangedNotification(Kernel::HLERequestContext& ctx);
+ void SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext& ctx);
+ void LockExit(Kernel::HLERequestContext& ctx);
+ void UnlockExit(Kernel::HLERequestContext& ctx);
+ void GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& ctx);
+ void CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx);
+ void SetScreenShotPermission(Kernel::HLERequestContext& ctx);
+
+ std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
+ Kernel::SharedPtr<Kernel::Event> launchable_event;
+};
+
+class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
+public:
+ ICommonStateGetter();
+
+private:
+ enum class FocusState : u8 {
+ InFocus = 1,
+ NotInFocus = 2,
+ };
+
+ enum class OperationMode : u8 {
+ Handheld = 0,
+ Docked = 1,
+ };
+
+ void GetEventHandle(Kernel::HLERequestContext& ctx);
+ void ReceiveMessage(Kernel::HLERequestContext& ctx);
+ void GetCurrentFocusState(Kernel::HLERequestContext& ctx);
+ void GetOperationMode(Kernel::HLERequestContext& ctx);
+ void GetPerformanceMode(Kernel::HLERequestContext& ctx);
+
+ Kernel::SharedPtr<Kernel::Event> event;
+};
+
+class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> {
+public:
+ ILibraryAppletCreator();
+
+private:
+ void CreateLibraryApplet(Kernel::HLERequestContext& ctx);
+};
+
+class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
+public:
+ IApplicationFunctions();
+
+private:
+ void PopLaunchParameter(Kernel::HLERequestContext& ctx);
+ void EnsureSaveData(Kernel::HLERequestContext& ctx);
+ void SetTerminateResult(Kernel::HLERequestContext& ctx);
+ void GetDesiredLanguage(Kernel::HLERequestContext& ctx);
+ void InitializeGamePlayRecording(Kernel::HLERequestContext& ctx);
+ void SetGamePlayRecordingState(Kernel::HLERequestContext& ctx);
+ void NotifyRunning(Kernel::HLERequestContext& ctx);
+};
+
/// Registers all AM services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager,
+ std::shared_ptr<NVFlinger::NVFlinger> nvflinger);
} // namespace AM
} // namespace Service
diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp
new file mode 100644
index 000000000..0e51caa70
--- /dev/null
+++ b/src/core/hle/service/am/applet_ae.cpp
@@ -0,0 +1,112 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/service/am/am.h"
+#include "core/hle/service/am/applet_ae.h"
+#include "core/hle/service/nvflinger/nvflinger.h"
+
+namespace Service {
+namespace AM {
+
+class ILibraryAppletProxy final : public ServiceFramework<ILibraryAppletProxy> {
+public:
+ ILibraryAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
+ : ServiceFramework("ILibraryAppletProxy"), nvflinger(std::move(nvflinger)) {
+ static const FunctionInfo functions[] = {
+ {0, &ILibraryAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"},
+ {1, &ILibraryAppletProxy::GetSelfController, "GetSelfController"},
+ {2, &ILibraryAppletProxy::GetWindowController, "GetWindowController"},
+ {3, &ILibraryAppletProxy::GetAudioController, "GetAudioController"},
+ {4, &ILibraryAppletProxy::GetDisplayController, "GetDisplayController"},
+ {11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
+ {20, &ILibraryAppletProxy::GetApplicationFunctions, "GetApplicationFunctions"},
+ {1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ void GetCommonStateGetter(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<ICommonStateGetter>();
+ LOG_DEBUG(Service_AM, "called");
+ }
+
+ void GetSelfController(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<ISelfController>(nvflinger);
+ LOG_DEBUG(Service_AM, "called");
+ }
+
+ void GetWindowController(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IWindowController>();
+ LOG_DEBUG(Service_AM, "called");
+ }
+
+ void GetAudioController(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IAudioController>();
+ LOG_DEBUG(Service_AM, "called");
+ }
+
+ void GetDisplayController(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IDisplayController>();
+ LOG_DEBUG(Service_AM, "called");
+ }
+
+ void GetDebugFunctions(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IDebugFunctions>();
+ LOG_DEBUG(Service_AM, "called");
+ }
+
+ void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<ILibraryAppletCreator>();
+ LOG_DEBUG(Service_AM, "called");
+ }
+
+ void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IApplicationFunctions>();
+ LOG_DEBUG(Service_AM, "called");
+ }
+
+ std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
+};
+
+void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger);
+ LOG_DEBUG(Service_AM, "called");
+}
+
+AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
+ : ServiceFramework("appletAE"), nvflinger(std::move(nvflinger)) {
+ static const FunctionInfo functions[] = {
+ {100, nullptr, "OpenSystemAppletProxy"},
+ {200, &AppletAE::OpenLibraryAppletProxyOld, "OpenLibraryAppletProxyOld"},
+ {201, nullptr, "OpenLibraryAppletProxy"},
+ {300, nullptr, "OpenOverlayAppletProxy"},
+ {350, nullptr, "OpenSystemApplicationProxy"},
+ {400, nullptr, "CreateSelfLibraryAppletCreatorForDevelop"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace AM
+} // namespace Service
diff --git a/src/core/hle/service/am/applet_ae.h b/src/core/hle/service/am/applet_ae.h
new file mode 100644
index 000000000..38fc428fb
--- /dev/null
+++ b/src/core/hle/service/am/applet_ae.h
@@ -0,0 +1,30 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace NVFlinger {
+class NVFlinger;
+}
+
+namespace AM {
+
+class AppletAE final : public ServiceFramework<AppletAE> {
+public:
+ AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger);
+ ~AppletAE() = default;
+
+private:
+ void OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx);
+
+ std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
+};
+
+} // namespace AM
+} // namespace Service
diff --git a/src/core/hle/service/am/applet_oe.cpp b/src/core/hle/service/am/applet_oe.cpp
index b360e7e5f..bdcebe689 100644
--- a/src/core/hle/service/am/applet_oe.cpp
+++ b/src/core/hle/service/am/applet_oe.cpp
@@ -4,260 +4,17 @@
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
-#include "core/hle/kernel/event.h"
+#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_oe.h"
-#include "core/hle/service/apm/apm.h"
+#include "core/hle/service/nvflinger/nvflinger.h"
namespace Service {
namespace AM {
-class IWindowController final : public ServiceFramework<IWindowController> {
-public:
- IWindowController() : ServiceFramework("IWindowController") {
- static const FunctionInfo functions[] = {
- {1, &IWindowController::GetAppletResourceUserId, "GetAppletResourceUserId"},
- {10, &IWindowController::AcquireForegroundRights, "AcquireForegroundRights"},
- };
- RegisterHandlers(functions);
- }
-
-private:
- void GetAppletResourceUserId(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
- IPC::RequestBuilder rb{ctx, 4};
- rb.Push(RESULT_SUCCESS);
- rb.Push<u64>(0);
- }
-
- void AcquireForegroundRights(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
- IPC::RequestBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
-};
-
-class IAudioController final : public ServiceFramework<IAudioController> {
-public:
- IAudioController() : ServiceFramework("IAudioController") {}
-};
-
-class IDisplayController final : public ServiceFramework<IDisplayController> {
-public:
- IDisplayController() : ServiceFramework("IDisplayController") {}
-};
-
-class IDebugFunctions final : public ServiceFramework<IDebugFunctions> {
-public:
- IDebugFunctions() : ServiceFramework("IDebugFunctions") {}
-};
-
-class ISelfController final : public ServiceFramework<ISelfController> {
-public:
- ISelfController() : ServiceFramework("ISelfController") {
- static const FunctionInfo functions[] = {
- {11, &ISelfController::SetOperationModeChangedNotification,
- "SetOperationModeChangedNotification"},
- {12, &ISelfController::SetPerformanceModeChangedNotification,
- "SetPerformanceModeChangedNotification"},
- {13, &ISelfController::SetFocusHandlingMode, "SetFocusHandlingMode"},
- {14, &ISelfController::SetRestartMessageEnabled, "SetRestartMessageEnabled"},
- {16, &ISelfController::SetOutOfFocusSuspendingEnabled,
- "SetOutOfFocusSuspendingEnabled"},
- };
- RegisterHandlers(functions);
- }
-
-private:
- void SetFocusHandlingMode(Kernel::HLERequestContext& ctx) {
- // Takes 3 input u8s with each field located immediately after the previous u8, these are
- // bool flags. No output.
-
- IPC::RequestParser rp{ctx};
-
- struct FocusHandlingModeParams {
- u8 unknown0;
- u8 unknown1;
- u8 unknown2;
- };
- auto flags = rp.PopRaw<FocusHandlingModeParams>();
-
- IPC::RequestBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
-
- LOG_WARNING(Service, "(STUBBED) called");
- }
-
- void SetRestartMessageEnabled(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
-
- LOG_WARNING(Service, "(STUBBED) called");
- }
-
- void SetPerformanceModeChangedNotification(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
-
- bool flag = rp.Pop<bool>();
-
- IPC::RequestBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
-
- LOG_WARNING(Service, "(STUBBED) called flag=%u", static_cast<u32>(flag));
- }
-
- void SetOperationModeChangedNotification(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
-
- bool flag = rp.Pop<bool>();
-
- IPC::RequestBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
-
- LOG_WARNING(Service, "(STUBBED) called flag=%u", static_cast<u32>(flag));
- }
-
- void SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext& ctx) {
- // Takes 3 input u8s with each field located immediately after the previous u8, these are
- // bool flags. No output.
- IPC::RequestParser rp{ctx};
-
- bool enabled = rp.Pop<bool>();
-
- IPC::RequestBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
-
- LOG_WARNING(Service, "(STUBBED) called enabled=%u", static_cast<u32>(enabled));
- }
-};
-
-class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
-public:
- ICommonStateGetter() : ServiceFramework("ICommonStateGetter") {
- static const FunctionInfo functions[] = {
- {0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"},
- {1, &ICommonStateGetter::ReceiveMessage, "ReceiveMessage"},
- {5, &ICommonStateGetter::GetOperationMode, "GetOperationMode"},
- {6, &ICommonStateGetter::GetPerformanceMode, "GetPerformanceMode"},
- {9, &ICommonStateGetter::GetCurrentFocusState, "GetCurrentFocusState"},
- };
- RegisterHandlers(functions);
-
- event = Kernel::Event::Create(Kernel::ResetType::OneShot, "ICommonStateGetter:Event");
- }
-
-private:
- enum class FocusState : u8 {
- InFocus = 1,
- NotInFocus = 2,
- };
-
- enum class OperationMode : u8 {
- Handheld = 0,
- Docked = 1,
- };
-
- void GetEventHandle(Kernel::HLERequestContext& ctx) {
- event->Signal();
-
- IPC::RequestBuilder rb{ctx, 2, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(event);
-
- LOG_WARNING(Service, "(STUBBED) called");
- }
-
- void ReceiveMessage(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(15);
-
- LOG_WARNING(Service, "(STUBBED) called");
- }
-
- void GetCurrentFocusState(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push(static_cast<u8>(FocusState::InFocus));
-
- LOG_WARNING(Service, "(STUBBED) called");
- }
-
- void GetOperationMode(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push(static_cast<u8>(OperationMode::Handheld));
-
- LOG_WARNING(Service, "(STUBBED) called");
- }
-
- void GetPerformanceMode(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push(static_cast<u32>(APM::PerformanceMode::Handheld));
-
- LOG_WARNING(Service, "(STUBBED) called");
- }
-
- Kernel::SharedPtr<Kernel::Event> event;
-};
-
-class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
-public:
- IApplicationFunctions() : ServiceFramework("IApplicationFunctions") {
- static const FunctionInfo functions[] = {
- {22, &IApplicationFunctions::SetTerminateResult, "SetTerminateResult"},
- {66, &IApplicationFunctions::InitializeGamePlayRecording,
- "InitializeGamePlayRecording"},
- {67, &IApplicationFunctions::SetGamePlayRecordingState, "SetGamePlayRecordingState"},
- {40, &IApplicationFunctions::NotifyRunning, "NotifyRunning"},
- };
- RegisterHandlers(functions);
- }
-
-private:
- void SetTerminateResult(Kernel::HLERequestContext& ctx) {
- // Takes an input u32 Result, no output.
- // For example, in some cases official apps use this with error 0x2A2 then uses svcBreak.
-
- IPC::RequestParser rp{ctx};
- u32 result = rp.Pop<u32>();
-
- IPC::RequestBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
-
- LOG_WARNING(Service, "(STUBBED) called, result=0x%08X", result);
- }
-
- void InitializeGamePlayRecording(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service, "(STUBBED) called");
- }
-
- void SetGamePlayRecordingState(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
-
- LOG_WARNING(Service, "(STUBBED) called");
- }
-
- void NotifyRunning(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push<u8>(0); // Unknown, seems to be ignored by official processes
-
- LOG_WARNING(Service, "(STUBBED) called");
- }
-};
-
-class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> {
-public:
- ILibraryAppletCreator() : ServiceFramework("ILibraryAppletCreator") {}
-};
-
class IApplicationProxy final : public ServiceFramework<IApplicationProxy> {
public:
- IApplicationProxy() : ServiceFramework("IApplicationProxy") {
+ IApplicationProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
+ : ServiceFramework("IApplicationProxy"), nvflinger(std::move(nvflinger)) {
static const FunctionInfo functions[] = {
{0, &IApplicationProxy::GetCommonStateGetter, "GetCommonStateGetter"},
{1, &IApplicationProxy::GetSelfController, "GetSelfController"},
@@ -273,70 +30,73 @@ public:
private:
void GetAudioController(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 2, 0, 0, 1};
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IAudioController>();
- LOG_DEBUG(Service, "called");
+ LOG_DEBUG(Service_AM, "called");
}
void GetDisplayController(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 2, 0, 0, 1};
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IDisplayController>();
- LOG_DEBUG(Service, "called");
+ LOG_DEBUG(Service_AM, "called");
}
void GetDebugFunctions(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 2, 0, 0, 1};
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IDebugFunctions>();
- LOG_DEBUG(Service, "called");
+ LOG_DEBUG(Service_AM, "called");
}
void GetWindowController(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 2, 0, 0, 1};
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IWindowController>();
- LOG_DEBUG(Service, "called");
+ LOG_DEBUG(Service_AM, "called");
}
void GetSelfController(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 2, 0, 0, 1};
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISelfController>();
- LOG_DEBUG(Service, "called");
+ rb.PushIpcInterface<ISelfController>(nvflinger);
+ LOG_DEBUG(Service_AM, "called");
}
void GetCommonStateGetter(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 2, 0, 0, 1};
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ICommonStateGetter>();
- LOG_DEBUG(Service, "called");
+ LOG_DEBUG(Service_AM, "called");
}
void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 2, 0, 0, 1};
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ILibraryAppletCreator>();
- LOG_DEBUG(Service, "called");
+ LOG_DEBUG(Service_AM, "called");
}
void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 2, 0, 0, 1};
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IApplicationFunctions>();
- LOG_DEBUG(Service, "called");
+ LOG_DEBUG(Service_AM, "called");
}
+
+ std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
};
void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 2, 0, 0, 1};
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IApplicationProxy>();
- LOG_DEBUG(Service, "called");
+ rb.PushIpcInterface<IApplicationProxy>(nvflinger);
+ LOG_DEBUG(Service_AM, "called");
}
-AppletOE::AppletOE() : ServiceFramework("appletOE") {
+AppletOE::AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
+ : ServiceFramework("appletOE"), nvflinger(std::move(nvflinger)) {
static const FunctionInfo functions[] = {
{0x00000000, &AppletOE::OpenApplicationProxy, "OpenApplicationProxy"},
};
diff --git a/src/core/hle/service/am/applet_oe.h b/src/core/hle/service/am/applet_oe.h
index beb75bf2a..d2ab44c67 100644
--- a/src/core/hle/service/am/applet_oe.h
+++ b/src/core/hle/service/am/applet_oe.h
@@ -4,19 +4,26 @@
#pragma once
+#include <memory>
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/service.h"
namespace Service {
+namespace NVFlinger {
+class NVFlinger;
+}
+
namespace AM {
class AppletOE final : public ServiceFramework<AppletOE> {
public:
- AppletOE();
+ AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger);
~AppletOE() = default;
private:
void OpenApplicationProxy(Kernel::HLERequestContext& ctx);
+
+ std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
};
} // namespace AM
diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp
index 260683201..8b55d2fcb 100644
--- a/src/core/hle/service/aoc/aoc_u.cpp
+++ b/src/core/hle/service/aoc/aoc_u.cpp
@@ -2,16 +2,44 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
#include "core/hle/service/aoc/aoc_u.h"
namespace Service {
namespace AOC {
+AOC_U::AOC_U() : ServiceFramework("aoc:u") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "CountAddOnContentByApplicationId"},
+ {1, nullptr, "ListAddOnContentByApplicationId"},
+ {2, &AOC_U::CountAddOnContent, "CountAddOnContent"},
+ {3, &AOC_U::ListAddOnContent, "ListAddOnContent"},
+ {4, nullptr, "GetAddOnContentBaseIdByApplicationId"},
+ {5, nullptr, "GetAddOnContentBaseId"},
+ {6, nullptr, "PrepareAddOnContentByApplicationId"},
+ {7, nullptr, "PrepareAddOnContent"},
+ };
+ RegisterHandlers(functions);
+}
+
+void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(0);
+ LOG_WARNING(Service_AOC, "(STUBBED) called");
+}
+
+void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(0);
+ LOG_WARNING(Service_AOC, "(STUBBED) called");
+}
+
void InstallInterfaces(SM::ServiceManager& service_manager) {
std::make_shared<AOC_U>()->InstallAsService(service_manager);
}
-AOC_U::AOC_U() : ServiceFramework("aoc:u") {}
-
} // namespace AOC
} // namespace Service
diff --git a/src/core/hle/service/aoc/aoc_u.h b/src/core/hle/service/aoc/aoc_u.h
index 0cbbf1e5d..6e0ba15a5 100644
--- a/src/core/hle/service/aoc/aoc_u.h
+++ b/src/core/hle/service/aoc/aoc_u.h
@@ -13,6 +13,10 @@ class AOC_U final : public ServiceFramework<AOC_U> {
public:
AOC_U();
~AOC_U() = default;
+
+private:
+ void CountAddOnContent(Kernel::HLERequestContext& ctx);
+ void ListAddOnContent(Kernel::HLERequestContext& ctx);
};
/// Registers all AOC services with the specified service manager.
diff --git a/src/core/hle/service/apm/apm.cpp b/src/core/hle/service/apm/apm.cpp
index 66d94ff52..c4b09b435 100644
--- a/src/core/hle/service/apm/apm.cpp
+++ b/src/core/hle/service/apm/apm.cpp
@@ -5,61 +5,15 @@
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/apm/apm.h"
+#include "core/hle/service/apm/interface.h"
namespace Service {
namespace APM {
void InstallInterfaces(SM::ServiceManager& service_manager) {
- std::make_shared<APM>()->InstallAsService(service_manager);
-}
-
-class ISession final : public ServiceFramework<ISession> {
-public:
- ISession() : ServiceFramework("ISession") {
- static const FunctionInfo functions[] = {
- {0, &ISession::SetPerformanceConfiguration, "SetPerformanceConfiguration"},
- {1, &ISession::GetPerformanceConfiguration, "GetPerformanceConfiguration"},
- };
- RegisterHandlers(functions);
- }
-
-private:
- void SetPerformanceConfiguration(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
-
- auto mode = static_cast<PerformanceMode>(rp.Pop<u32>());
- u32 config = rp.Pop<u32>();
-
- IPC::RequestBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
-
- LOG_WARNING(Service, "(STUBBED) called mode=%u config=%u", static_cast<u32>(mode), config);
- }
-
- void GetPerformanceConfiguration(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
-
- auto mode = static_cast<PerformanceMode>(rp.Pop<u32>());
-
- IPC::RequestBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // Performance configuration
-
- LOG_WARNING(Service, "(STUBBED) called mode=%u", static_cast<u32>(mode));
- }
-};
-
-APM::APM() : ServiceFramework("apm") {
- static const FunctionInfo functions[] = {
- {0x00000000, &APM::OpenSession, "OpenSession"}, {0x00000001, nullptr, "GetPerformanceMode"},
- };
- RegisterHandlers(functions);
-}
-
-void APM::OpenSession(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 2, 0, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISession>();
+ auto module_ = std::make_shared<Module>();
+ std::make_shared<APM>(module_, "apm")->InstallAsService(service_manager);
+ std::make_shared<APM>(module_, "apm:p")->InstallAsService(service_manager);
}
} // namespace APM
diff --git a/src/core/hle/service/apm/apm.h b/src/core/hle/service/apm/apm.h
index 90a1afbbc..070ab21f8 100644
--- a/src/core/hle/service/apm/apm.h
+++ b/src/core/hle/service/apm/apm.h
@@ -14,13 +14,10 @@ enum class PerformanceMode : u8 {
Docked = 1,
};
-class APM final : public ServiceFramework<APM> {
+class Module final {
public:
- APM();
- ~APM() = default;
-
-private:
- void OpenSession(Kernel::HLERequestContext& ctx);
+ Module() = default;
+ ~Module() = default;
};
/// Registers all AM services with the specified service manager.
diff --git a/src/core/hle/service/apm/interface.cpp b/src/core/hle/service/apm/interface.cpp
new file mode 100644
index 000000000..0179351ba
--- /dev/null
+++ b/src/core/hle/service/apm/interface.cpp
@@ -0,0 +1,66 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/service/apm/apm.h"
+#include "core/hle/service/apm/interface.h"
+
+namespace Service {
+namespace APM {
+
+class ISession final : public ServiceFramework<ISession> {
+public:
+ ISession() : ServiceFramework("ISession") {
+ static const FunctionInfo functions[] = {
+ {0, &ISession::SetPerformanceConfiguration, "SetPerformanceConfiguration"},
+ {1, &ISession::GetPerformanceConfiguration, "GetPerformanceConfiguration"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ void SetPerformanceConfiguration(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto mode = static_cast<PerformanceMode>(rp.Pop<u32>());
+ u32 config = rp.Pop<u32>();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+
+ LOG_WARNING(Service_APM, "(STUBBED) called mode=%u config=%u", static_cast<u32>(mode),
+ config);
+ }
+
+ void GetPerformanceConfiguration(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto mode = static_cast<PerformanceMode>(rp.Pop<u32>());
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0); // Performance configuration
+
+ LOG_WARNING(Service_APM, "(STUBBED) called mode=%u", static_cast<u32>(mode));
+ }
+};
+
+APM::APM(std::shared_ptr<Module> apm, const char* name)
+ : ServiceFramework(name), apm(std::move(apm)) {
+ static const FunctionInfo functions[] = {
+ {0, &APM::OpenSession, "OpenSession"},
+ {1, nullptr, "GetPerformanceMode"},
+ };
+ RegisterHandlers(functions);
+}
+
+void APM::OpenSession(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<ISession>();
+}
+
+} // namespace APM
+} // namespace Service
diff --git a/src/core/hle/service/apm/interface.h b/src/core/hle/service/apm/interface.h
new file mode 100644
index 000000000..7d53721de
--- /dev/null
+++ b/src/core/hle/service/apm/interface.h
@@ -0,0 +1,27 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace APM {
+
+class APM final : public ServiceFramework<APM> {
+public:
+ APM(std::shared_ptr<Module> apm, const char* name);
+ ~APM() = default;
+
+private:
+ void OpenSession(Kernel::HLERequestContext& ctx);
+
+ std::shared_ptr<Module> apm;
+};
+
+/// Registers all AM services with the specified service manager.
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace APM
+} // namespace Service
diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp
new file mode 100644
index 000000000..ee749fddd
--- /dev/null
+++ b/src/core/hle/service/audio/audin_u.cpp
@@ -0,0 +1,41 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/service/audio/audin_u.h"
+
+namespace Service {
+namespace Audio {
+
+class IAudioIn final : public ServiceFramework<IAudioIn> {
+public:
+ IAudioIn() : ServiceFramework("IAudioIn") {
+ static const FunctionInfo functions[] = {
+ {0x0, nullptr, "GetAudioInState"},
+ {0x1, nullptr, "StartAudioIn"},
+ {0x2, nullptr, "StopAudioIn"},
+ {0x3, nullptr, "AppendAudioInBuffer_1"},
+ {0x4, nullptr, "RegisterBufferEvent"},
+ {0x5, nullptr, "GetReleasedAudioInBuffer_1"},
+ {0x6, nullptr, "ContainsAudioInBuffer"},
+ {0x7, nullptr, "AppendAudioInBuffer_2"},
+ {0x8, nullptr, "GetReleasedAudioInBuffer_2"},
+ };
+ RegisterHandlers(functions);
+ }
+ ~IAudioIn() = default;
+};
+
+AudInU::AudInU() : ServiceFramework("audin:u") {
+ static const FunctionInfo functions[] = {
+ {0x00000000, nullptr, "ListAudioIns"},
+ {0x00000001, nullptr, "OpenAudioIn"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Audio
+} // namespace Service
diff --git a/src/core/hle/service/audio/audin_u.h b/src/core/hle/service/audio/audin_u.h
new file mode 100644
index 000000000..2b8576756
--- /dev/null
+++ b/src/core/hle/service/audio/audin_u.h
@@ -0,0 +1,23 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Kernel {
+class HLERequestContext;
+}
+
+namespace Service {
+namespace Audio {
+
+class AudInU final : public ServiceFramework<AudInU> {
+public:
+ explicit AudInU();
+ ~AudInU() = default;
+};
+
+} // namespace Audio
+} // namespace Service
diff --git a/src/core/hle/service/audio/audio.cpp b/src/core/hle/service/audio/audio.cpp
index 2b4c6c5d0..3f7fb44eb 100644
--- a/src/core/hle/service/audio/audio.cpp
+++ b/src/core/hle/service/audio/audio.cpp
@@ -2,14 +2,22 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "core/hle/service/audio/audin_u.h"
#include "core/hle/service/audio/audio.h"
#include "core/hle/service/audio/audout_u.h"
+#include "core/hle/service/audio/audrec_u.h"
+#include "core/hle/service/audio/audren_u.h"
+#include "core/hle/service/audio/codecctl.h"
namespace Service {
namespace Audio {
void InstallInterfaces(SM::ServiceManager& service_manager) {
std::make_shared<AudOutU>()->InstallAsService(service_manager);
+ std::make_shared<AudInU>()->InstallAsService(service_manager);
+ std::make_shared<AudRecU>()->InstallAsService(service_manager);
+ std::make_shared<AudRenU>()->InstallAsService(service_manager);
+ std::make_shared<CodecCtl>()->InstallAsService(service_manager);
}
} // namespace Audio
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index c028262c6..e873d768f 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -2,23 +2,195 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <vector>
#include "common/logging/log.h"
+#include "core/core_timing.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/event.h"
+#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/audio/audout_u.h"
namespace Service {
namespace Audio {
+/// Switch sample rate frequency
+constexpr u32 sample_rate{48000};
+/// TODO(st4rk): dynamic number of channels, as I think Switch has support
+/// to more audio channels (probably when Docked I guess)
+constexpr u32 audio_channels{2};
+/// TODO(st4rk): find a proper value for the audio_ticks
+constexpr u64 audio_ticks{static_cast<u64>(BASE_CLOCK_RATE / 500)};
+
+class IAudioOut final : public ServiceFramework<IAudioOut> {
+public:
+ IAudioOut() : ServiceFramework("IAudioOut"), audio_out_state(AudioState::Stopped) {
+ static const FunctionInfo functions[] = {
+ {0x0, nullptr, "GetAudioOutState"},
+ {0x1, &IAudioOut::StartAudioOut, "StartAudioOut"},
+ {0x2, &IAudioOut::StopAudioOut, "StopAudioOut"},
+ {0x3, &IAudioOut::AppendAudioOutBuffer_1, "AppendAudioOutBuffer_1"},
+ {0x4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"},
+ {0x5, &IAudioOut::GetReleasedAudioOutBuffer_1, "GetReleasedAudioOutBuffer_1"},
+ {0x6, nullptr, "ContainsAudioOutBuffer"},
+ {0x7, nullptr, "AppendAudioOutBuffer_2"},
+ {0x8, nullptr, "GetReleasedAudioOutBuffer_2"},
+ };
+ RegisterHandlers(functions);
+
+ // This is the event handle used to check if the audio buffer was released
+ buffer_event =
+ Kernel::Event::Create(Kernel::ResetType::OneShot, "IAudioOutBufferReleasedEvent");
+
+ // Register event callback to update the Audio Buffer
+ audio_event = CoreTiming::RegisterEvent(
+ "IAudioOut::UpdateAudioBuffersCallback", [this](u64 userdata, int cycles_late) {
+ UpdateAudioBuffersCallback();
+ CoreTiming::ScheduleEvent(audio_ticks - cycles_late, audio_event);
+ });
+
+ // Start the audio event
+ CoreTiming::ScheduleEvent(audio_ticks, audio_event);
+ }
+
+ ~IAudioOut() {
+ CoreTiming::UnscheduleEvent(audio_event, 0);
+ }
+
+private:
+ void StartAudioOut(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
+
+ // Start audio
+ audio_out_state = AudioState::Started;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void StopAudioOut(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
+
+ // Stop audio
+ audio_out_state = AudioState::Stopped;
+
+ queue_keys.clear();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void RegisterBufferEvent(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(buffer_event);
+ }
+
+ void AppendAudioOutBuffer_1(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+
+ const u64 key{rp.Pop<u64>()};
+ queue_keys.insert(queue_keys.begin(), key);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetReleasedAudioOutBuffer_1(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
+
+ // TODO(st4rk): This is how libtransistor currently implements the
+ // GetReleasedAudioOutBuffer, it should return the key (a VAddr) to the app and this address
+ // is used to know which buffer should be filled with data and send again to the service
+ // through AppendAudioOutBuffer. Check if this is the proper way to do it.
+ u64 key{0};
+
+ if (queue_keys.size()) {
+ key = queue_keys.back();
+ queue_keys.pop_back();
+ }
+
+ ctx.WriteBuffer(&key, sizeof(u64));
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ // TODO(st4rk): This might be the total of released buffers, needs to be verified on
+ // hardware
+ rb.Push<u32>(static_cast<u32>(queue_keys.size()));
+ }
+
+ void UpdateAudioBuffersCallback() {
+ if (audio_out_state != AudioState::Started) {
+ return;
+ }
+
+ if (queue_keys.empty()) {
+ return;
+ }
+
+ buffer_event->Signal();
+ }
+
+ enum class AudioState : u32 {
+ Started,
+ Stopped,
+ };
+
+ /// This is used to trigger the audio event callback that is going to read the samples from the
+ /// audio_buffer list and enqueue the samples using the sink (audio_core).
+ CoreTiming::EventType* audio_event;
+
+ /// This is the evend handle used to check if the audio buffer was released
+ Kernel::SharedPtr<Kernel::Event> buffer_event;
+
+ /// (st4rk): This is just a temporary workaround for the future implementation. Libtransistor
+ /// uses the key as an address in the App, so we need to return when the
+ /// GetReleasedAudioOutBuffer_1 is called, otherwise we'll run in problems, because
+ /// libtransistor uses the key returned as an pointer.
+ std::vector<u64> queue_keys;
+
+ AudioState audio_out_state;
+};
+
void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
- IPC::RequestBuilder rb{ctx, 2};
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+
+ const std::string audio_interface = "AudioInterface";
+ ctx.WriteBuffer(audio_interface.c_str(), audio_interface.size());
+
+ IPC::ResponseBuilder rb = rp.MakeBuilder(3, 0, 0);
+
+ rb.Push(RESULT_SUCCESS);
+ // TODO(st4rk): We're currently returning only one audio interface (stringlist size). However,
+ // it's highly possible to have more than one interface (despite that libtransistor requires
+ // only one).
+ rb.Push<u32>(1);
+}
+
+void AudOutU::OpenAudioOut(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
+
+ if (!audio_out_interface) {
+ audio_out_interface = std::make_shared<IAudioOut>();
+ }
+
+ IPC::ResponseBuilder rb{ctx, 6, 0, 1};
rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(sample_rate);
+ rb.Push<u32>(audio_channels);
+ rb.Push<u32>(static_cast<u32>(PcmFormat::Int16));
+ rb.Push<u32>(0); // This field is unknown
+ rb.PushIpcInterface<Audio::IAudioOut>(audio_out_interface);
}
AudOutU::AudOutU() : ServiceFramework("audout:u") {
- static const FunctionInfo functions[] = {
- {0x00000000, &AudOutU::ListAudioOuts, "ListAudioOuts"},
- };
+ static const FunctionInfo functions[] = {{0x00000000, &AudOutU::ListAudioOuts, "ListAudioOuts"},
+ {0x00000001, &AudOutU::OpenAudioOut, "OpenAudioOut"},
+ {0x00000002, nullptr, "Unknown2"},
+ {0x00000003, nullptr, "Unknown3"}};
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/audio/audout_u.h b/src/core/hle/service/audio/audout_u.h
index 42680af94..7fbce2225 100644
--- a/src/core/hle/service/audio/audout_u.h
+++ b/src/core/hle/service/audio/audout_u.h
@@ -4,19 +4,37 @@
#pragma once
-#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/service.h"
+namespace Kernel {
+class HLERequestContext;
+}
+
namespace Service {
namespace Audio {
+class IAudioOut;
+
class AudOutU final : public ServiceFramework<AudOutU> {
public:
AudOutU();
~AudOutU() = default;
private:
+ std::shared_ptr<IAudioOut> audio_out_interface;
+
void ListAudioOuts(Kernel::HLERequestContext& ctx);
+ void OpenAudioOut(Kernel::HLERequestContext& ctx);
+
+ enum class PcmFormat : u32 {
+ Invalid = 0,
+ Int8 = 1,
+ Int16 = 2,
+ Int24 = 3,
+ Int32 = 4,
+ PcmFloat = 5,
+ Adpcm = 6,
+ };
};
} // namespace Audio
diff --git a/src/core/hle/service/audio/audrec_u.cpp b/src/core/hle/service/audio/audrec_u.cpp
new file mode 100644
index 000000000..f2626ec70
--- /dev/null
+++ b/src/core/hle/service/audio/audrec_u.cpp
@@ -0,0 +1,38 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/service/audio/audrec_u.h"
+
+namespace Service {
+namespace Audio {
+
+class IFinalOutputRecorder final : public ServiceFramework<IFinalOutputRecorder> {
+public:
+ IFinalOutputRecorder() : ServiceFramework("IFinalOutputRecorder") {
+ static const FunctionInfo functions[] = {
+ {0x0, nullptr, "GetFinalOutputRecorderState"},
+ {0x1, nullptr, "StartFinalOutputRecorder"},
+ {0x2, nullptr, "StopFinalOutputRecorder"},
+ {0x3, nullptr, "AppendFinalOutputRecorderBuffer"},
+ {0x4, nullptr, "RegisterBufferEvent"},
+ {0x5, nullptr, "GetReleasedFinalOutputRecorderBuffer"},
+ {0x6, nullptr, "ContainsFinalOutputRecorderBuffer"},
+ };
+ RegisterHandlers(functions);
+ }
+ ~IFinalOutputRecorder() = default;
+};
+
+AudRecU::AudRecU() : ServiceFramework("audrec:u") {
+ static const FunctionInfo functions[] = {
+ {0x00000000, nullptr, "OpenFinalOutputRecorder"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Audio
+} // namespace Service
diff --git a/src/core/hle/service/audio/audrec_u.h b/src/core/hle/service/audio/audrec_u.h
new file mode 100644
index 000000000..c31e412c1
--- /dev/null
+++ b/src/core/hle/service/audio/audrec_u.h
@@ -0,0 +1,23 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Kernel {
+class HLERequestContext;
+}
+
+namespace Service {
+namespace Audio {
+
+class AudRecU final : public ServiceFramework<AudRecU> {
+public:
+ explicit AudRecU();
+ ~AudRecU() = default;
+};
+
+} // namespace Audio
+} // namespace Service
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
new file mode 100644
index 000000000..6d0461bbc
--- /dev/null
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -0,0 +1,260 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "core/core_timing.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/event.h"
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/service/audio/audren_u.h"
+
+namespace Service {
+namespace Audio {
+
+/// TODO(bunnei): Find a proper value for the audio_ticks
+constexpr u64 audio_ticks{static_cast<u64>(BASE_CLOCK_RATE / 200)};
+
+class IAudioRenderer final : public ServiceFramework<IAudioRenderer> {
+public:
+ IAudioRenderer() : ServiceFramework("IAudioRenderer") {
+ static const FunctionInfo functions[] = {
+ {0x0, nullptr, "GetAudioRendererSampleRate"},
+ {0x1, nullptr, "GetAudioRendererSampleCount"},
+ {0x2, nullptr, "GetAudioRendererMixBufferCount"},
+ {0x3, nullptr, "GetAudioRendererState"},
+ {0x4, &IAudioRenderer::RequestUpdateAudioRenderer, "RequestUpdateAudioRenderer"},
+ {0x5, &IAudioRenderer::StartAudioRenderer, "StartAudioRenderer"},
+ {0x6, &IAudioRenderer::StopAudioRenderer, "StopAudioRenderer"},
+ {0x7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"},
+ {0x8, nullptr, "SetAudioRendererRenderingTimeLimit"},
+ {0x9, nullptr, "GetAudioRendererRenderingTimeLimit"},
+ };
+ RegisterHandlers(functions);
+
+ system_event =
+ Kernel::Event::Create(Kernel::ResetType::OneShot, "IAudioRenderer:SystemEvent");
+
+ // Register event callback to update the Audio Buffer
+ audio_event = CoreTiming::RegisterEvent(
+ "IAudioRenderer::UpdateAudioCallback", [this](u64 userdata, int cycles_late) {
+ UpdateAudioCallback();
+ CoreTiming::ScheduleEvent(audio_ticks - cycles_late, audio_event);
+ });
+
+ // Start the audio event
+ CoreTiming::ScheduleEvent(audio_ticks, audio_event);
+ }
+ ~IAudioRenderer() {
+ CoreTiming::UnscheduleEvent(audio_event, 0);
+ }
+
+private:
+ void UpdateAudioCallback() {
+ system_event->Signal();
+ }
+
+ void RequestUpdateAudioRenderer(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Audio, "%s", ctx.Description().c_str());
+ AudioRendererResponseData response_data{};
+
+ response_data.section_0_size =
+ static_cast<u32>(response_data.state_entries.size() * sizeof(AudioRendererStateEntry));
+ response_data.section_1_size = static_cast<u32>(response_data.section_1.size());
+ response_data.section_2_size = static_cast<u32>(response_data.section_2.size());
+ response_data.section_3_size = static_cast<u32>(response_data.section_3.size());
+ response_data.section_4_size = static_cast<u32>(response_data.section_4.size());
+ response_data.section_5_size = static_cast<u32>(response_data.section_5.size());
+ response_data.total_size = sizeof(AudioRendererResponseData);
+
+ for (unsigned i = 0; i < response_data.state_entries.size(); i++) {
+ // 4 = Busy and 5 = Ready?
+ response_data.state_entries[i].state = 5;
+ }
+
+ ctx.WriteBuffer(&response_data, response_data.total_size);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+
+ rb.Push(RESULT_SUCCESS);
+
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
+ }
+
+ void StartAudioRenderer(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+
+ rb.Push(RESULT_SUCCESS);
+
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
+ }
+
+ void StopAudioRenderer(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+
+ rb.Push(RESULT_SUCCESS);
+
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
+ }
+
+ void QuerySystemEvent(Kernel::HLERequestContext& ctx) {
+ // system_event->Signal();
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(system_event);
+
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
+ }
+
+ struct AudioRendererStateEntry {
+ u32_le state;
+ u32_le unknown_4;
+ u32_le unknown_8;
+ u32_le unknown_c;
+ };
+ static_assert(sizeof(AudioRendererStateEntry) == 0x10,
+ "AudioRendererStateEntry has wrong size");
+
+ struct AudioRendererResponseData {
+ u32_le unknown_0;
+ u32_le section_5_size;
+ u32_le section_0_size;
+ u32_le section_1_size;
+ u32_le unknown_10;
+ u32_le section_2_size;
+ u32_le unknown_18;
+ u32_le section_3_size;
+ u32_le section_4_size;
+ u32_le unknown_24;
+ u32_le unknown_28;
+ u32_le unknown_2c;
+ u32_le unknown_30;
+ u32_le unknown_34;
+ u32_le unknown_38;
+ u32_le total_size;
+
+ std::array<AudioRendererStateEntry, 0x18e> state_entries;
+
+ std::array<u8, 0x600> section_1;
+ std::array<u8, 0xe0> section_2;
+ std::array<u8, 0x20> section_3;
+ std::array<u8, 0x10> section_4;
+ std::array<u8, 0xb0> section_5;
+ };
+ static_assert(sizeof(AudioRendererResponseData) == 0x20e0,
+ "AudioRendererResponseData has wrong size");
+
+ /// This is used to trigger the audio event callback.
+ CoreTiming::EventType* audio_event;
+
+ Kernel::SharedPtr<Kernel::Event> system_event;
+};
+
+class IAudioDevice final : public ServiceFramework<IAudioDevice> {
+public:
+ IAudioDevice() : ServiceFramework("IAudioDevice") {
+ static const FunctionInfo functions[] = {
+ {0x0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"},
+ {0x1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"},
+ {0x2, nullptr, "GetAudioDeviceOutputVolume"},
+ {0x3, nullptr, "GetActiveAudioDeviceName"},
+ {0x4, &IAudioDevice::QueryAudioDeviceSystemEvent, "QueryAudioDeviceSystemEvent"},
+ {0x5, &IAudioDevice::GetActiveChannelCount, "GetActiveChannelCount"},
+ {0x6, nullptr, "ListAudioDeviceNameAuto"},
+ {0x7, nullptr, "SetAudioDeviceOutputVolumeAuto"},
+ {0x8, nullptr, "GetAudioDeviceOutputVolumeAuto"},
+ {0x10, nullptr, "GetActiveAudioDeviceNameAuto"},
+ {0x11, nullptr, "QueryAudioDeviceInputEvent"},
+ {0x12, nullptr, "QueryAudioDeviceOutputEvent"}};
+ RegisterHandlers(functions);
+
+ buffer_event =
+ Kernel::Event::Create(Kernel::ResetType::OneShot, "IAudioOutBufferReleasedEvent");
+ }
+
+private:
+ void ListAudioDeviceName(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+
+ const std::string audio_interface = "AudioInterface";
+ ctx.WriteBuffer(audio_interface.c_str(), audio_interface.size());
+
+ IPC::ResponseBuilder rb = rp.MakeBuilder(3, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(1);
+ }
+
+ void SetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
+
+ IPC::RequestParser rp{ctx};
+ f32 volume = static_cast<f32>(rp.Pop<u32>());
+
+ auto file_buffer = ctx.ReadBuffer();
+ auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0');
+
+ IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void QueryAudioDeviceSystemEvent(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
+
+ buffer_event->Signal();
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(buffer_event);
+ }
+
+ void GetActiveChannelCount(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(1);
+ }
+
+ Kernel::SharedPtr<Kernel::Event> buffer_event;
+
+}; // namespace Audio
+
+AudRenU::AudRenU() : ServiceFramework("audren:u") {
+ static const FunctionInfo functions[] = {
+ {0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"},
+ {1, &AudRenU::GetAudioRendererWorkBufferSize, "GetAudioRendererWorkBufferSize"},
+ {2, &AudRenU::GetAudioDevice, "GetAudioDevice"},
+ };
+ RegisterHandlers(functions);
+}
+
+void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<Audio::IAudioRenderer>();
+
+ LOG_DEBUG(Service_Audio, "called");
+}
+
+void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 4};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(0x400);
+
+ LOG_WARNING(Service_Audio, "(STUBBED) called");
+}
+
+void AudRenU::GetAudioDevice(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<Audio::IAudioDevice>();
+
+ LOG_DEBUG(Service_Audio, "called");
+}
+
+} // namespace Audio
+} // namespace Service
diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h
new file mode 100644
index 000000000..f59d1627d
--- /dev/null
+++ b/src/core/hle/service/audio/audren_u.h
@@ -0,0 +1,28 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Kernel {
+class HLERequestContext;
+}
+
+namespace Service {
+namespace Audio {
+
+class AudRenU final : public ServiceFramework<AudRenU> {
+public:
+ explicit AudRenU();
+ ~AudRenU() = default;
+
+private:
+ void OpenAudioRenderer(Kernel::HLERequestContext& ctx);
+ void GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx);
+ void GetAudioDevice(Kernel::HLERequestContext& ctx);
+};
+
+} // namespace Audio
+} // namespace Service
diff --git a/src/core/hle/service/audio/codecctl.cpp b/src/core/hle/service/audio/codecctl.cpp
new file mode 100644
index 000000000..d2a7f4cd0
--- /dev/null
+++ b/src/core/hle/service/audio/codecctl.cpp
@@ -0,0 +1,33 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/service/audio/codecctl.h"
+
+namespace Service {
+namespace Audio {
+
+CodecCtl::CodecCtl() : ServiceFramework("codecctl") {
+ static const FunctionInfo functions[] = {
+ {0x00000000, nullptr, "InitializeCodecController"},
+ {0x00000001, nullptr, "FinalizeCodecController"},
+ {0x00000002, nullptr, "SleepCodecController"},
+ {0x00000003, nullptr, "WakeCodecController"},
+ {0x00000004, nullptr, "SetCodecVolume"},
+ {0x00000005, nullptr, "GetCodecVolumeMax"},
+ {0x00000006, nullptr, "GetCodecVolumeMin"},
+ {0x00000007, nullptr, "SetCodecActiveTarget"},
+ {0x00000008, nullptr, "Unknown"},
+ {0x00000009, nullptr, "BindCodecHeadphoneMicJackInterrupt"},
+ {0x0000000A, nullptr, "IsCodecHeadphoneMicJackInserted"},
+ {0x0000000B, nullptr, "ClearCodecHeadphoneMicJackInterrupt"},
+ {0x0000000C, nullptr, "IsCodecDeviceRequested"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Audio
+} // namespace Service
diff --git a/src/core/hle/service/audio/codecctl.h b/src/core/hle/service/audio/codecctl.h
new file mode 100644
index 000000000..1121ab0b1
--- /dev/null
+++ b/src/core/hle/service/audio/codecctl.h
@@ -0,0 +1,23 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Kernel {
+class HLERequestContext;
+}
+
+namespace Service {
+namespace Audio {
+
+class CodecCtl final : public ServiceFramework<CodecCtl> {
+public:
+ explicit CodecCtl();
+ ~CodecCtl() = default;
+};
+
+} // namespace Audio
+} // namespace Service
diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp
new file mode 100644
index 000000000..1a18e0051
--- /dev/null
+++ b/src/core/hle/service/fatal/fatal.cpp
@@ -0,0 +1,38 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/service/fatal/fatal.h"
+#include "core/hle/service/fatal/fatal_p.h"
+#include "core/hle/service/fatal/fatal_u.h"
+
+namespace Service {
+namespace Fatal {
+
+Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
+ : ServiceFramework(name), module(std::move(module)) {}
+
+void Module::Interface::FatalSimple(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp(ctx);
+ u32 error_code = rp.Pop<u32>();
+ LOG_WARNING(Service_Fatal, "(STUBBED) called, error_code=0x%X", error_code);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Module::Interface::TransitionToFatalError(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_Fatal, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ auto module = std::make_shared<Module>();
+ std::make_shared<Fatal_P>(module)->InstallAsService(service_manager);
+ std::make_shared<Fatal_U>(module)->InstallAsService(service_manager);
+}
+
+} // namespace Fatal
+} // namespace Service
diff --git a/src/core/hle/service/fatal/fatal.h b/src/core/hle/service/fatal/fatal.h
new file mode 100644
index 000000000..85272b4be
--- /dev/null
+++ b/src/core/hle/service/fatal/fatal.h
@@ -0,0 +1,29 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace Fatal {
+
+class Module final {
+public:
+ class Interface : public ServiceFramework<Interface> {
+ public:
+ Interface(std::shared_ptr<Module> module, const char* name);
+
+ void FatalSimple(Kernel::HLERequestContext& ctx);
+ void TransitionToFatalError(Kernel::HLERequestContext& ctx);
+
+ protected:
+ std::shared_ptr<Module> module;
+ };
+};
+
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace Fatal
+} // namespace Service
diff --git a/src/core/hle/service/fatal/fatal_p.cpp b/src/core/hle/service/fatal/fatal_p.cpp
new file mode 100644
index 000000000..ba194e340
--- /dev/null
+++ b/src/core/hle/service/fatal/fatal_p.cpp
@@ -0,0 +1,14 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/fatal/fatal_p.h"
+
+namespace Service {
+namespace Fatal {
+
+Fatal_P::Fatal_P(std::shared_ptr<Module> module)
+ : Module::Interface(std::move(module), "fatal:p") {}
+
+} // namespace Fatal
+} // namespace Service
diff --git a/src/core/hle/service/fatal/fatal_p.h b/src/core/hle/service/fatal/fatal_p.h
new file mode 100644
index 000000000..d77b24bc4
--- /dev/null
+++ b/src/core/hle/service/fatal/fatal_p.h
@@ -0,0 +1,18 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/fatal/fatal.h"
+
+namespace Service {
+namespace Fatal {
+
+class Fatal_P final : public Module::Interface {
+public:
+ explicit Fatal_P(std::shared_ptr<Module> module);
+};
+
+} // namespace Fatal
+} // namespace Service
diff --git a/src/core/hle/service/fatal/fatal_u.cpp b/src/core/hle/service/fatal/fatal_u.cpp
new file mode 100644
index 000000000..065cc868d
--- /dev/null
+++ b/src/core/hle/service/fatal/fatal_u.cpp
@@ -0,0 +1,19 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/fatal/fatal_u.h"
+
+namespace Service {
+namespace Fatal {
+
+Fatal_U::Fatal_U(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "fatal:u") {
+ static const FunctionInfo functions[] = {
+ {1, &Fatal_U::FatalSimple, "FatalSimple"},
+ {2, &Fatal_U::TransitionToFatalError, "TransitionToFatalError"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Fatal
+} // namespace Service
diff --git a/src/core/hle/service/fatal/fatal_u.h b/src/core/hle/service/fatal/fatal_u.h
new file mode 100644
index 000000000..22374755e
--- /dev/null
+++ b/src/core/hle/service/fatal/fatal_u.h
@@ -0,0 +1,18 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/fatal/fatal.h"
+
+namespace Service {
+namespace Fatal {
+
+class Fatal_U final : public Module::Interface {
+public:
+ explicit Fatal_U(std::shared_ptr<Module> module);
+};
+
+} // namespace Fatal
+} // namespace Service
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
new file mode 100644
index 000000000..945832e98
--- /dev/null
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -0,0 +1,79 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <boost/container/flat_map.hpp>
+#include "common/file_util.h"
+#include "core/file_sys/filesystem.h"
+#include "core/file_sys/savedata_factory.h"
+#include "core/file_sys/sdmc_factory.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/filesystem/fsp_srv.h"
+
+namespace Service {
+namespace FileSystem {
+
+/**
+ * Map of registered file systems, identified by type. Once an file system is registered here, it
+ * is never removed until UnregisterFileSystems is called.
+ */
+static boost::container::flat_map<Type, std::unique_ptr<FileSys::FileSystemFactory>> filesystem_map;
+
+ResultCode RegisterFileSystem(std::unique_ptr<FileSys::FileSystemFactory>&& factory, Type type) {
+ auto result = filesystem_map.emplace(type, std::move(factory));
+
+ bool inserted = result.second;
+ ASSERT_MSG(inserted, "Tried to register more than one system with same id code");
+
+ auto& filesystem = result.first->second;
+ LOG_DEBUG(Service_FS, "Registered file system %s with id code 0x%08X",
+ filesystem->GetName().c_str(), static_cast<u32>(type));
+ return RESULT_SUCCESS;
+}
+
+ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenFileSystem(Type type,
+ FileSys::Path& path) {
+ LOG_TRACE(Service_FS, "Opening FileSystem with type=%d", type);
+
+ auto itr = filesystem_map.find(type);
+ if (itr == filesystem_map.end()) {
+ // TODO(bunnei): Find a better error code for this
+ return ResultCode(-1);
+ }
+
+ return itr->second->Open(path);
+}
+
+ResultCode FormatFileSystem(Type type) {
+ LOG_TRACE(Service_FS, "Formatting FileSystem with type=%d", type);
+
+ auto itr = filesystem_map.find(type);
+ if (itr == filesystem_map.end()) {
+ // TODO(bunnei): Find a better error code for this
+ return ResultCode(-1);
+ }
+
+ FileSys::Path unused;
+ return itr->second->Format(unused);
+}
+
+void RegisterFileSystems() {
+ filesystem_map.clear();
+
+ std::string nand_directory = FileUtil::GetUserPath(D_NAND_IDX);
+ std::string sd_directory = FileUtil::GetUserPath(D_SDMC_IDX);
+
+ auto savedata = std::make_unique<FileSys::SaveData_Factory>(std::move(nand_directory));
+ RegisterFileSystem(std::move(savedata), Type::SaveData);
+
+ auto sdcard = std::make_unique<FileSys::SDMC_Factory>(std::move(sd_directory));
+ RegisterFileSystem(std::move(sdcard), Type::SDMC);
+}
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ RegisterFileSystems();
+ std::make_shared<FSP_SRV>()->InstallAsService(service_manager);
+}
+
+} // namespace FileSystem
+} // namespace Service
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
new file mode 100644
index 000000000..56d26146e
--- /dev/null
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -0,0 +1,59 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "common/common_types.h"
+#include "core/hle/result.h"
+
+namespace FileSys {
+class FileSystemBackend;
+class FileSystemFactory;
+class Path;
+} // namespace FileSys
+
+namespace Service {
+
+namespace SM {
+class ServiceManager;
+} // namespace SM
+
+namespace FileSystem {
+
+/// Supported FileSystem types
+enum class Type {
+ RomFS = 1,
+ SaveData = 2,
+ SDMC = 3,
+};
+
+/**
+ * Registers a FileSystem, instances of which can later be opened using its IdCode.
+ * @param factory FileSystem backend interface to use
+ * @param type Type used to access this type of FileSystem
+ */
+ResultCode RegisterFileSystem(std::unique_ptr<FileSys::FileSystemFactory>&& factory, Type type);
+
+/**
+ * Opens a file system
+ * @param type Type of the file system to open
+ * @param path Path to the file system, used with Binary paths
+ * @return FileSys::FileSystemBackend interface to the file system
+ */
+ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenFileSystem(Type type,
+ FileSys::Path& path);
+
+/**
+ * Formats a file system
+ * @param type Type of the file system to format
+ * @return ResultCode of the operation
+ */
+ResultCode FormatFileSystem(Type type);
+
+/// Registers all Filesystem services with the specified service manager.
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace FileSystem
+} // namespace Service
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
new file mode 100644
index 000000000..89fa70ae6
--- /dev/null
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -0,0 +1,464 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cinttypes>
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/file_sys/directory.h"
+#include "core/file_sys/filesystem.h"
+#include "core/file_sys/storage.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/client_port.h"
+#include "core/hle/kernel/client_session.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/filesystem/fsp_srv.h"
+
+namespace Service {
+namespace FileSystem {
+
+class IStorage final : public ServiceFramework<IStorage> {
+public:
+ IStorage(std::unique_ptr<FileSys::StorageBackend>&& backend)
+ : ServiceFramework("IStorage"), backend(std::move(backend)) {
+ static const FunctionInfo functions[] = {
+ {0, &IStorage::Read, "Read"}, {1, nullptr, "Write"}, {2, nullptr, "Flush"},
+ {3, nullptr, "SetSize"}, {4, nullptr, "GetSize"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ std::unique_ptr<FileSys::StorageBackend> backend;
+
+ void Read(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const s64 offset = rp.Pop<s64>();
+ const s64 length = rp.Pop<s64>();
+
+ LOG_DEBUG(Service_FS, "called, offset=0x%ld, length=0x%ld", offset, length);
+
+ // Error checking
+ if (length < 0) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidLength));
+ return;
+ }
+ if (offset < 0) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidOffset));
+ return;
+ }
+
+ // Read the data from the Storage backend
+ std::vector<u8> output(length);
+ ResultVal<size_t> res = backend->Read(offset, length, output.data());
+ if (res.Failed()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(res.Code());
+ return;
+ }
+
+ // Write the data to memory
+ ctx.WriteBuffer(output);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+};
+
+class IFile final : public ServiceFramework<IFile> {
+public:
+ explicit IFile(std::unique_ptr<FileSys::StorageBackend>&& backend)
+ : ServiceFramework("IFile"), backend(std::move(backend)) {
+ static const FunctionInfo functions[] = {
+ {0, &IFile::Read, "Read"}, {1, &IFile::Write, "Write"}, {2, nullptr, "Flush"},
+ {3, &IFile::SetSize, "SetSize"}, {4, &IFile::GetSize, "GetSize"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ std::unique_ptr<FileSys::StorageBackend> backend;
+
+ void Read(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 unk = rp.Pop<u64>();
+ const s64 offset = rp.Pop<s64>();
+ const s64 length = rp.Pop<s64>();
+
+ LOG_DEBUG(Service_FS, "called, offset=0x%ld, length=0x%ld", offset, length);
+
+ // Error checking
+ if (length < 0) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidLength));
+ return;
+ }
+ if (offset < 0) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidOffset));
+ return;
+ }
+
+ // Read the data from the Storage backend
+ std::vector<u8> output(length);
+ ResultVal<size_t> res = backend->Read(offset, length, output.data());
+ if (res.Failed()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(res.Code());
+ return;
+ }
+
+ // Write the data to memory
+ ctx.WriteBuffer(output);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(static_cast<u64>(*res));
+ }
+
+ void Write(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 unk = rp.Pop<u64>();
+ const s64 offset = rp.Pop<s64>();
+ const s64 length = rp.Pop<s64>();
+
+ LOG_DEBUG(Service_FS, "called, offset=0x%ld, length=0x%ld", offset, length);
+
+ // Error checking
+ if (length < 0) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidLength));
+ return;
+ }
+ if (offset < 0) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultCode(ErrorModule::FS, ErrorDescription::InvalidOffset));
+ return;
+ }
+
+ // Write the data to the Storage backend
+ std::vector<u8> data = ctx.ReadBuffer();
+ ResultVal<size_t> res = backend->Write(offset, length, true, data.data());
+ if (res.Failed()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(res.Code());
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void SetSize(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 size = rp.Pop<u64>();
+ backend->SetSize(size);
+ LOG_DEBUG(Service_FS, "called, size=%" PRIu64, size);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetSize(Kernel::HLERequestContext& ctx) {
+ const u64 size = backend->GetSize();
+ LOG_DEBUG(Service_FS, "called, size=%" PRIu64, size);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(size);
+ }
+};
+
+class IDirectory final : public ServiceFramework<IDirectory> {
+public:
+ explicit IDirectory(std::unique_ptr<FileSys::DirectoryBackend>&& backend)
+ : ServiceFramework("IDirectory"), backend(std::move(backend)) {
+ static const FunctionInfo functions[] = {
+ {0, &IDirectory::Read, "Read"},
+ {1, &IDirectory::GetEntryCount, "GetEntryCount"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ std::unique_ptr<FileSys::DirectoryBackend> backend;
+
+ void Read(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 unk = rp.Pop<u64>();
+
+ LOG_DEBUG(Service_FS, "called, unk=0x%llx", unk);
+
+ // Calculate how many entries we can fit in the output buffer
+ u64 count_entries = ctx.GetWriteBufferSize() / sizeof(FileSys::Entry);
+
+ // Read the data from the Directory backend
+ std::vector<FileSys::Entry> entries(count_entries);
+ u64 read_entries = backend->Read(count_entries, entries.data());
+
+ // Convert the data into a byte array
+ std::vector<u8> output(entries.size() * sizeof(FileSys::Entry));
+ std::memcpy(output.data(), entries.data(), output.size());
+
+ // Write the data to memory
+ ctx.WriteBuffer(output);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(read_entries);
+ }
+
+ void GetEntryCount(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_FS, "called");
+
+ u64 count = backend->GetEntryCount();
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(count);
+ }
+};
+
+class IFileSystem final : public ServiceFramework<IFileSystem> {
+public:
+ explicit IFileSystem(std::unique_ptr<FileSys::FileSystemBackend>&& backend)
+ : ServiceFramework("IFileSystem"), backend(std::move(backend)) {
+ static const FunctionInfo functions[] = {
+ {0, &IFileSystem::CreateFile, "CreateFile"},
+ {2, &IFileSystem::CreateDirectory, "CreateDirectory"},
+ {7, &IFileSystem::GetEntryType, "GetEntryType"},
+ {8, &IFileSystem::OpenFile, "OpenFile"},
+ {9, &IFileSystem::OpenDirectory, "OpenDirectory"},
+ {10, &IFileSystem::Commit, "Commit"},
+ };
+ RegisterHandlers(functions);
+ }
+
+ void CreateFile(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto file_buffer = ctx.ReadBuffer();
+ auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0');
+
+ std::string name(file_buffer.begin(), end);
+
+ u64 mode = rp.Pop<u64>();
+ u32 size = rp.Pop<u32>();
+
+ LOG_DEBUG(Service_FS, "called file %s mode 0x%" PRIX64 " size 0x%08X", name.c_str(), mode,
+ size);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(backend->CreateFile(name, size));
+ }
+
+ void CreateDirectory(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto file_buffer = ctx.ReadBuffer();
+ auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0');
+
+ std::string name(file_buffer.begin(), end);
+
+ LOG_DEBUG(Service_FS, "called directory %s", name.c_str());
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(backend->CreateDirectory(name));
+ }
+
+ void OpenFile(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto file_buffer = ctx.ReadBuffer();
+ auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0');
+
+ std::string name(file_buffer.begin(), end);
+
+ auto mode = static_cast<FileSys::Mode>(rp.Pop<u32>());
+
+ LOG_DEBUG(Service_FS, "called file %s mode %u", name.c_str(), static_cast<u32>(mode));
+
+ auto result = backend->OpenFile(name, mode);
+ if (result.Failed()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result.Code());
+ return;
+ }
+
+ auto file = std::move(result.Unwrap());
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IFile>(std::move(file));
+ }
+
+ void OpenDirectory(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto file_buffer = ctx.ReadBuffer();
+ auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0');
+
+ std::string name(file_buffer.begin(), end);
+
+ // TODO(Subv): Implement this filter.
+ u32 filter_flags = rp.Pop<u32>();
+
+ LOG_DEBUG(Service_FS, "called directory %s filter %u", name.c_str(), filter_flags);
+
+ auto result = backend->OpenDirectory(name);
+ if (result.Failed()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result.Code());
+ return;
+ }
+
+ auto directory = std::move(result.Unwrap());
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IDirectory>(std::move(directory));
+ }
+
+ void GetEntryType(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto file_buffer = ctx.ReadBuffer();
+ auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0');
+
+ std::string name(file_buffer.begin(), end);
+
+ LOG_DEBUG(Service_FS, "called file %s", name.c_str());
+
+ auto result = backend->GetEntryType(name);
+ if (result.Failed()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result.Code());
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(static_cast<u32>(*result));
+ }
+
+ void Commit(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_FS, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+private:
+ std::unique_ptr<FileSys::FileSystemBackend> backend;
+};
+
+FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
+ static const FunctionInfo functions[] = {
+ {1, &FSP_SRV::Initalize, "Initalize"},
+ {18, &FSP_SRV::MountSdCard, "MountSdCard"},
+ {22, &FSP_SRV::CreateSaveData, "CreateSaveData"},
+ {51, &FSP_SRV::MountSaveData, "MountSaveData"},
+ {200, &FSP_SRV::OpenDataStorageByCurrentProcess, "OpenDataStorageByCurrentProcess"},
+ {202, nullptr, "OpenDataStorageByDataId"},
+ {203, &FSP_SRV::OpenRomStorage, "OpenRomStorage"},
+ {1005, &FSP_SRV::GetGlobalAccessLogMode, "GetGlobalAccessLogMode"},
+ };
+ RegisterHandlers(functions);
+}
+
+void FSP_SRV::TryLoadRomFS() {
+ if (romfs) {
+ return;
+ }
+ FileSys::Path unused;
+ auto res = OpenFileSystem(Type::RomFS, unused);
+ if (res.Succeeded()) {
+ romfs = std::move(res.Unwrap());
+ }
+}
+
+void FSP_SRV::Initalize(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_FS, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void FSP_SRV::MountSdCard(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_FS, "called");
+
+ FileSys::Path unused;
+ auto filesystem = OpenFileSystem(Type::SDMC, unused).Unwrap();
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IFileSystem>(std::move(filesystem));
+}
+
+void FSP_SRV::CreateSaveData(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto save_struct = rp.PopRaw<std::array<u8, 0x40>>();
+ auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>();
+ u128 uid = rp.PopRaw<u128>();
+
+ LOG_WARNING(Service_FS, "(STUBBED) called uid = %016" PRIX64 "%016" PRIX64, uid[1], uid[0]);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void FSP_SRV::MountSaveData(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_FS, "(STUBBED) called");
+
+ FileSys::Path unused;
+ auto filesystem = OpenFileSystem(Type::SaveData, unused).Unwrap();
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IFileSystem>(std::move(filesystem));
+}
+
+void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_FS, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(5);
+}
+
+void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_FS, "called");
+
+ TryLoadRomFS();
+ if (!romfs) {
+ // TODO (bunnei): Find the right error code to use here
+ LOG_CRITICAL(Service_FS, "no file system interface available!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultCode(-1));
+ return;
+ }
+
+ // Attempt to open a StorageBackend interface to the RomFS
+ auto storage = romfs->OpenFile({}, {});
+ if (storage.Failed()) {
+ LOG_CRITICAL(Service_FS, "no storage interface available!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(storage.Code());
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IStorage>(std::move(storage.Unwrap()));
+}
+
+void FSP_SRV::OpenRomStorage(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_FS, "(STUBBED) called, using OpenDataStorageByCurrentProcess");
+ OpenDataStorageByCurrentProcess(ctx);
+}
+
+} // namespace FileSystem
+} // namespace Service
diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h
new file mode 100644
index 000000000..e15ba4375
--- /dev/null
+++ b/src/core/hle/service/filesystem/fsp_srv.h
@@ -0,0 +1,37 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "core/hle/service/service.h"
+
+namespace FileSys {
+class FileSystemBackend;
+}
+
+namespace Service {
+namespace FileSystem {
+
+class FSP_SRV final : public ServiceFramework<FSP_SRV> {
+public:
+ explicit FSP_SRV();
+ ~FSP_SRV() = default;
+
+private:
+ void TryLoadRomFS();
+
+ void Initalize(Kernel::HLERequestContext& ctx);
+ void MountSdCard(Kernel::HLERequestContext& ctx);
+ void CreateSaveData(Kernel::HLERequestContext& ctx);
+ void MountSaveData(Kernel::HLERequestContext& ctx);
+ void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
+ void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
+ void OpenRomStorage(Kernel::HLERequestContext& ctx);
+
+ std::unique_ptr<FileSys::FileSystemBackend> romfs;
+};
+
+} // namespace FileSystem
+} // namespace Service
diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp
new file mode 100644
index 000000000..26593bb0c
--- /dev/null
+++ b/src/core/hle/service/friend/friend.cpp
@@ -0,0 +1,28 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/service/friend/friend.h"
+#include "core/hle/service/friend/friend_a.h"
+
+namespace Service {
+namespace Friend {
+
+void Module::Interface::Unknown(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_Friend, "(STUBBED) called");
+}
+
+Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
+ : ServiceFramework(name), module(std::move(module)) {}
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ auto module = std::make_shared<Module>();
+ std::make_shared<Friend_A>(module)->InstallAsService(service_manager);
+}
+
+} // namespace Friend
+} // namespace Service
diff --git a/src/core/hle/service/friend/friend.h b/src/core/hle/service/friend/friend.h
new file mode 100644
index 000000000..ffa498397
--- /dev/null
+++ b/src/core/hle/service/friend/friend.h
@@ -0,0 +1,29 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace Friend {
+
+class Module final {
+public:
+ class Interface : public ServiceFramework<Interface> {
+ public:
+ Interface(std::shared_ptr<Module> module, const char* name);
+
+ void Unknown(Kernel::HLERequestContext& ctx);
+
+ protected:
+ std::shared_ptr<Module> module;
+ };
+};
+
+/// Registers all Friend services with the specified service manager.
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace Friend
+} // namespace Service
diff --git a/src/core/hle/service/friend/friend_a.cpp b/src/core/hle/service/friend/friend_a.cpp
new file mode 100644
index 000000000..e1f2397c2
--- /dev/null
+++ b/src/core/hle/service/friend/friend_a.cpp
@@ -0,0 +1,19 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/friend/friend_a.h"
+
+namespace Service {
+namespace Friend {
+
+Friend_A::Friend_A(std::shared_ptr<Module> module)
+ : Module::Interface(std::move(module), "friend:a") {
+ static const FunctionInfo functions[] = {
+ {0, &Friend_A::Unknown, "Unknown"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Friend
+} // namespace Service
diff --git a/src/core/hle/service/friend/friend_a.h b/src/core/hle/service/friend/friend_a.h
new file mode 100644
index 000000000..68fa58297
--- /dev/null
+++ b/src/core/hle/service/friend/friend_a.h
@@ -0,0 +1,18 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/friend/friend.h"
+
+namespace Service {
+namespace Friend {
+
+class Friend_A final : public Module::Interface {
+public:
+ explicit Friend_A(std::shared_ptr<Module> module);
+};
+
+} // namespace Friend
+} // namespace Service
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 6254237fa..019a09444 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -9,6 +9,7 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
+#include "core/hle/kernel/event.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/service.h"
@@ -18,9 +19,9 @@ namespace HID {
// Updating period for each HID device.
// TODO(shinyquagsire23): These need better values.
-constexpr u64 pad_update_ticks = BASE_CLOCK_RATE / 234;
-constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE / 104;
-constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE / 101;
+constexpr u64 pad_update_ticks = BASE_CLOCK_RATE / 10000;
+constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE / 10000;
+constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE / 10000;
class IAppletResource final : public ServiceFramework<IAppletResource> {
public:
@@ -44,12 +45,16 @@ public:
CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event);
}
+ ~IAppletResource() {
+ CoreTiming::UnscheduleEvent(pad_update_event, 0);
+ }
+
private:
void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 2, 1};
+ IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(shared_mem);
- LOG_DEBUG(Service, "called");
+ LOG_DEBUG(Service_HID, "called");
}
void LoadInputDevices() {
@@ -60,13 +65,14 @@ private:
}
void UpdatePadCallback(u64 userdata, int cycles_late) {
- SharedMemory* mem = reinterpret_cast<SharedMemory*>(shared_mem->GetPointer());
+ SharedMemory mem{};
+ std::memcpy(&mem, shared_mem->GetPointer(), sizeof(SharedMemory));
if (is_device_reload_pending.exchange(false))
LoadInputDevices();
// Set up controllers as neon red+blue Joy-Con attached to console
- ControllerHeader& controller_header = mem->controllers[Controller_Handheld].header;
+ ControllerHeader& controller_header = mem.controllers[Controller_Handheld].header;
controller_header.type = ControllerType_Handheld | ControllerType_JoyconPair;
controller_header.single_colors_descriptor = ColorDesc_ColorsNonexistent;
controller_header.right_color_body = JOYCON_BODY_NEON_RED;
@@ -74,8 +80,8 @@ private:
controller_header.left_color_body = JOYCON_BODY_NEON_BLUE;
controller_header.left_color_buttons = JOYCON_BUTTONS_NEON_BLUE;
- for (int layoutIdx = 0; layoutIdx < HID_NUM_LAYOUTS; layoutIdx++) {
- ControllerLayout& layout = mem->controllers[Controller_Handheld].layouts[layoutIdx];
+ for (int index = 0; index < HID_NUM_LAYOUTS; index++) {
+ ControllerLayout& layout = mem.controllers[Controller_Handheld].layouts[index];
layout.header.num_entries = HID_NUM_ENTRIES;
layout.header.max_entry_index = HID_NUM_ENTRIES - 1;
@@ -131,10 +137,25 @@ private:
// layouts)
}
- // TODO(shinyquagsire23): Update touch info
+ // TODO(bunnei): Properly implement the touch screen, the below will just write empty data
+
+ TouchScreen& touchscreen = mem.touchscreen;
+ const u64 last_entry = touchscreen.header.latest_entry;
+ const u64 curr_entry = (last_entry + 1) % touchscreen.entries.size();
+ const u64 timestamp = CoreTiming::GetTicks();
+ const u64 sample_counter = touchscreen.entries[last_entry].header.timestamp + 1;
+ touchscreen.header.timestamp_ticks = timestamp;
+ touchscreen.header.num_entries = touchscreen.entries.size();
+ touchscreen.header.latest_entry = curr_entry;
+ touchscreen.header.max_entry_index = touchscreen.entries.size();
+ touchscreen.header.timestamp = timestamp;
+ touchscreen.entries[curr_entry].header.timestamp = sample_counter;
+ touchscreen.entries[curr_entry].header.num_touches = 0;
// TODO(shinyquagsire23): Signal events
+ std::memcpy(shared_mem->GetPointer(), &mem, sizeof(SharedMemory));
+
// Reschedule recurrent event
CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event);
}
@@ -151,29 +172,203 @@ private:
buttons;
};
+class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> {
+public:
+ IActiveVibrationDeviceList() : ServiceFramework("IActiveVibrationDeviceList") {
+ static const FunctionInfo functions[] = {
+ {0, &IActiveVibrationDeviceList::ActivateVibrationDevice, "ActivateVibrationDevice"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ void ActivateVibrationDevice(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+};
+
class Hid final : public ServiceFramework<Hid> {
public:
Hid() : ServiceFramework("hid") {
static const FunctionInfo functions[] = {
- {0x00000000, &Hid::CreateAppletResource, "CreateAppletResource"},
+ {0, &Hid::CreateAppletResource, "CreateAppletResource"},
+ {1, &Hid::ActivateDebugPad, "ActivateDebugPad"},
+ {11, &Hid::ActivateTouchScreen, "ActivateTouchScreen"},
+ {21, &Hid::ActivateMouse, "ActivateMouse"},
+ {31, &Hid::ActivateKeyboard, "ActivateKeyboard"},
+ {66, &Hid::StartSixAxisSensor, "StartSixAxisSensor"},
+ {79, &Hid::SetGyroscopeZeroDriftMode, "SetGyroscopeZeroDriftMode"},
+ {100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"},
+ {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"},
+ {102, &Hid::SetSupportedNpadIdType, "SetSupportedNpadIdType"},
+ {103, &Hid::ActivateNpad, "ActivateNpad"},
+ {106, &Hid::AcquireNpadStyleSetUpdateEventHandle,
+ "AcquireNpadStyleSetUpdateEventHandle"},
+ {120, &Hid::SetNpadJoyHoldType, "SetNpadJoyHoldType"},
+ {121, &Hid::GetNpadJoyHoldType, "GetNpadJoyHoldType"},
+ {122, &Hid::SetNpadJoyAssignmentModeSingleByDefault,
+ "SetNpadJoyAssignmentModeSingleByDefault"},
+ {124, &Hid::SetNpadJoyAssignmentModeDual, "SetNpadJoyAssignmentModeDual"},
+ {128, &Hid::SetNpadHandheldActivationMode, "SetNpadHandheldActivationMode"},
+ {200, &Hid::GetVibrationDeviceInfo, "GetVibrationDeviceInfo"},
+ {201, &Hid::SendVibrationValue, "SendVibrationValue"},
+ {202, &Hid::GetActualVibrationValue, "GetActualVibrationValue"},
+ {203, &Hid::CreateActiveVibrationDeviceList, "CreateActiveVibrationDeviceList"},
+ {206, &Hid::SendVibrationValues, "SendVibrationValues"},
};
RegisterHandlers(functions);
+
+ event = Kernel::Event::Create(Kernel::ResetType::OneShot, "hid:EventHandle");
}
~Hid() = default;
private:
+ std::shared_ptr<IAppletResource> applet_resource;
+ u32 joy_hold_type{0};
+ Kernel::SharedPtr<Kernel::Event> event;
+
void CreateAppletResource(Kernel::HLERequestContext& ctx) {
- auto client_port = std::make_shared<IAppletResource>()->CreatePort();
- auto session = client_port->Connect();
- if (session.Succeeded()) {
- LOG_DEBUG(Service, "called, initialized IAppletResource -> session=%u",
- (*session)->GetObjectId());
- IPC::RequestBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushMoveObjects(std::move(session).Unwrap());
- } else {
- UNIMPLEMENTED();
+ if (applet_resource == nullptr) {
+ applet_resource = std::make_shared<IAppletResource>();
}
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IAppletResource>(applet_resource);
+ LOG_DEBUG(Service_HID, "called");
+ }
+
+ void ActivateDebugPad(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void ActivateTouchScreen(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void ActivateMouse(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void ActivateKeyboard(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void StartSixAxisSensor(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void ActivateNpad(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(event);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(joy_hold_type);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void SendVibrationValue(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void GetActualVibrationValue(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(0);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ }
+
+ void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IActiveVibrationDeviceList>();
+ LOG_DEBUG(Service_HID, "called");
+ }
+
+ void SendVibrationValues(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_HID, "(STUBBED) called");
}
};
diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp
index 2d0d2fb65..b8e53d2c7 100644
--- a/src/core/hle/service/lm/lm.cpp
+++ b/src/core/hle/service/lm/lm.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <sstream>
#include <string>
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
@@ -28,25 +29,35 @@ private:
IsHead = 1,
IsTail = 2,
};
+ enum Severity : u32_le {
+ Trace,
+ Info,
+ Warning,
+ Error,
+ Critical,
+ };
u64_le pid;
u64_le threadContext;
union {
BitField<0, 16, Flags> flags;
- BitField<16, 8, u32_le> severity;
+ BitField<16, 8, Severity> severity;
BitField<24, 8, u32_le> verbosity;
};
u32_le payload_size;
- /// Returns true if this is part of a single log message
- bool IsSingleMessage() const {
- return (flags & Flags::IsHead) && (flags & Flags::IsTail);
+ bool IsHeadLog() const {
+ return flags & Flags::IsHead;
+ }
+ bool IsTailLog() const {
+ return flags & Flags::IsTail;
}
};
static_assert(sizeof(MessageHeader) == 0x18, "MessageHeader is incorrect size");
/// Log field type
enum class Field : u8 {
+ Skip = 1,
Message = 2,
Line = 3,
Filename = 4,
@@ -56,7 +67,7 @@ private:
};
/**
- * LM::Initialize service function
+ * LM::Log service function
* Inputs:
* 0: 0x00000000
* Outputs:
@@ -64,7 +75,7 @@ private:
*/
void Log(Kernel::HLERequestContext& ctx) {
// This function only succeeds - Get that out of the way
- IPC::RequestBuilder rb{ctx, 1};
+ IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
// Read MessageHeader, despite not doing anything with it right now
@@ -74,9 +85,9 @@ private:
Memory::ReadBlock(addr, &header, sizeof(MessageHeader));
addr += sizeof(MessageHeader);
- if (!header.IsSingleMessage()) {
- LOG_WARNING(Service, "Multi message logs are unimplemeneted");
- return;
+ if (header.IsHeadLog()) {
+ log_stream.str("");
+ log_stream.clear();
}
// Parse out log metadata
@@ -84,7 +95,12 @@ private:
std::string message, filename, function;
while (addr < end_addr) {
const Field field{static_cast<Field>(Memory::Read8(addr++))};
- size_t length{Memory::Read8(addr++)};
+ const size_t length{Memory::Read8(addr++)};
+
+ if (static_cast<Field>(Memory::Read8(addr)) == Field::Skip) {
+ ++addr;
+ }
+
switch (field) {
case Field::Message:
message = Memory::ReadCString(addr, length);
@@ -99,32 +115,52 @@ private:
function = Memory::ReadCString(addr, length);
break;
}
+
addr += length;
}
// Empty log - nothing to do here
- if (message.empty()) {
+ if (log_stream.str().empty() && message.empty()) {
return;
}
// Format a nicely printable string out of the log metadata
- std::string output;
- if (filename.size()) {
- output += filename + ':';
+ if (!filename.empty()) {
+ log_stream << filename << ':';
}
- if (function.size()) {
- output += function + ':';
+ if (!function.empty()) {
+ log_stream << function << ':';
}
if (line) {
- output += std::to_string(line) + ':';
+ log_stream << std::to_string(line) << ':';
}
- if (output.back() == ':') {
- output += ' ';
+ if (log_stream.str().length() > 0 && log_stream.str().back() == ':') {
+ log_stream << ' ';
}
- output += message;
+ log_stream << message;
- LOG_DEBUG(Debug_Emulated, "%s", output.c_str());
+ if (header.IsTailLog()) {
+ switch (header.severity) {
+ case MessageHeader::Severity::Trace:
+ LOG_TRACE(Debug_Emulated, "%s", log_stream.str().c_str());
+ break;
+ case MessageHeader::Severity::Info:
+ LOG_INFO(Debug_Emulated, "%s", log_stream.str().c_str());
+ break;
+ case MessageHeader::Severity::Warning:
+ LOG_WARNING(Debug_Emulated, "%s", log_stream.str().c_str());
+ break;
+ case MessageHeader::Severity::Error:
+ LOG_ERROR(Debug_Emulated, "%s", log_stream.str().c_str());
+ break;
+ case MessageHeader::Severity::Critical:
+ LOG_CRITICAL(Debug_Emulated, "%s", log_stream.str().c_str());
+ break;
+ }
+ }
}
+
+ std::ostringstream log_stream;
};
void InstallInterfaces(SM::ServiceManager& service_manager) {
@@ -139,20 +175,11 @@ void InstallInterfaces(SM::ServiceManager& service_manager) {
* 0: ResultCode
*/
void LM::Initialize(Kernel::HLERequestContext& ctx) {
- auto client_port = std::make_shared<Logger>()->CreatePort();
- auto session = client_port->Connect();
- if (session.Succeeded()) {
- LOG_DEBUG(Service_SM, "called, initialized logger -> session=%u",
- (*session)->GetObjectId());
- IPC::RequestBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushMoveObjects(std::move(session).Unwrap());
- registered_loggers.emplace_back(std::move(client_port));
- } else {
- UNIMPLEMENTED();
- }
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<Logger>();
- LOG_INFO(Service_SM, "called");
+ LOG_DEBUG(Service_LM, "called");
}
LM::LM() : ServiceFramework("lm") {
diff --git a/src/core/hle/service/lm/lm.h b/src/core/hle/service/lm/lm.h
index 4b954bdb2..371135057 100644
--- a/src/core/hle/service/lm/lm.h
+++ b/src/core/hle/service/lm/lm.h
@@ -5,7 +5,6 @@
#pragma once
#include <vector>
-#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/service.h"
@@ -19,8 +18,6 @@ public:
private:
void Initialize(Kernel::HLERequestContext& ctx);
-
- std::vector<Kernel::SharedPtr<Kernel::ClientPort>> registered_loggers;
};
/// Registers all LM services with the specified service manager.
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
new file mode 100644
index 000000000..49870841c
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -0,0 +1,28 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/service/nfp/nfp.h"
+#include "core/hle/service/nfp/nfp_user.h"
+
+namespace Service {
+namespace NFP {
+
+Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
+ : ServiceFramework(name), module(std::move(module)) {}
+
+void Module::Interface::Unknown(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NFP, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ auto module = std::make_shared<Module>();
+ std::make_shared<NFP_User>(module)->InstallAsService(service_manager);
+}
+
+} // namespace NFP
+} // namespace Service
diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h
new file mode 100644
index 000000000..1163e9954
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp.h
@@ -0,0 +1,28 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace NFP {
+
+class Module final {
+public:
+ class Interface : public ServiceFramework<Interface> {
+ public:
+ Interface(std::shared_ptr<Module> module, const char* name);
+
+ void Unknown(Kernel::HLERequestContext& ctx);
+
+ protected:
+ std::shared_ptr<Module> module;
+ };
+};
+
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace NFP
+} // namespace Service
diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp
new file mode 100644
index 000000000..14e5647c4
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_user.cpp
@@ -0,0 +1,19 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/nfp/nfp_user.h"
+
+namespace Service {
+namespace NFP {
+
+NFP_User::NFP_User(std::shared_ptr<Module> module)
+ : Module::Interface(std::move(module), "nfp:user") {
+ static const FunctionInfo functions[] = {
+ {0, &NFP_User::Unknown, "Unknown"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace NFP
+} // namespace Service
diff --git a/src/core/hle/service/nfp/nfp_user.h b/src/core/hle/service/nfp/nfp_user.h
new file mode 100644
index 000000000..1606444ca
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_user.h
@@ -0,0 +1,18 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/nfp/nfp.h"
+
+namespace Service {
+namespace NFP {
+
+class NFP_User final : public Module::Interface {
+public:
+ explicit NFP_User(std::shared_ptr<Module> module);
+};
+
+} // namespace NFP
+} // namespace Service
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
new file mode 100644
index 000000000..dd2d5fe63
--- /dev/null
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -0,0 +1,213 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/event.h"
+#include "core/hle/service/nifm/nifm.h"
+#include "core/hle/service/nifm/nifm_a.h"
+#include "core/hle/service/nifm/nifm_s.h"
+#include "core/hle/service/nifm/nifm_u.h"
+
+namespace Service {
+namespace NIFM {
+
+class IScanRequest final : public ServiceFramework<IScanRequest> {
+public:
+ explicit IScanRequest() : ServiceFramework("IScanRequest") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "Submit"},
+ {1, nullptr, "IsProcessing"},
+ {2, nullptr, "GetResult"},
+ {3, nullptr, "GetSystemEventReadableHandle"},
+ };
+ RegisterHandlers(functions);
+ }
+};
+
+class IRequest final : public ServiceFramework<IRequest> {
+public:
+ explicit IRequest() : ServiceFramework("IRequest") {
+ static const FunctionInfo functions[] = {
+ {0, &IRequest::GetRequestState, "GetRequestState"},
+ {1, &IRequest::GetResult, "GetResult"},
+ {2, &IRequest::GetSystemEventReadableHandles, "GetSystemEventReadableHandles"},
+ {3, &IRequest::Cancel, "Cancel"},
+ {4, nullptr, "Submit"},
+ {5, nullptr, "SetRequirement"},
+ {6, nullptr, "SetRequirementPreset"},
+ {8, nullptr, "SetPriority"},
+ {9, nullptr, "SetNetworkProfileId"},
+ {10, nullptr, "SetRejectable"},
+ {11, nullptr, "SetConnectionConfirmationOption"},
+ {12, nullptr, "SetPersistent"},
+ {13, nullptr, "SetInstant"},
+ {14, nullptr, "SetSustainable"},
+ {15, nullptr, "SetRawPriority"},
+ {16, nullptr, "SetGreedy"},
+ {17, nullptr, "SetSharable"},
+ {18, nullptr, "SetRequirementByRevision"},
+ {19, nullptr, "GetRequirement"},
+ {20, nullptr, "GetRevision"},
+ {21, nullptr, "GetAppletInfo"},
+ {22, nullptr, "GetAdditionalInfo"},
+ {23, nullptr, "SetKeptInSleep"},
+ {24, nullptr, "RegisterSocketDescriptor"},
+ {25, nullptr, "UnregisterSocketDescriptor"},
+ };
+ RegisterHandlers(functions);
+
+ event1 = Kernel::Event::Create(Kernel::ResetType::OneShot, "IRequest:Event1");
+ event2 = Kernel::Event::Create(Kernel::ResetType::OneShot, "IRequest:Event2");
+ }
+
+private:
+ void GetRequestState(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0);
+ }
+ void GetResult(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0);
+ }
+ void GetSystemEventReadableHandles(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2, 2};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(event1, event2);
+ }
+ void Cancel(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ Kernel::SharedPtr<Kernel::Event> event1, event2;
+};
+
+class INetworkProfile final : public ServiceFramework<INetworkProfile> {
+public:
+ explicit INetworkProfile() : ServiceFramework("INetworkProfile") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "Update"},
+ {1, nullptr, "PersistOld"},
+ {2, nullptr, "Persist"},
+ };
+ RegisterHandlers(functions);
+ }
+};
+
+class IGeneralService final : public ServiceFramework<IGeneralService> {
+public:
+ IGeneralService();
+
+private:
+ void GetClientId(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(0);
+ }
+ void CreateScanRequest(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IScanRequest>();
+
+ LOG_DEBUG(Service_NIFM, "called");
+ }
+ void CreateRequest(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IRequest>();
+
+ LOG_DEBUG(Service_NIFM, "called");
+ }
+ void RemoveNetworkProfile(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+ void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<INetworkProfile>();
+
+ LOG_DEBUG(Service_NIFM, "called");
+ }
+};
+
+IGeneralService::IGeneralService() : ServiceFramework("IGeneralService") {
+ static const FunctionInfo functions[] = {
+ {1, &IGeneralService::GetClientId, "GetClientId"},
+ {2, &IGeneralService::CreateScanRequest, "CreateScanRequest"},
+ {4, &IGeneralService::CreateRequest, "CreateRequest"},
+ {5, nullptr, "GetCurrentNetworkProfile"},
+ {6, nullptr, "EnumerateNetworkInterfaces"},
+ {7, nullptr, "EnumerateNetworkProfiles"},
+ {8, nullptr, "GetNetworkProfile"},
+ {9, nullptr, "SetNetworkProfile"},
+ {10, &IGeneralService::RemoveNetworkProfile, "RemoveNetworkProfile"},
+ {11, nullptr, "GetScanDataOld"},
+ {12, nullptr, "GetCurrentIpAddress"},
+ {13, nullptr, "GetCurrentAccessPointOld"},
+ {14, &IGeneralService::CreateTemporaryNetworkProfile, "CreateTemporaryNetworkProfile"},
+ {15, nullptr, "GetCurrentIpConfigInfo"},
+ {16, nullptr, "SetWirelessCommunicationEnabled"},
+ {17, nullptr, "IsWirelessCommunicationEnabled"},
+ {18, nullptr, "GetInternetConnectionStatus"},
+ {19, nullptr, "SetEthernetCommunicationEnabled"},
+ {20, nullptr, "IsEthernetCommunicationEnabled"},
+ {21, nullptr, "IsAnyInternetRequestAccepted"},
+ {22, nullptr, "IsAnyForegroundRequestAccepted"},
+ {23, nullptr, "PutToSleep"},
+ {24, nullptr, "WakeUp"},
+ {25, nullptr, "GetSsidListVersion"},
+ {26, nullptr, "SetExclusiveClient"},
+ {27, nullptr, "GetDefaultIpSetting"},
+ {28, nullptr, "SetDefaultIpSetting"},
+ {29, nullptr, "SetWirelessCommunicationEnabledForTest"},
+ {30, nullptr, "SetEthernetCommunicationEnabledForTest"},
+ {31, nullptr, "GetTelemetorySystemEventReadableHandle"},
+ {32, nullptr, "GetTelemetryInfo"},
+ {33, nullptr, "ConfirmSystemAvailability"},
+ {34, nullptr, "SetBackgroundRequestEnabled"},
+ {35, nullptr, "GetScanData"},
+ {36, nullptr, "GetCurrentAccessPoint"},
+ {37, nullptr, "Shutdown"},
+ };
+ RegisterHandlers(functions);
+}
+
+void Module::Interface::CreateGeneralServiceOld(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IGeneralService>();
+ LOG_DEBUG(Service_NIFM, "called");
+}
+
+void Module::Interface::CreateGeneralService(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IGeneralService>();
+ LOG_DEBUG(Service_NIFM, "called");
+}
+
+Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
+ : ServiceFramework(name), module(std::move(module)) {}
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ auto module = std::make_shared<Module>();
+ std::make_shared<NIFM_A>(module)->InstallAsService(service_manager);
+ std::make_shared<NIFM_S>(module)->InstallAsService(service_manager);
+ std::make_shared<NIFM_U>(module)->InstallAsService(service_manager);
+}
+
+} // namespace NIFM
+} // namespace Service
diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h
new file mode 100644
index 000000000..11d263b12
--- /dev/null
+++ b/src/core/hle/service/nifm/nifm.h
@@ -0,0 +1,29 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace NIFM {
+
+class Module final {
+public:
+ class Interface : public ServiceFramework<Interface> {
+ public:
+ Interface(std::shared_ptr<Module> module, const char* name);
+
+ void CreateGeneralServiceOld(Kernel::HLERequestContext& ctx);
+ void CreateGeneralService(Kernel::HLERequestContext& ctx);
+
+ protected:
+ std::shared_ptr<Module> module;
+ };
+};
+
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace NIFM
+} // namespace Service
diff --git a/src/core/hle/service/nifm/nifm_a.cpp b/src/core/hle/service/nifm/nifm_a.cpp
new file mode 100644
index 000000000..f75df8c04
--- /dev/null
+++ b/src/core/hle/service/nifm/nifm_a.cpp
@@ -0,0 +1,19 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/nifm/nifm_a.h"
+
+namespace Service {
+namespace NIFM {
+
+NIFM_A::NIFM_A(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "nifm:a") {
+ static const FunctionInfo functions[] = {
+ {4, &NIFM_A::CreateGeneralServiceOld, "CreateGeneralServiceOld"},
+ {5, &NIFM_A::CreateGeneralService, "CreateGeneralService"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace NIFM
+} // namespace Service
diff --git a/src/core/hle/service/nifm/nifm_a.h b/src/core/hle/service/nifm/nifm_a.h
new file mode 100644
index 000000000..eaea14e29
--- /dev/null
+++ b/src/core/hle/service/nifm/nifm_a.h
@@ -0,0 +1,18 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/nifm/nifm.h"
+
+namespace Service {
+namespace NIFM {
+
+class NIFM_A final : public Module::Interface {
+public:
+ explicit NIFM_A(std::shared_ptr<Module> module);
+};
+
+} // namespace NIFM
+} // namespace Service
diff --git a/src/core/hle/service/nifm/nifm_s.cpp b/src/core/hle/service/nifm/nifm_s.cpp
new file mode 100644
index 000000000..9c0b300e4
--- /dev/null
+++ b/src/core/hle/service/nifm/nifm_s.cpp
@@ -0,0 +1,19 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/nifm/nifm_s.h"
+
+namespace Service {
+namespace NIFM {
+
+NIFM_S::NIFM_S(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "nifm:s") {
+ static const FunctionInfo functions[] = {
+ {4, &NIFM_S::CreateGeneralServiceOld, "CreateGeneralServiceOld"},
+ {5, &NIFM_S::CreateGeneralService, "CreateGeneralService"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace NIFM
+} // namespace Service
diff --git a/src/core/hle/service/nifm/nifm_s.h b/src/core/hle/service/nifm/nifm_s.h
new file mode 100644
index 000000000..f9e2d8039
--- /dev/null
+++ b/src/core/hle/service/nifm/nifm_s.h
@@ -0,0 +1,18 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/nifm/nifm.h"
+
+namespace Service {
+namespace NIFM {
+
+class NIFM_S final : public Module::Interface {
+public:
+ explicit NIFM_S(std::shared_ptr<Module> module);
+};
+
+} // namespace NIFM
+} // namespace Service
diff --git a/src/core/hle/service/nifm/nifm_u.cpp b/src/core/hle/service/nifm/nifm_u.cpp
new file mode 100644
index 000000000..44e6f483d
--- /dev/null
+++ b/src/core/hle/service/nifm/nifm_u.cpp
@@ -0,0 +1,19 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/nifm/nifm_u.h"
+
+namespace Service {
+namespace NIFM {
+
+NIFM_U::NIFM_U(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "nifm:u") {
+ static const FunctionInfo functions[] = {
+ {4, &NIFM_U::CreateGeneralServiceOld, "CreateGeneralServiceOld"},
+ {5, &NIFM_U::CreateGeneralService, "CreateGeneralService"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace NIFM
+} // namespace Service
diff --git a/src/core/hle/service/nifm/nifm_u.h b/src/core/hle/service/nifm/nifm_u.h
new file mode 100644
index 000000000..912006775
--- /dev/null
+++ b/src/core/hle/service/nifm/nifm_u.h
@@ -0,0 +1,18 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/nifm/nifm.h"
+
+namespace Service {
+namespace NIFM {
+
+class NIFM_U final : public Module::Interface {
+public:
+ explicit NIFM_U(std::shared_ptr<Module> module);
+};
+
+} // namespace NIFM
+} // namespace Service
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
new file mode 100644
index 000000000..45681c50f
--- /dev/null
+++ b/src/core/hle/service/ns/ns.cpp
@@ -0,0 +1,16 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/ns/ns.h"
+#include "core/hle/service/ns/pl_u.h"
+
+namespace Service {
+namespace NS {
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ std::make_shared<PL_U>()->InstallAsService(service_manager);
+}
+
+} // namespace NS
+} // namespace Service
diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h
new file mode 100644
index 000000000..a4b7e3ded
--- /dev/null
+++ b/src/core/hle/service/ns/ns.h
@@ -0,0 +1,16 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace NS {
+
+/// Registers all NS services with the specified service manager.
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace NS
+} // namespace Service
diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp
new file mode 100644
index 000000000..ef3c7799a
--- /dev/null
+++ b/src/core/hle/service/ns/pl_u.cpp
@@ -0,0 +1,122 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/common_paths.h"
+#include "common/file_util.h"
+#include "core/core.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/service/ns/pl_u.h"
+
+namespace Service {
+namespace NS {
+
+struct FontRegion {
+ u32 offset;
+ u32 size;
+};
+
+// The below data is specific to shared font data dumped from Switch on f/w 2.2
+// Virtual address and offsets/sizes likely will vary by dump
+static constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL};
+static constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000};
+static constexpr std::array<FontRegion, 6> SHARED_FONT_REGIONS{
+ FontRegion{0x00000008, 0x001fe764}, FontRegion{0x001fe774, 0x00773e58},
+ FontRegion{0x009725d4, 0x0001aca8}, FontRegion{0x0098d284, 0x00369cec},
+ FontRegion{0x00cf6f78, 0x0039b858}, FontRegion{0x010927d8, 0x00019e80},
+};
+
+enum class LoadState : u32 {
+ Loading = 0,
+ Done = 1,
+};
+
+PL_U::PL_U() : ServiceFramework("pl:u") {
+ static const FunctionInfo functions[] = {
+ {0, &PL_U::RequestLoad, "RequestLoad"},
+ {1, &PL_U::GetLoadState, "GetLoadState"},
+ {2, &PL_U::GetSize, "GetSize"},
+ {3, &PL_U::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"},
+ {4, &PL_U::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"}};
+ RegisterHandlers(functions);
+
+ // Attempt to load shared font data from disk
+ const std::string filepath{FileUtil::GetUserPath(D_SYSDATA_IDX) + SHARED_FONT};
+ FileUtil::CreateFullPath(filepath); // Create path if not already created
+ FileUtil::IOFile file(filepath, "rb");
+
+ if (file.IsOpen()) {
+ // Read shared font data
+ ASSERT(file.GetSize() == SHARED_FONT_MEM_SIZE);
+ shared_font = std::make_shared<std::vector<u8>>(static_cast<size_t>(file.GetSize()));
+ file.ReadBytes(shared_font->data(), shared_font->size());
+ } else {
+ LOG_WARNING(Service_NS, "Unable to load shared font: %s", filepath.c_str());
+ }
+}
+
+void PL_U::RequestLoad(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u32 shared_font_type{rp.Pop<u32>()};
+
+ LOG_DEBUG(Service_NS, "called, shared_font_type=%d", shared_font_type);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void PL_U::GetLoadState(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u32 font_id{rp.Pop<u32>()};
+
+ LOG_DEBUG(Service_NS, "called, font_id=%d", font_id);
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(static_cast<u32>(LoadState::Done));
+}
+
+void PL_U::GetSize(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u32 font_id{rp.Pop<u32>()};
+
+ LOG_DEBUG(Service_NS, "called, font_id=%d", font_id);
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(SHARED_FONT_REGIONS[font_id].size);
+}
+
+void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u32 font_id{rp.Pop<u32>()};
+
+ LOG_DEBUG(Service_NS, "called, font_id=%d", font_id);
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(SHARED_FONT_REGIONS[font_id].offset);
+}
+
+void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) {
+ if (shared_font != nullptr) {
+ // TODO(bunnei): This is a less-than-ideal solution to load a RAM dump of the Switch shared
+ // font data. This (likely) relies on exact address, size, and offsets from the original
+ // dump. In the future, we need to replace this with a more robust solution.
+
+ // Map backing memory for the font data
+ Core::CurrentProcess()->vm_manager.MapMemoryBlock(SHARED_FONT_MEM_VADDR, shared_font, 0,
+ SHARED_FONT_MEM_SIZE,
+ Kernel::MemoryState::Shared);
+
+ // Create shared font memory object
+ shared_font_mem = Kernel::SharedMemory::Create(
+ Core::CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite,
+ Kernel::MemoryPermission::Read, SHARED_FONT_MEM_VADDR, Kernel::MemoryRegion::BASE,
+ "PL_U:shared_font_mem");
+ }
+
+ LOG_DEBUG(Service_NS, "called");
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(shared_font_mem);
+}
+
+} // namespace NS
+} // namespace Service
diff --git a/src/core/hle/service/ns/pl_u.h b/src/core/hle/service/ns/pl_u.h
new file mode 100644
index 000000000..360482d13
--- /dev/null
+++ b/src/core/hle/service/ns/pl_u.h
@@ -0,0 +1,34 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "core/hle/kernel/shared_memory.h"
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace NS {
+
+class PL_U final : public ServiceFramework<PL_U> {
+public:
+ PL_U();
+ ~PL_U() = default;
+
+private:
+ void RequestLoad(Kernel::HLERequestContext& ctx);
+ void GetLoadState(Kernel::HLERequestContext& ctx);
+ void GetSize(Kernel::HLERequestContext& ctx);
+ void GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx);
+ void GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx);
+
+ /// Handle to shared memory region designated for a shared font
+ Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem;
+
+ /// Backing memory for the shared font data
+ std::shared_ptr<std::vector<u8>> shared_font;
+};
+
+} // namespace NS
+} // namespace Service
diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h
index 5ee33b3d6..cdc25b059 100644
--- a/src/core/hle/service/nvdrv/devices/nvdevice.h
+++ b/src/core/hle/service/nvdrv/devices/nvdevice.h
@@ -5,7 +5,9 @@
#pragma once
#include <vector>
+#include "common/bit_field.h"
#include "common/common_types.h"
+#include "common/swap.h"
namespace Service {
namespace Nvidia {
@@ -17,6 +19,14 @@ class nvdevice {
public:
nvdevice() = default;
virtual ~nvdevice() = default;
+ union Ioctl {
+ u32_le raw;
+ BitField<0, 8, u32_le> cmd;
+ BitField<8, 8, u32_le> group;
+ BitField<16, 14, u32_le> length;
+ BitField<30, 1, u32_le> is_in;
+ BitField<31, 1, u32_le> is_out;
+ };
/**
* Handles an ioctl request.
@@ -25,7 +35,7 @@ public:
* @param output A buffer where the output data will be written to.
* @returns The result code of the ioctl.
*/
- virtual u32 ioctl(u32 command, const std::vector<u8>& input, std::vector<u8>& output) = 0;
+ virtual u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) = 0;
};
} // namespace Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
index b65d79f11..87b3a2d74 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
@@ -14,24 +14,25 @@ namespace Service {
namespace Nvidia {
namespace Devices {
-u32 nvdisp_disp0::ioctl(u32 command, const std::vector<u8>& input, std::vector<u8>& output) {
+u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
UNIMPLEMENTED();
return 0;
}
void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height,
- u32 stride) {
+ u32 stride, NVFlinger::BufferQueue::BufferTransformFlags transform) {
VAddr addr = nvmap_dev->GetObjectAddress(buffer_handle);
LOG_WARNING(Service,
- "Drawing from address %llx offset %08X Width %u Height %u Stride %u Format %u",
- addr, offset, width, height, stride, format);
+ "Drawing from address %lx offset %08X Width %u Height %u Stride %u Format %u", addr,
+ offset, width, height, stride, format);
- using PixelFormat = RendererBase::FramebufferInfo::PixelFormat;
- const RendererBase::FramebufferInfo framebuffer_info{
- addr, offset, width, height, stride, static_cast<PixelFormat>(format)};
+ using PixelFormat = Tegra::FramebufferConfig::PixelFormat;
+ const Tegra::FramebufferConfig framebuffer{
+ addr, offset, width, height, stride, static_cast<PixelFormat>(format), transform};
Core::System::GetInstance().perf_stats.EndGameFrame();
- VideoCore::g_renderer->SwapBuffers(framebuffer_info);
+
+ VideoCore::g_renderer->SwapBuffers(framebuffer);
}
} // namespace Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
index f5f9de3f4..66f56f23d 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
@@ -8,6 +8,7 @@
#include <vector>
#include "common/common_types.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
+#include "core/hle/service/nvflinger/buffer_queue.h"
namespace Service {
namespace Nvidia {
@@ -20,10 +21,11 @@ public:
nvdisp_disp0(std::shared_ptr<nvmap> nvmap_dev) : nvdevice(), nvmap_dev(std::move(nvmap_dev)) {}
~nvdisp_disp0() = default;
- u32 ioctl(u32 command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
/// Performs a screen flip, drawing the buffer pointed to by the handle.
- void flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride);
+ void flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride,
+ NVFlinger::BufferQueue::BufferTransformFlags transform);
private:
std::shared_ptr<nvmap> nvmap_dev;
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 9db08339a..9892402fa 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
@@ -2,16 +2,116 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cinttypes>
#include "common/assert.h"
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
+#include "core/hle/service/nvdrv/devices/nvmap.h"
namespace Service {
namespace Nvidia {
namespace Devices {
-u32 nvhost_as_gpu::ioctl(u32 command, const std::vector<u8>& input, std::vector<u8>& output) {
- UNIMPLEMENTED();
+u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+ LOG_DEBUG(Service_NVDRV, "called, command=0x%08x, input_size=0x%zx, output_size=0x%zx",
+ command.raw, input.size(), output.size());
+
+ switch (static_cast<IoctlCommand>(command.raw)) {
+ case IoctlCommand::IocInitalizeExCommand:
+ return InitalizeEx(input, output);
+ case IoctlCommand::IocAllocateSpaceCommand:
+ return AllocateSpace(input, output);
+ case IoctlCommand::IocMapBufferExCommand:
+ return MapBufferEx(input, output);
+ case IoctlCommand::IocBindChannelCommand:
+ return BindChannel(input, output);
+ case IoctlCommand::IocGetVaRegionsCommand:
+ return GetVARegions(input, output);
+ }
+ return 0;
+}
+
+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);
+ std::memcpy(output.data(), &params, output.size());
+ return 0;
+}
+
+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 = Core::System::GetInstance().GPU();
+ const u64 size{static_cast<u64>(params.pages) * static_cast<u64>(params.page_size)};
+ if (params.flags & 1) {
+ params.offset = gpu.memory_manager->AllocateSpace(params.offset, size, 1);
+ } else {
+ params.offset = gpu.memory_manager->AllocateSpace(size, params.align);
+ }
+
+ std::memcpy(output.data(), &params, output.size());
+ return 0;
+}
+
+u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlMapBufferEx params{};
+ std::memcpy(&params, input.data(), input.size());
+
+ LOG_DEBUG(Service_NVDRV,
+ "called, flags=%x, nvmap_handle=%x, buffer_offset=%" PRIu64 ", mapping_size=%" PRIu64
+ ", offset=%" PRIu64,
+ params.flags, params.nvmap_handle, params.buffer_offset, params.mapping_size,
+ params.offset);
+
+ if (!params.nvmap_handle) {
+ return 0;
+ }
+
+ auto object = nvmap_dev->GetObject(params.nvmap_handle);
+ ASSERT(object);
+
+ auto& gpu = Core::System::GetInstance().GPU();
+
+ if (params.flags & 1) {
+ params.offset = gpu.memory_manager->MapBufferEx(object->addr, params.offset, object->size);
+ } else {
+ params.offset = gpu.memory_manager->MapBufferEx(object->addr, object->size);
+ }
+
+ std::memcpy(output.data(), &params, output.size());
+ return 0;
+}
+
+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;
+ std::memcpy(output.data(), &params, output.size());
+ return 0;
+}
+
+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=%" PRIu64 ", buf_size=%x",
+ params.buf_addr, params.buf_size);
+
+ params.buf_size = 0x30;
+ params.regions[0].offset = 0x04000000;
+ params.regions[0].page_size = 0x1000;
+ params.regions[0].pages = 0x3fbfff;
+
+ 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;
}
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 01f8861c8..f8a60cce7 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
@@ -4,20 +4,100 @@
#pragma once
+#include <memory>
+#include <utility>
#include <vector>
#include "common/common_types.h"
+#include "common/swap.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
namespace Service {
namespace Nvidia {
namespace Devices {
+class nvmap;
+
class nvhost_as_gpu final : public nvdevice {
public:
- nvhost_as_gpu() = default;
+ nvhost_as_gpu(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {}
~nvhost_as_gpu() override = default;
- u32 ioctl(u32 command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+
+private:
+ enum class IoctlCommand : u32_le {
+ IocInitalizeExCommand = 0x40284109,
+ IocAllocateSpaceCommand = 0xC0184102,
+ IocMapBufferExCommand = 0xC0284106,
+ IocBindChannelCommand = 0x40044101,
+ IocGetVaRegionsCommand = 0xC0404108,
+ };
+
+ struct IoctlInitalizeEx {
+ u32_le big_page_size; // depends on GPU's available_big_page_sizes; 0=default
+ s32_le as_fd; // ignored; passes 0
+ u32_le flags; // passes 0
+ u32_le reserved; // ignored; passes 0
+ u64_le unk0;
+ u64_le unk1;
+ u64_le unk2;
+ };
+ static_assert(sizeof(IoctlInitalizeEx) == 40, "IoctlInitalizeEx is incorrect size");
+
+ struct IoctlAllocSpace {
+ u32_le pages;
+ u32_le page_size;
+ u32_le flags;
+ INSERT_PADDING_WORDS(1);
+ union {
+ u64_le offset;
+ u64_le align;
+ };
+ };
+ static_assert(sizeof(IoctlAllocSpace) == 24, "IoctlInitalizeEx is incorrect size");
+
+ struct IoctlMapBufferEx {
+ u32_le 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;
+ u64_le mapping_size;
+ u64_le offset;
+ };
+ static_assert(sizeof(IoctlMapBufferEx) == 40, "IoctlMapBufferEx is incorrect size");
+
+ struct IoctlBindChannel {
+ u32_le fd;
+ };
+ static_assert(sizeof(IoctlBindChannel) == 4, "IoctlBindChannel is incorrect size");
+
+ struct IoctlVaRegion {
+ u64_le offset;
+ u32_le page_size;
+ INSERT_PADDING_WORDS(1);
+ u64_le pages;
+ };
+ static_assert(sizeof(IoctlVaRegion) == 24, "IoctlVaRegion is incorrect size");
+
+ struct IoctlGetVaRegions {
+ u64_le buf_addr; // (contained output user ptr on linux, ignored)
+ u32_le buf_size; // forced to 2*sizeof(struct va_region)
+ u32_le reserved;
+ IoctlVaRegion regions[2];
+ };
+ static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(IoctlVaRegion) * 2,
+ "IoctlGetVaRegions is incorrect size");
+
+ u32 channel{};
+
+ u32 InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 BindChannel(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 GetVARegions(const std::vector<u8>& input, std::vector<u8>& output);
+
+ std::shared_ptr<nvmap> nvmap_dev;
};
} // namespace Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
new file mode 100644
index 000000000..45711d686
--- /dev/null
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
@@ -0,0 +1,64 @@
+// Copyright 2018 yuzu emulator team
+// 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/hle/service/nvdrv/devices/nvhost_ctrl.h"
+
+namespace Service {
+namespace Nvidia {
+namespace Devices {
+
+u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+ LOG_DEBUG(Service_NVDRV, "called, command=0x%08x, input_size=0x%zx, output_size=0x%zx",
+ command.raw, input.size(), output.size());
+
+ switch (static_cast<IoctlCommand>(command.raw)) {
+ case IoctlCommand::IocGetConfigCommand:
+ return NvOsGetConfigU32(input, output);
+ case IoctlCommand::IocCtrlEventWaitCommand:
+ return IocCtrlEventWait(input, output);
+ }
+ UNIMPLEMENTED();
+ return 0;
+}
+
+u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) {
+ IocGetConfigParams params{};
+ std::memcpy(&params, input.data(), sizeof(params));
+ LOG_DEBUG(Service_NVDRV, "called, setting=%s!%s", params.domain_str.data(),
+ params.param_str.data());
+
+ if (!strcmp(params.domain_str.data(), "nv")) {
+ if (!strcmp(params.param_str.data(), "NV_MEMORY_PROFILER")) {
+ params.config_str[0] = '0';
+ } else if (!strcmp(params.param_str.data(), "NVN_THROUGH_OPENGL")) {
+ params.config_str[0] = '0';
+ } else if (!strcmp(params.param_str.data(), "NVRM_GPU_PREVENT_USE")) {
+ params.config_str[0] = '0';
+ } else {
+ params.config_str[0] = '0';
+ }
+ } else {
+ UNIMPLEMENTED(); // unknown domain? Only nv has been seen so far on hardware
+ }
+ std::memcpy(output.data(), &params, sizeof(params));
+ return 0;
+}
+
+u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output) {
+ IocCtrlEventWaitParams params{};
+ std::memcpy(&params, input.data(), sizeof(params));
+ LOG_WARNING(Service_NVDRV, "(STUBBED) called, syncpt_id=%u threshold=%u timeout=%d",
+ params.syncpt_id, params.threshold, params.timeout);
+
+ // TODO(Subv): Implement actual syncpt waiting.
+ params.value = 0;
+ std::memcpy(output.data(), &params, sizeof(params));
+ return 0;
+}
+
+} // namespace Devices
+} // namespace Nvidia
+} // namespace Service
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
new file mode 100644
index 000000000..0ca01aa6d
--- /dev/null
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
@@ -0,0 +1,60 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <cstdlib>
+#include <cstring>
+#include <vector>
+#include "common/common_types.h"
+#include "core/hle/service/nvdrv/devices/nvdevice.h"
+
+namespace Service {
+namespace Nvidia {
+namespace Devices {
+
+class nvhost_ctrl final : public nvdevice {
+public:
+ nvhost_ctrl() = default;
+ ~nvhost_ctrl() override = default;
+
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+
+private:
+ enum class IoctlCommand : u32_le {
+ IocSyncptReadCommand = 0xC0080014,
+ IocSyncptIncrCommand = 0x40040015,
+ IocSyncptWaitCommand = 0xC00C0016,
+ IocModuleMutexCommand = 0x40080017,
+ IocModuleRegRDWRCommand = 0xC008010E,
+ IocSyncptWaitexCommand = 0xC0100019,
+ IocSyncptReadMaxCommand = 0xC008001A,
+ IocCtrlEventWaitCommand = 0xC010001D,
+ IocGetConfigCommand = 0xC183001B,
+ };
+
+ struct IocGetConfigParams {
+ std::array<char, 0x41> domain_str;
+ std::array<char, 0x41> param_str;
+ std::array<char, 0x101> config_str;
+ };
+ static_assert(sizeof(IocGetConfigParams) == 387, "IocGetConfigParams is incorrect size");
+
+ struct IocCtrlEventWaitParams {
+ u32_le syncpt_id;
+ u32_le threshold;
+ s32_le timeout;
+ u32_le value;
+ };
+ static_assert(sizeof(IocCtrlEventWaitParams) == 16, "IocCtrlEventWaitParams is incorrect size");
+
+ u32 NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output);
+
+ u32 IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output);
+};
+
+} // namespace Devices
+} // namespace Nvidia
+} // namespace Service
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
new file mode 100644
index 000000000..3b353d742
--- /dev/null
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
@@ -0,0 +1,127 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cinttypes>
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h"
+
+namespace Service {
+namespace Nvidia {
+namespace Devices {
+
+u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+ LOG_DEBUG(Service_NVDRV, "called, command=0x%08x, input_size=0x%zx, output_size=0x%zx",
+ command.raw, input.size(), output.size());
+
+ switch (static_cast<IoctlCommand>(command.raw)) {
+ case IoctlCommand::IocGetCharacteristicsCommand:
+ return GetCharacteristics(input, output);
+ case IoctlCommand::IocGetTPCMasksCommand:
+ return GetTPCMasks(input, output);
+ case IoctlCommand::IocGetActiveSlotMaskCommand:
+ return GetActiveSlotMask(input, output);
+ case IoctlCommand::IocZcullGetCtxSizeCommand:
+ return ZCullGetCtxSize(input, output);
+ case IoctlCommand::IocZcullGetInfo:
+ return ZCullGetInfo(input, output);
+ }
+ UNIMPLEMENTED();
+ return 0;
+}
+
+u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output) {
+ LOG_DEBUG(Service_NVDRV, "called");
+ IoctlCharacteristics params{};
+ std::memcpy(&params, input.data(), input.size());
+ params.gc.arch = 0x120;
+ params.gc.impl = 0xb;
+ params.gc.rev = 0xa1;
+ params.gc.num_gpc = 0x1;
+ params.gc.l2_cache_size = 0x40000;
+ params.gc.on_board_video_memory_size = 0x0;
+ params.gc.num_tpc_per_gpc = 0x2;
+ params.gc.bus_type = 0x20;
+ params.gc.big_page_size = 0x20000;
+ params.gc.compression_page_size = 0x20000;
+ params.gc.pde_coverage_bit_count = 0x1B;
+ params.gc.available_big_page_sizes = 0x30000;
+ params.gc.gpc_mask = 0x1;
+ params.gc.sm_arch_sm_version = 0x503;
+ params.gc.sm_arch_spa_version = 0x503;
+ params.gc.sm_arch_warp_count = 0x80;
+ params.gc.gpu_va_bit_count = 0x28;
+ params.gc.reserved = 0x0;
+ params.gc.flags = 0x55;
+ params.gc.twod_class = 0x902D;
+ params.gc.threed_class = 0xB197;
+ params.gc.compute_class = 0xB1C0;
+ params.gc.gpfifo_class = 0xB06F;
+ params.gc.inline_to_memory_class = 0xA140;
+ params.gc.dma_copy_class = 0xB0B5;
+ params.gc.max_fbps_count = 0x1;
+ params.gc.fbp_en_mask = 0x0;
+ params.gc.max_ltc_per_fbp = 0x2;
+ params.gc.max_lts_per_ltc = 0x1;
+ params.gc.max_tex_per_tpc = 0x0;
+ params.gc.max_gpc_count = 0x1;
+ params.gc.rop_l2_en_mask_0 = 0x21D70;
+ params.gc.rop_l2_en_mask_1 = 0x0;
+ params.gc.chipname = 0x6230326D67;
+ params.gc.gr_compbit_store_base_hw = 0x0;
+ params.gpu_characteristics_buf_size = 0xA0;
+ params.gpu_characteristics_buf_addr = 0xdeadbeef; // Cannot be 0 (UNUSED)
+ std::memcpy(output.data(), &params, output.size());
+ return 0;
+}
+
+u32 nvhost_ctrl_gpu::GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlGpuGetTpcMasksArgs params{};
+ std::memcpy(&params, input.data(), input.size());
+ LOG_WARNING(Service_NVDRV, "(STUBBED) called, mask=0x%x, mask_buf_addr=0x%" PRIx64,
+ params.mask_buf_size, params.mask_buf_addr);
+ std::memcpy(output.data(), &params, sizeof(params));
+ return 0;
+}
+
+u32 nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output) {
+ LOG_DEBUG(Service_NVDRV, "called");
+ IoctlActiveSlotMask params{};
+ std::memcpy(&params, input.data(), input.size());
+ params.slot = 0x07;
+ params.mask = 0x01;
+ std::memcpy(output.data(), &params, output.size());
+ return 0;
+}
+
+u32 nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output) {
+ LOG_DEBUG(Service_NVDRV, "called");
+ IoctlZcullGetCtxSize params{};
+ std::memcpy(&params, input.data(), input.size());
+ params.size = 0x1;
+ std::memcpy(output.data(), &params, output.size());
+ return 0;
+}
+
+u32 nvhost_ctrl_gpu::ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output) {
+ LOG_DEBUG(Service_NVDRV, "called");
+ IoctlNvgpuGpuZcullGetInfoArgs params{};
+ std::memcpy(&params, input.data(), input.size());
+ params.width_align_pixels = 0x20;
+ params.height_align_pixels = 0x20;
+ params.pixel_squares_by_aliquots = 0x400;
+ params.aliquot_total = 0x800;
+ params.region_byte_multiplier = 0x20;
+ params.region_header_size = 0x20;
+ params.subregion_header_size = 0xc0;
+ params.subregion_width_align_pixels = 0x20;
+ params.subregion_height_align_pixels = 0x40;
+ params.subregion_count = 0x10;
+ std::memcpy(output.data(), &params, output.size());
+ return 0;
+}
+
+} // namespace Devices
+} // namespace Nvidia
+} // namespace Service
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
new file mode 100644
index 000000000..dc0476993
--- /dev/null
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
@@ -0,0 +1,130 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/service/nvdrv/devices/nvdevice.h"
+
+namespace Service {
+namespace Nvidia {
+namespace Devices {
+
+class nvhost_ctrl_gpu final : public nvdevice {
+public:
+ nvhost_ctrl_gpu() = default;
+ ~nvhost_ctrl_gpu() override = default;
+
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+
+private:
+ enum class IoctlCommand : u32_le {
+ IocGetCharacteristicsCommand = 0xC0B04705,
+ IocGetTPCMasksCommand = 0xC0184706,
+ IocGetActiveSlotMaskCommand = 0x80084714,
+ IocZcullGetCtxSizeCommand = 0x80044701,
+ IocZcullGetInfo = 0x80284702,
+ };
+
+ struct IoctlGpuCharacteristics {
+ u32_le arch; // 0x120 (NVGPU_GPU_ARCH_GM200)
+ u32_le impl; // 0xB (NVGPU_GPU_IMPL_GM20B)
+ u32_le rev; // 0xA1 (Revision A1)
+ u32_le num_gpc; // 0x1
+ u64_le l2_cache_size; // 0x40000
+ u64_le on_board_video_memory_size; // 0x0 (not used)
+ u32_le num_tpc_per_gpc; // 0x2
+ u32_le bus_type; // 0x20 (NVGPU_GPU_BUS_TYPE_AXI)
+ u32_le big_page_size; // 0x20000
+ u32_le compression_page_size; // 0x20000
+ u32_le pde_coverage_bit_count; // 0x1B
+ u32_le available_big_page_sizes; // 0x30000
+ u32_le gpc_mask; // 0x1
+ u32_le sm_arch_sm_version; // 0x503 (Maxwell Generation 5.0.3?)
+ u32_le sm_arch_spa_version; // 0x503 (Maxwell Generation 5.0.3?)
+ u32_le sm_arch_warp_count; // 0x80
+ u32_le gpu_va_bit_count; // 0x28
+ u32_le reserved; // NULL
+ u64_le flags; // 0x55
+ u32_le twod_class; // 0x902D (FERMI_TWOD_A)
+ u32_le threed_class; // 0xB197 (MAXWELL_B)
+ u32_le compute_class; // 0xB1C0 (MAXWELL_COMPUTE_B)
+ u32_le gpfifo_class; // 0xB06F (MAXWELL_CHANNEL_GPFIFO_A)
+ u32_le inline_to_memory_class; // 0xA140 (KEPLER_INLINE_TO_MEMORY_B)
+ u32_le dma_copy_class; // 0xB0B5 (MAXWELL_DMA_COPY_A)
+ u32_le max_fbps_count; // 0x1
+ u32_le fbp_en_mask; // 0x0 (disabled)
+ u32_le max_ltc_per_fbp; // 0x2
+ u32_le max_lts_per_ltc; // 0x1
+ u32_le max_tex_per_tpc; // 0x0 (not supported)
+ u32_le max_gpc_count; // 0x1
+ u32_le rop_l2_en_mask_0; // 0x21D70 (fuse_status_opt_rop_l2_fbp_r)
+ u32_le rop_l2_en_mask_1; // 0x0
+ u64_le chipname; // 0x6230326D67 ("gm20b")
+ u64_le gr_compbit_store_base_hw; // 0x0 (not supported)
+ };
+ static_assert(sizeof(IoctlGpuCharacteristics) == 160,
+ "IoctlGpuCharacteristics is incorrect size");
+
+ struct IoctlCharacteristics {
+ u64_le gpu_characteristics_buf_size; // must not be NULL, but gets overwritten with
+ // 0xA0=max_size
+ u64_le gpu_characteristics_buf_addr; // ignored, but must not be NULL
+ IoctlGpuCharacteristics gc;
+ };
+ static_assert(sizeof(IoctlCharacteristics) == 16 + sizeof(IoctlGpuCharacteristics),
+ "IoctlCharacteristics is incorrect size");
+
+ struct IoctlGpuGetTpcMasksArgs {
+ /// [in] TPC mask buffer size reserved by userspace. Should be at least
+ /// sizeof(__u32) * fls(gpc_mask) to receive TPC mask for each GPC.
+ /// [out] full kernel buffer size
+ u32_le mask_buf_size;
+ u32_le reserved;
+
+ /// [in] pointer to TPC mask buffer. It will receive one 32-bit TPC mask per GPC or 0 if
+ /// GPC is not enabled or not present. This parameter is ignored if mask_buf_size is 0.
+ u64_le mask_buf_addr;
+ u64_le unk; // Nintendo add this?
+ };
+ static_assert(sizeof(IoctlGpuGetTpcMasksArgs) == 24,
+ "IoctlGpuGetTpcMasksArgs is incorrect size");
+
+ struct IoctlActiveSlotMask {
+ u32_le slot; // always 0x07
+ u32_le mask;
+ };
+ static_assert(sizeof(IoctlActiveSlotMask) == 8, "IoctlActiveSlotMask is incorrect size");
+
+ struct IoctlZcullGetCtxSize {
+ u32_le size;
+ };
+ static_assert(sizeof(IoctlZcullGetCtxSize) == 4, "IoctlZcullGetCtxSize is incorrect size");
+
+ struct IoctlNvgpuGpuZcullGetInfoArgs {
+ u32_le width_align_pixels;
+ u32_le height_align_pixels;
+ u32_le pixel_squares_by_aliquots;
+ u32_le aliquot_total;
+ u32_le region_byte_multiplier;
+ u32_le region_header_size;
+ u32_le subregion_header_size;
+ u32_le subregion_width_align_pixels;
+ u32_le subregion_height_align_pixels;
+ u32_le subregion_count;
+ };
+ static_assert(sizeof(IoctlNvgpuGpuZcullGetInfoArgs) == 40,
+ "IoctlNvgpuGpuZcullGetInfoArgs is incorrect size");
+
+ u32 GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output);
+};
+} // namespace Devices
+} // namespace Nvidia
+} // namespace Service
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
new file mode 100644
index 000000000..da44c65f3
--- /dev/null
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -0,0 +1,147 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cinttypes>
+#include <map>
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
+
+namespace Service {
+namespace Nvidia {
+namespace Devices {
+
+u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+ LOG_DEBUG(Service_NVDRV, "called, command=0x%08x, input_size=0x%zx, output_size=0x%zx",
+ command.raw, input.size(), output.size());
+
+ switch (static_cast<IoctlCommand>(command.raw)) {
+ case IoctlCommand::IocSetNVMAPfdCommand:
+ return SetNVMAPfd(input, output);
+ case IoctlCommand::IocSetClientDataCommand:
+ return SetClientData(input, output);
+ case IoctlCommand::IocGetClientDataCommand:
+ return GetClientData(input, output);
+ case IoctlCommand::IocZCullBind:
+ return ZCullBind(input, output);
+ case IoctlCommand::IocSetErrorNotifierCommand:
+ return SetErrorNotifier(input, output);
+ case IoctlCommand::IocChannelSetPriorityCommand:
+ return SetChannelPriority(input, output);
+ case IoctlCommand::IocAllocGPFIFOEx2Command:
+ return AllocGPFIFOEx2(input, output);
+ case IoctlCommand::IocAllocObjCtxCommand:
+ return AllocateObjectContext(input, output);
+ }
+
+ if (command.group == NVGPU_IOCTL_MAGIC) {
+ if (command.cmd == NVGPU_IOCTL_CHANNEL_SUBMIT_GPFIFO) {
+ return SubmitGPFIFO(input, output);
+ }
+ }
+
+ UNIMPLEMENTED();
+ return 0;
+};
+
+u32 nvhost_gpu::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlSetNvmapFD params{};
+ std::memcpy(&params, input.data(), input.size());
+ LOG_DEBUG(Service_NVDRV, "called, fd=%x", params.nvmap_fd);
+ nvmap_fd = params.nvmap_fd;
+ std::memcpy(output.data(), &params, output.size());
+ return 0;
+}
+
+u32 nvhost_gpu::SetClientData(const std::vector<u8>& input, std::vector<u8>& output) {
+ LOG_DEBUG(Service_NVDRV, "called");
+ IoctlClientData params{};
+ std::memcpy(&params, input.data(), input.size());
+ user_data = params.data;
+ std::memcpy(output.data(), &params, output.size());
+ return 0;
+}
+
+u32 nvhost_gpu::GetClientData(const std::vector<u8>& input, std::vector<u8>& output) {
+ LOG_DEBUG(Service_NVDRV, "called");
+ IoctlClientData params{};
+ std::memcpy(&params, input.data(), input.size());
+ params.data = user_data;
+ std::memcpy(output.data(), &params, output.size());
+ return 0;
+}
+
+u32 nvhost_gpu::ZCullBind(const std::vector<u8>& input, std::vector<u8>& output) {
+ std::memcpy(&zcull_params, input.data(), input.size());
+ LOG_DEBUG(Service_NVDRV, "called, gpu_va=%" PRIx64 ", mode=%x", zcull_params.gpu_va,
+ zcull_params.mode);
+ std::memcpy(output.data(), &zcull_params, output.size());
+ return 0;
+}
+
+u32 nvhost_gpu::SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlSetErrorNotifier params{};
+ std::memcpy(&params, input.data(), input.size());
+ LOG_WARNING(Service_NVDRV, "(STUBBED) called, offset=%" PRIx64 ", size=%" PRIx64 ", mem=%x",
+ params.offset, params.size, params.mem);
+ std::memcpy(output.data(), &params, output.size());
+ return 0;
+}
+
+u32 nvhost_gpu::SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output) {
+ std::memcpy(&channel_priority, input.data(), input.size());
+ LOG_DEBUG(Service_NVDRV, "(STUBBED) called, priority=%x", channel_priority);
+ std::memcpy(output.data(), &channel_priority, output.size());
+ return 0;
+}
+
+u32 nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlAllocGpfifoEx2 params{};
+ std::memcpy(&params, input.data(), input.size());
+ LOG_WARNING(Service_NVDRV,
+ "(STUBBED) called, num_entries=%x, flags=%x, unk0=%x, unk1=%x, unk2=%x, unk3=%x",
+ params.num_entries, params.flags, params.unk0, params.unk1, params.unk2,
+ params.unk3);
+ params.fence_out.id = 0;
+ params.fence_out.value = 0;
+ std::memcpy(output.data(), &params, output.size());
+ return 0;
+}
+
+u32 nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlAllocObjCtx params{};
+ std::memcpy(&params, input.data(), input.size());
+ LOG_WARNING(Service_NVDRV, "(STUBBED) called, class_num=%x, flags=%x", params.class_num,
+ params.flags);
+ params.obj_id = 0x0;
+ std::memcpy(output.data(), &params, output.size());
+ return 0;
+}
+
+u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output) {
+ if (input.size() < sizeof(IoctlSubmitGpfifo))
+ UNIMPLEMENTED();
+ IoctlSubmitGpfifo params{};
+ std::memcpy(&params, input.data(), sizeof(IoctlSubmitGpfifo));
+ LOG_WARNING(Service_NVDRV, "(STUBBED) called, gpfifo=%" PRIx64 ", num_entries=%x, flags=%x",
+ params.gpfifo, params.num_entries, params.flags);
+
+ auto entries = std::vector<IoctlGpfifoEntry>();
+ entries.resize(params.num_entries);
+ std::memcpy(&entries[0], &input.data()[sizeof(IoctlSubmitGpfifo)],
+ params.num_entries * sizeof(IoctlGpfifoEntry));
+ for (auto entry : entries) {
+ VAddr va_addr = entry.Address();
+ Core::System::GetInstance().GPU().ProcessCommandList(va_addr, entry.sz);
+ }
+ params.fence_out.id = 0;
+ params.fence_out.value = 0;
+ std::memcpy(output.data(), &params, output.size());
+ return 0;
+}
+
+} // namespace Devices
+} // namespace Nvidia
+} // namespace Service
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
new file mode 100644
index 000000000..e7e9a0088
--- /dev/null
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
@@ -0,0 +1,144 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <vector>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/service/nvdrv/devices/nvdevice.h"
+
+namespace Service {
+namespace Nvidia {
+namespace Devices {
+
+class nvmap;
+constexpr u32 NVGPU_IOCTL_MAGIC('H');
+constexpr u32 NVGPU_IOCTL_CHANNEL_SUBMIT_GPFIFO(0x8);
+
+class nvhost_gpu final : public nvdevice {
+public:
+ nvhost_gpu(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {}
+ ~nvhost_gpu() override = default;
+
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+
+private:
+ enum class IoctlCommand : u32_le {
+ IocSetNVMAPfdCommand = 0x40044801,
+ IocSetClientDataCommand = 0x40084714,
+ IocGetClientDataCommand = 0x80084715,
+ IocZCullBind = 0xc010480b,
+ IocSetErrorNotifierCommand = 0xC018480C,
+ IocChannelSetPriorityCommand = 0x4004480D,
+ IocAllocGPFIFOEx2Command = 0xC020481A,
+ IocAllocObjCtxCommand = 0xC0104809,
+ };
+
+ enum class CtxObjects : u32_le {
+ Ctx2D = 0x902D,
+ Ctx3D = 0xB197,
+ CtxCompute = 0xB1C0,
+ CtxKepler = 0xA140,
+ CtxDMA = 0xB0B5,
+ CtxChannelGPFIFO = 0xB06F,
+ };
+
+ struct IoctlSetNvmapFD {
+ u32_le nvmap_fd;
+ };
+ static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size");
+
+ struct IoctlClientData {
+ u64_le data;
+ };
+ static_assert(sizeof(IoctlClientData) == 8, "IoctlClientData is incorrect size");
+
+ struct IoctlZCullBind {
+ u64_le gpu_va;
+ u32_le mode; // 0=global, 1=no_ctxsw, 2=separate_buffer, 3=part_of_regular_buf
+ INSERT_PADDING_WORDS(1);
+ };
+ static_assert(sizeof(IoctlZCullBind) == 16, "IoctlZCullBind is incorrect size");
+
+ struct IoctlSetErrorNotifier {
+ u64_le offset;
+ u64_le size;
+ u32_le mem; // nvmap object handle
+ INSERT_PADDING_WORDS(1);
+ };
+ static_assert(sizeof(IoctlSetErrorNotifier) == 24, "IoctlSetErrorNotifier is incorrect size");
+
+ struct IoctlFence {
+ u32_le id;
+ u32_le value;
+ };
+ static_assert(sizeof(IoctlFence) == 8, "IoctlFence is incorrect size");
+
+ struct IoctlAllocGpfifoEx2 {
+ u32_le num_entries; // in
+ u32_le flags; // in
+ u32_le unk0; // in (1 works)
+ IoctlFence fence_out; // out
+ u32_le unk1; // in
+ u32_le unk2; // in
+ u32_le unk3; // in
+ };
+ static_assert(sizeof(IoctlAllocGpfifoEx2) == 32, "IoctlAllocGpfifoEx2 is incorrect size");
+
+ struct IoctlAllocObjCtx {
+ u32_le class_num; // 0x902D=2d, 0xB197=3d, 0xB1C0=compute, 0xA140=kepler, 0xB0B5=DMA,
+ // 0xB06F=channel_gpfifo
+ u32_le flags;
+ u64_le obj_id; // (ignored) used for FREE_OBJ_CTX ioctl, which is not supported
+ };
+ static_assert(sizeof(IoctlAllocObjCtx) == 16, "IoctlAllocObjCtx is incorrect size");
+
+ struct IoctlGpfifoEntry {
+ u32_le entry0; // gpu_va_lo
+ union {
+ u32_le entry1; // gpu_va_hi | (unk_0x02 << 0x08) | (size << 0x0A) | (unk_0x01 << 0x1F)
+ BitField<0, 8, u32_le> gpu_va_hi;
+ BitField<8, 2, u32_le> unk1;
+ BitField<10, 21, u32_le> sz;
+ BitField<31, 1, u32_le> unk2;
+ };
+
+ VAddr Address() const {
+ return (static_cast<VAddr>(gpu_va_hi) << 32) | entry0;
+ }
+ };
+ static_assert(sizeof(IoctlGpfifoEntry) == 8, "IoctlGpfifoEntry is incorrect size");
+
+ struct IoctlSubmitGpfifo {
+ u64_le gpfifo; // (ignored) pointer to gpfifo fence structs
+ u32_le num_entries; // number of fence objects being submitted
+ u32_le flags;
+ IoctlFence fence_out; // returned new fence object for others to wait on
+ };
+ static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(IoctlFence),
+ "submit_gpfifo is incorrect size");
+
+ u32_le nvmap_fd{};
+ u64_le user_data{};
+ IoctlZCullBind zcull_params{};
+ u32_le channel_priority{};
+
+ u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 SetClientData(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 GetClientData(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 ZCullBind(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output);
+ u32 SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output);
+
+ std::shared_ptr<nvmap> nvmap_dev;
+};
+
+} // namespace Devices
+} // namespace Nvidia
+} // namespace Service
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp
index d37b5b159..b3842eb4c 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <algorithm>
+#include <cinttypes>
#include "common/assert.h"
#include "common/logging/log.h"
@@ -13,29 +14,28 @@ namespace Nvidia {
namespace Devices {
VAddr nvmap::GetObjectAddress(u32 handle) const {
- auto itr = handles.find(handle);
- ASSERT(itr != handles.end());
-
- auto object = itr->second;
+ auto object = GetObject(handle);
+ ASSERT(object);
ASSERT(object->status == Object::Status::Allocated);
return object->addr;
}
-u32 nvmap::ioctl(u32 command, const std::vector<u8>& input, std::vector<u8>& output) {
- switch (command) {
- case IocCreateCommand:
+u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+ switch (static_cast<IoctlCommand>(command.raw)) {
+ case IoctlCommand::Create:
return IocCreate(input, output);
- case IocAllocCommand:
+ case IoctlCommand::Alloc:
return IocAlloc(input, output);
- case IocGetIdCommand:
+ case IoctlCommand::GetId:
return IocGetId(input, output);
- case IocFromIdCommand:
+ case IoctlCommand::FromId:
return IocFromId(input, output);
- case IocParamCommand:
+ case IoctlCommand::Param:
return IocParam(input, output);
}
UNIMPLEMENTED();
+ return 0;
}
u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
@@ -51,7 +51,7 @@ u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
u32 handle = next_handle++;
handles[handle] = std::move(object);
- LOG_WARNING(Service, "(STUBBED) size 0x%08X", params.size);
+ LOG_DEBUG(Service_NVDRV, "size=0x%08X", params.size);
params.handle = handle;
@@ -63,17 +63,16 @@ u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
IocAllocParams params;
std::memcpy(&params, input.data(), sizeof(params));
- auto itr = handles.find(params.handle);
- ASSERT(itr != handles.end());
+ auto object = GetObject(params.handle);
+ ASSERT(object);
- auto object = itr->second;
object->flags = params.flags;
object->align = params.align;
object->kind = params.kind;
object->addr = params.addr;
object->status = Object::Status::Allocated;
- LOG_WARNING(Service, "(STUBBED) Allocated address 0x%llx", params.addr);
+ LOG_DEBUG(Service_NVDRV, "called, addr=0x%" PRIx64, params.addr);
std::memcpy(output.data(), &params, sizeof(params));
return 0;
@@ -83,12 +82,12 @@ u32 nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) {
IocGetIdParams params;
std::memcpy(&params, input.data(), sizeof(params));
- LOG_WARNING(Service, "called");
+ LOG_WARNING(Service_NVDRV, "called");
- auto itr = handles.find(params.handle);
- ASSERT(itr != handles.end());
+ auto object = GetObject(params.handle);
+ ASSERT(object);
- params.id = itr->second->id;
+ params.id = object->id;
std::memcpy(output.data(), &params, sizeof(params));
return 0;
@@ -98,17 +97,14 @@ u32 nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) {
IocFromIdParams params;
std::memcpy(&params, input.data(), sizeof(params));
- LOG_WARNING(Service, "(STUBBED) called");
+ LOG_WARNING(Service_NVDRV, "(STUBBED) called");
auto itr = std::find_if(handles.begin(), handles.end(),
[&](const auto& entry) { return entry.second->id == params.id; });
ASSERT(itr != handles.end());
- // Make a new handle for the object
- u32 handle = next_handle++;
- handles[handle] = itr->second;
-
- params.handle = handle;
+ // Return the existing handle instead of creating a new one.
+ params.handle = itr->first;
std::memcpy(output.data(), &params, sizeof(params));
return 0;
@@ -120,12 +116,10 @@ u32 nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) {
IocParamParams params;
std::memcpy(&params, input.data(), sizeof(params));
- LOG_WARNING(Service, "(STUBBED) called type=%u", params.type);
-
- auto itr = handles.find(params.handle);
- ASSERT(itr != handles.end());
+ LOG_WARNING(Service_NVDRV, "(STUBBED) called type=%u", params.type);
- auto object = itr->second;
+ auto object = GetObject(params.handle);
+ ASSERT(object);
ASSERT(object->status == Object::Status::Allocated);
switch (static_cast<ParamTypes>(params.type)) {
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h
index 6954c0324..4681e438b 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.h
+++ b/src/core/hle/service/nvdrv/devices/nvmap.h
@@ -24,10 +24,9 @@ public:
/// Returns the allocated address of an nvmap object given its handle.
VAddr GetObjectAddress(u32 handle) const;
- u32 ioctl(u32 command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
-private:
- // Represents an nvmap object.
+ /// Represents an nvmap object.
struct Object {
enum class Status { Created, Allocated };
u32 id;
@@ -39,21 +38,30 @@ private:
Status status;
};
+ std::shared_ptr<Object> GetObject(u32 handle) const {
+ auto itr = handles.find(handle);
+ if (itr != handles.end()) {
+ return itr->second;
+ }
+ return {};
+ }
+
+private:
/// Id to use for the next handle that is created.
u32 next_handle = 1;
- // Id to use for the next object that is created.
+ /// Id to use for the next object that is created.
u32 next_id = 1;
/// Mapping of currently allocated handles to the objects they represent.
std::unordered_map<u32, std::shared_ptr<Object>> handles;
- enum IoctlCommands {
- IocCreateCommand = 0xC0080101,
- IocFromIdCommand = 0xC0080103,
- IocAllocCommand = 0xC0200104,
- IocParamCommand = 0xC00C0109,
- IocGetIdCommand = 0xC008010E
+ enum class IoctlCommand : u32 {
+ Create = 0xC0080101,
+ FromId = 0xC0080103,
+ Alloc = 0xC0200104,
+ Param = 0xC00C0109,
+ GetId = 0xC008010E
};
struct IocCreateParams {
diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp
index 0670ca155..c70370f1f 100644
--- a/src/core/hle/service/nvdrv/interface.cpp
+++ b/src/core/hle/service/nvdrv/interface.cpp
@@ -2,8 +2,10 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cinttypes>
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/event.h"
#include "core/hle/service/nvdrv/interface.h"
#include "core/hle/service/nvdrv/nvdrv.h"
@@ -11,58 +13,95 @@ namespace Service {
namespace Nvidia {
void NVDRV::Open(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ LOG_DEBUG(Service_NVDRV, "called");
- auto buffer = ctx.BufferDescriptorA()[0];
-
- std::string device_name = Memory::ReadCString(buffer.Address(), buffer.Size());
+ const auto& buffer = ctx.ReadBuffer();
+ std::string device_name(buffer.begin(), buffer.end());
u32 fd = nvdrv->Open(device_name);
- IPC::RequestBuilder rb{ctx, 4};
+ IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(fd);
rb.Push<u32>(0);
}
void NVDRV::Ioctl(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ LOG_DEBUG(Service_NVDRV, "called");
IPC::RequestParser rp{ctx};
u32 fd = rp.Pop<u32>();
u32 command = rp.Pop<u32>();
- auto input_buffer = ctx.BufferDescriptorA()[0];
- auto output_buffer = ctx.BufferDescriptorB()[0];
+ std::vector<u8> output(ctx.GetWriteBufferSize());
- std::vector<u8> input(input_buffer.Size());
- std::vector<u8> output(output_buffer.Size());
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(nvdrv->Ioctl(fd, command, ctx.ReadBuffer(), output));
- Memory::ReadBlock(input_buffer.Address(), input.data(), input_buffer.Size());
+ ctx.WriteBuffer(output);
+}
- u32 nv_result = nvdrv->Ioctl(fd, command, input, output);
+void NVDRV::Close(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NVDRV, "called");
- Memory::WriteBlock(output_buffer.Address(), output.data(), output_buffer.Size());
+ IPC::RequestParser rp{ctx};
+ u32 fd = rp.Pop<u32>();
- IPC::RequestBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push(nv_result);
+ auto result = nvdrv->Close(fd);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
}
void NVDRV::Initialize(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
- IPC::RequestBuilder rb{ctx, 3};
+ LOG_WARNING(Service_NVDRV, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0);
+}
+
+void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ u32 fd = rp.Pop<u32>();
+ u32 event_id = rp.Pop<u32>();
+ LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd=%x, event_id=%x", fd, event_id);
+
+ IPC::ResponseBuilder rb{ctx, 3, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(query_event);
+ rb.Push<u32>(0);
+}
+
+void NVDRV::SetClientPID(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ pid = rp.Pop<u64>();
+
+ LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid=0x%" PRIx64, pid);
+ IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(0);
}
+void NVDRV::FinishInitialize(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NVDRV, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name)
: ServiceFramework(name), nvdrv(std::move(nvdrv)) {
static const FunctionInfo functions[] = {
{0, &NVDRV::Open, "Open"},
{1, &NVDRV::Ioctl, "Ioctl"},
+ {2, &NVDRV::Close, "Close"},
{3, &NVDRV::Initialize, "Initialize"},
+ {4, &NVDRV::QueryEvent, "QueryEvent"},
+ {8, &NVDRV::SetClientPID, "SetClientPID"},
+ {13, &NVDRV::FinishInitialize, "FinishInitialize"},
};
RegisterHandlers(functions);
+
+ query_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "NVDRV::query_event");
}
} // namespace Nvidia
diff --git a/src/core/hle/service/nvdrv/interface.h b/src/core/hle/service/nvdrv/interface.h
index 8c95b7217..daf2302af 100644
--- a/src/core/hle/service/nvdrv/interface.h
+++ b/src/core/hle/service/nvdrv/interface.h
@@ -6,6 +6,7 @@
#include <memory>
#include <string>
+#include "core/hle/kernel/event.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/service.h"
@@ -20,9 +21,17 @@ public:
private:
void Open(Kernel::HLERequestContext& ctx);
void Ioctl(Kernel::HLERequestContext& ctx);
+ void Close(Kernel::HLERequestContext& ctx);
void Initialize(Kernel::HLERequestContext& ctx);
+ void QueryEvent(Kernel::HLERequestContext& ctx);
+ void SetClientPID(Kernel::HLERequestContext& ctx);
+ void FinishInitialize(Kernel::HLERequestContext& ctx);
std::shared_ptr<Module> nvdrv;
+
+ u64 pid{};
+
+ Kernel::SharedPtr<Kernel::Event> query_event;
};
} // namespace Nvidia
diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp
index cf525a875..ea00240e6 100644
--- a/src/core/hle/service/nvdrv/nvdrv.cpp
+++ b/src/core/hle/service/nvdrv/nvdrv.cpp
@@ -6,9 +6,13 @@
#include "core/hle/service/nvdrv/devices/nvdevice.h"
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
+#include "core/hle/service/nvdrv/devices/nvhost_ctrl.h"
+#include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h"
+#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
#include "core/hle/service/nvdrv/devices/nvmap.h"
-#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvdrv/interface.h"
+#include "core/hle/service/nvdrv/nvdrv.h"
+#include "core/hle/service/nvdrv/nvmemp.h"
namespace Service {
namespace Nvidia {
@@ -19,14 +23,20 @@ void InstallInterfaces(SM::ServiceManager& service_manager) {
auto module_ = std::make_shared<Module>();
std::make_shared<NVDRV>(module_, "nvdrv")->InstallAsService(service_manager);
std::make_shared<NVDRV>(module_, "nvdrv:a")->InstallAsService(service_manager);
+ std::make_shared<NVDRV>(module_, "nvdrv:s")->InstallAsService(service_manager);
+ std::make_shared<NVDRV>(module_, "nvdrv:t")->InstallAsService(service_manager);
+ std::make_shared<NVMEMP>()->InstallAsService(service_manager);
nvdrv = module_;
}
Module::Module() {
auto nvmap_dev = std::make_shared<Devices::nvmap>();
- devices["/dev/nvhost-as-gpu"] = std::make_shared<Devices::nvhost_as_gpu>();
+ devices["/dev/nvhost-as-gpu"] = std::make_shared<Devices::nvhost_as_gpu>(nvmap_dev);
+ devices["/dev/nvhost-gpu"] = std::make_shared<Devices::nvhost_gpu>(nvmap_dev);
+ devices["/dev/nvhost-ctrl-gpu"] = std::make_shared<Devices::nvhost_ctrl_gpu>();
devices["/dev/nvmap"] = nvmap_dev;
devices["/dev/nvdisp_disp0"] = std::make_shared<Devices::nvdisp_disp0>(nvmap_dev);
+ devices["/dev/nvhost-ctrl"] = std::make_shared<Devices::nvhost_ctrl>();
}
u32 Module::Open(std::string device_name) {
@@ -41,12 +51,22 @@ u32 Module::Open(std::string device_name) {
return fd;
}
-u32 Module::Ioctl(u32 fd, u32 command, const std::vector<u8>& input, std::vector<u8>& output) {
+u32 Module::Ioctl(u32 fd, u32_le command, const std::vector<u8>& input, std::vector<u8>& output) {
auto itr = open_files.find(fd);
ASSERT_MSG(itr != open_files.end(), "Tried to talk to an invalid device");
auto device = itr->second;
- return device->ioctl(command, input, output);
+ return device->ioctl({command}, input, output);
+}
+
+ResultCode Module::Close(u32 fd) {
+ auto itr = open_files.find(fd);
+ ASSERT_MSG(itr != open_files.end(), "Tried to talk to an invalid device");
+
+ open_files.erase(itr);
+
+ // TODO(flerovium): return correct result code if operation failed.
+ return RESULT_SUCCESS;
}
} // namespace Nvidia
diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h
index 1940ced99..6a55ff96d 100644
--- a/src/core/hle/service/nvdrv/nvdrv.h
+++ b/src/core/hle/service/nvdrv/nvdrv.h
@@ -17,6 +17,13 @@ namespace Devices {
class nvdevice;
}
+struct IoctlFence {
+ u32 id;
+ u32 value;
+};
+
+static_assert(sizeof(IoctlFence) == 8, "IoctlFence has wrong size");
+
class Module final {
public:
Module();
@@ -35,6 +42,8 @@ public:
u32 Open(std::string device_name);
/// Sends an ioctl command to the specified file descriptor.
u32 Ioctl(u32 fd, u32 command, const std::vector<u8>& input, std::vector<u8>& output);
+ /// Closes a device file descriptor and returns operation success.
+ ResultCode Close(u32 fd);
private:
/// Id to use for the next open file descriptor.
diff --git a/src/core/hle/service/nvdrv/nvmemp.cpp b/src/core/hle/service/nvdrv/nvmemp.cpp
new file mode 100644
index 000000000..5a13732c7
--- /dev/null
+++ b/src/core/hle/service/nvdrv/nvmemp.cpp
@@ -0,0 +1,31 @@
+// Copyright 2018 yuzu emulator team
+// 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/hle/ipc_helpers.h"
+#include "core/hle/service/nvdrv/nvdrv.h"
+#include "core/hle/service/nvdrv/nvmemp.h"
+
+namespace Service {
+namespace Nvidia {
+
+NVMEMP::NVMEMP() : ServiceFramework("nvmemp") {
+ static const FunctionInfo functions[] = {
+ {0, &NVMEMP::Unknown0, "Unknown0"},
+ {1, &NVMEMP::Unknown1, "Unknown1"},
+ };
+ RegisterHandlers(functions);
+}
+
+void NVMEMP::Unknown0(Kernel::HLERequestContext& ctx) {
+ UNIMPLEMENTED();
+}
+
+void NVMEMP::Unknown1(Kernel::HLERequestContext& ctx) {
+ UNIMPLEMENTED();
+}
+
+} // namespace Nvidia
+} // namespace Service
diff --git a/src/core/hle/service/nvdrv/nvmemp.h b/src/core/hle/service/nvdrv/nvmemp.h
new file mode 100644
index 000000000..a6b5fbb82
--- /dev/null
+++ b/src/core/hle/service/nvdrv/nvmemp.h
@@ -0,0 +1,23 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace Nvidia {
+
+class NVMEMP final : public ServiceFramework<NVMEMP> {
+public:
+ NVMEMP();
+ ~NVMEMP() = default;
+
+private:
+ void Unknown0(Kernel::HLERequestContext& ctx);
+ void Unknown1(Kernel::HLERequestContext& ctx);
+};
+
+} // namespace Nvidia
+} // namespace Service
diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp
new file mode 100644
index 000000000..e4ff2e267
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue.cpp
@@ -0,0 +1,115 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+
+#include "common/alignment.h"
+#include "common/scope_exit.h"
+#include "core/core_timing.h"
+#include "core/hle/service/nvflinger/buffer_queue.h"
+
+namespace Service {
+namespace NVFlinger {
+
+BufferQueue::BufferQueue(u32 id, u64 layer_id) : id(id), layer_id(layer_id) {
+ native_handle = Kernel::Event::Create(Kernel::ResetType::OneShot, "BufferQueue NativeHandle");
+ native_handle->Signal();
+}
+
+void BufferQueue::SetPreallocatedBuffer(u32 slot, IGBPBuffer& igbp_buffer) {
+ Buffer buffer{};
+ buffer.slot = slot;
+ buffer.igbp_buffer = igbp_buffer;
+ buffer.status = Buffer::Status::Free;
+
+ LOG_WARNING(Service, "Adding graphics buffer %u", slot);
+
+ queue.emplace_back(buffer);
+
+ if (buffer_wait_event) {
+ buffer_wait_event->Signal();
+ }
+}
+
+boost::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) {
+ auto itr = std::find_if(queue.begin(), queue.end(), [&](const Buffer& buffer) {
+ // Only consider free buffers. Buffers become free once again after they've been Acquired
+ // and Released by the compositor, see the NVFlinger::Compose method.
+ if (buffer.status != Buffer::Status::Free) {
+ return false;
+ }
+
+ // Make sure that the parameters match.
+ return buffer.igbp_buffer.width == width && buffer.igbp_buffer.height == height;
+ });
+
+ if (itr == queue.end()) {
+ return boost::none;
+ }
+
+ buffer_wait_event = nullptr;
+
+ itr->status = Buffer::Status::Dequeued;
+ return itr->slot;
+}
+
+const IGBPBuffer& BufferQueue::RequestBuffer(u32 slot) const {
+ auto itr = std::find_if(queue.begin(), queue.end(),
+ [&](const Buffer& buffer) { return buffer.slot == slot; });
+ ASSERT(itr != queue.end());
+ ASSERT(itr->status == Buffer::Status::Dequeued);
+ return itr->igbp_buffer;
+}
+
+void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform) {
+ auto itr = std::find_if(queue.begin(), queue.end(),
+ [&](const Buffer& buffer) { return buffer.slot == slot; });
+ ASSERT(itr != queue.end());
+ ASSERT(itr->status == Buffer::Status::Dequeued);
+ itr->status = Buffer::Status::Queued;
+ itr->transform = transform;
+}
+
+boost::optional<const BufferQueue::Buffer&> BufferQueue::AcquireBuffer() {
+ auto itr = std::find_if(queue.begin(), queue.end(), [](const Buffer& buffer) {
+ return buffer.status == Buffer::Status::Queued;
+ });
+ if (itr == queue.end())
+ return boost::none;
+ itr->status = Buffer::Status::Acquired;
+ return *itr;
+}
+
+void BufferQueue::ReleaseBuffer(u32 slot) {
+ auto itr = std::find_if(queue.begin(), queue.end(),
+ [&](const Buffer& buffer) { return buffer.slot == slot; });
+ ASSERT(itr != queue.end());
+ ASSERT(itr->status == Buffer::Status::Acquired);
+ itr->status = Buffer::Status::Free;
+
+ if (buffer_wait_event) {
+ buffer_wait_event->Signal();
+ }
+}
+
+u32 BufferQueue::Query(QueryType type) {
+ LOG_WARNING(Service, "(STUBBED) called type=%u", static_cast<u32>(type));
+ switch (type) {
+ case QueryType::NativeWindowFormat:
+ // TODO(Subv): Use an enum for this
+ static constexpr u32 FormatABGR8 = 1;
+ return FormatABGR8;
+ }
+
+ UNIMPLEMENTED();
+ return 0;
+}
+
+void BufferQueue::SetBufferWaitEvent(Kernel::SharedPtr<Kernel::Event>&& wait_event) {
+ ASSERT_MSG(!buffer_wait_event, "buffer_wait_event only supports a single waiting thread!");
+ buffer_wait_event = std::move(wait_event);
+}
+
+} // namespace NVFlinger
+} // namespace Service
diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h
new file mode 100644
index 000000000..1de5767cb
--- /dev/null
+++ b/src/core/hle/service/nvflinger/buffer_queue.h
@@ -0,0 +1,102 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include <boost/optional.hpp>
+#include "common/swap.h"
+#include "core/hle/kernel/event.h"
+
+namespace CoreTiming {
+struct EventType;
+}
+
+namespace Service {
+namespace NVFlinger {
+
+struct IGBPBuffer {
+ u32_le magic;
+ u32_le width;
+ u32_le height;
+ u32_le stride;
+ u32_le format;
+ u32_le usage;
+ INSERT_PADDING_WORDS(1);
+ u32_le index;
+ INSERT_PADDING_WORDS(3);
+ u32_le gpu_buffer_id;
+ INSERT_PADDING_WORDS(17);
+ u32_le nvmap_handle;
+ u32_le offset;
+ INSERT_PADDING_WORDS(60);
+};
+
+static_assert(sizeof(IGBPBuffer) == 0x16C, "IGBPBuffer has wrong size");
+
+class BufferQueue final {
+public:
+ enum class QueryType {
+ NativeWindowWidth = 0,
+ NativeWindowHeight = 1,
+ NativeWindowFormat = 2,
+ };
+
+ BufferQueue(u32 id, u64 layer_id);
+ ~BufferQueue() = default;
+
+ enum class BufferTransformFlags : u32 {
+ /// No transform flags are set
+ Unset = 0x00,
+ /// Flip source image horizontally (around the vertical axis)
+ FlipH = 0x01,
+ /// Flip source image vertically (around the horizontal axis)
+ FlipV = 0x02,
+ /// Rotate source image 90 degrees clockwise
+ Rotate90 = 0x04,
+ /// Rotate source image 180 degrees
+ Roate180 = 0x03,
+ /// Rotate source image 270 degrees clockwise
+ Roate270 = 0x07,
+ };
+
+ struct Buffer {
+ enum class Status { Free = 0, Queued = 1, Dequeued = 2, Acquired = 3 };
+
+ u32 slot;
+ Status status = Status::Free;
+ IGBPBuffer igbp_buffer;
+ BufferTransformFlags transform;
+ };
+
+ void SetPreallocatedBuffer(u32 slot, IGBPBuffer& buffer);
+ boost::optional<u32> DequeueBuffer(u32 width, u32 height);
+ const IGBPBuffer& RequestBuffer(u32 slot) const;
+ void QueueBuffer(u32 slot, BufferTransformFlags transform);
+ boost::optional<const Buffer&> AcquireBuffer();
+ void ReleaseBuffer(u32 slot);
+ u32 Query(QueryType type);
+ void SetBufferWaitEvent(Kernel::SharedPtr<Kernel::Event>&& wait_event);
+
+ u32 GetId() const {
+ return id;
+ }
+
+ Kernel::SharedPtr<Kernel::Event> GetNativeHandle() const {
+ return native_handle;
+ }
+
+private:
+ u32 id;
+ u64 layer_id;
+
+ std::vector<Buffer> queue;
+ Kernel::SharedPtr<Kernel::Event> native_handle;
+
+ /// Used to signal waiting thread when no buffers are available
+ Kernel::SharedPtr<Kernel::Event> buffer_wait_event;
+};
+
+} // namespace NVFlinger
+} // namespace Service
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
new file mode 100644
index 000000000..0d30f54dc
--- /dev/null
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -0,0 +1,166 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+
+#include "common/alignment.h"
+#include "common/scope_exit.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
+#include "core/hle/service/nvdrv/nvdrv.h"
+#include "core/hle/service/nvflinger/buffer_queue.h"
+#include "core/hle/service/nvflinger/nvflinger.h"
+#include "video_core/renderer_base.h"
+#include "video_core/video_core.h"
+
+namespace Service {
+namespace NVFlinger {
+
+constexpr size_t SCREEN_REFRESH_RATE = 60;
+constexpr u64 frame_ticks = static_cast<u64>(BASE_CLOCK_RATE / SCREEN_REFRESH_RATE);
+
+NVFlinger::NVFlinger() {
+ // Add the different displays to the list of displays.
+ Display default_{0, "Default"};
+ Display external{1, "External"};
+ Display edid{2, "Edid"};
+ Display internal{3, "Internal"};
+
+ displays.emplace_back(default_);
+ displays.emplace_back(external);
+ displays.emplace_back(edid);
+ displays.emplace_back(internal);
+
+ // Schedule the screen composition events
+ composition_event =
+ CoreTiming::RegisterEvent("ScreenCompositioin", [this](u64 userdata, int cycles_late) {
+ Compose();
+ CoreTiming::ScheduleEvent(frame_ticks - cycles_late, composition_event);
+ });
+
+ CoreTiming::ScheduleEvent(frame_ticks, composition_event);
+}
+
+NVFlinger::~NVFlinger() {
+ CoreTiming::UnscheduleEvent(composition_event, 0);
+}
+
+u64 NVFlinger::OpenDisplay(const std::string& name) {
+ LOG_WARNING(Service, "Opening display %s", name.c_str());
+
+ // TODO(Subv): Currently we only support the Default display.
+ ASSERT(name == "Default");
+
+ auto itr = std::find_if(displays.begin(), displays.end(),
+ [&](const Display& display) { return display.name == name; });
+
+ ASSERT(itr != displays.end());
+
+ return itr->id;
+}
+
+u64 NVFlinger::CreateLayer(u64 display_id) {
+ auto& display = GetDisplay(display_id);
+
+ ASSERT_MSG(display.layers.empty(), "Only one layer is supported per display at the moment");
+
+ u64 layer_id = next_layer_id++;
+ u32 buffer_queue_id = next_buffer_queue_id++;
+ auto buffer_queue = std::make_shared<BufferQueue>(buffer_queue_id, layer_id);
+ display.layers.emplace_back(layer_id, buffer_queue);
+ buffer_queues.emplace_back(std::move(buffer_queue));
+ return layer_id;
+}
+
+u32 NVFlinger::GetBufferQueueId(u64 display_id, u64 layer_id) {
+ const auto& layer = GetLayer(display_id, layer_id);
+ return layer.buffer_queue->GetId();
+}
+
+Kernel::SharedPtr<Kernel::Event> NVFlinger::GetVsyncEvent(u64 display_id) {
+ const auto& display = GetDisplay(display_id);
+ return display.vsync_event;
+}
+
+std::shared_ptr<BufferQueue> NVFlinger::GetBufferQueue(u32 id) const {
+ auto itr = std::find_if(buffer_queues.begin(), buffer_queues.end(),
+ [&](const auto& queue) { return queue->GetId() == id; });
+
+ ASSERT(itr != buffer_queues.end());
+ return *itr;
+}
+
+Display& NVFlinger::GetDisplay(u64 display_id) {
+ auto itr = std::find_if(displays.begin(), displays.end(),
+ [&](const Display& display) { return display.id == display_id; });
+
+ ASSERT(itr != displays.end());
+ return *itr;
+}
+
+Layer& NVFlinger::GetLayer(u64 display_id, u64 layer_id) {
+ auto& display = GetDisplay(display_id);
+
+ auto itr = std::find_if(display.layers.begin(), display.layers.end(),
+ [&](const Layer& layer) { return layer.id == layer_id; });
+
+ ASSERT(itr != display.layers.end());
+ return *itr;
+}
+
+void NVFlinger::Compose() {
+ for (auto& display : displays) {
+ // Trigger vsync for this display at the end of drawing
+ SCOPE_EXIT({ display.vsync_event->Signal(); });
+
+ // Don't do anything for displays without layers.
+ if (display.layers.empty())
+ continue;
+
+ // TODO(Subv): Support more than 1 layer.
+ ASSERT_MSG(display.layers.size() == 1, "Max 1 layer per display is supported");
+
+ Layer& layer = display.layers[0];
+ auto& buffer_queue = layer.buffer_queue;
+
+ // Search for a queued buffer and acquire it
+ auto buffer = buffer_queue->AcquireBuffer();
+
+ if (buffer == boost::none) {
+ // There was no queued buffer to draw, render previous frame
+ Core::System::GetInstance().perf_stats.EndGameFrame();
+ VideoCore::g_renderer->SwapBuffers({});
+ continue;
+ }
+
+ auto& igbp_buffer = buffer->igbp_buffer;
+
+ // Now send the buffer to the GPU for drawing.
+ auto nvdrv = Nvidia::nvdrv.lock();
+ ASSERT(nvdrv);
+
+ // TODO(Subv): Support more than just disp0. The display device selection is probably based
+ // on which display we're drawing (Default, Internal, External, etc)
+ auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>("/dev/nvdisp_disp0");
+ ASSERT(nvdisp);
+
+ nvdisp->flip(igbp_buffer.gpu_buffer_id, igbp_buffer.offset, igbp_buffer.format,
+ igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride, buffer->transform);
+
+ buffer_queue->ReleaseBuffer(buffer->slot);
+
+ // TODO(Subv): Figure out when we should actually signal this event.
+ buffer_queue->GetNativeHandle()->Signal();
+ }
+}
+
+Layer::Layer(u64 id, std::shared_ptr<BufferQueue> queue) : id(id), buffer_queue(std::move(queue)) {}
+
+Display::Display(u64 id, std::string name) : id(id), name(std::move(name)) {
+ vsync_event = Kernel::Event::Create(Kernel::ResetType::Pulse, "Display VSync Event");
+}
+
+} // namespace NVFlinger
+} // namespace Service
diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h
new file mode 100644
index 000000000..3126018ad
--- /dev/null
+++ b/src/core/hle/service/nvflinger/nvflinger.h
@@ -0,0 +1,84 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <boost/optional.hpp>
+#include "core/hle/kernel/event.h"
+
+namespace CoreTiming {
+struct EventType;
+}
+
+namespace Service {
+namespace NVFlinger {
+
+class BufferQueue;
+
+struct Layer {
+ Layer(u64 id, std::shared_ptr<BufferQueue> queue);
+ ~Layer() = default;
+
+ u64 id;
+ std::shared_ptr<BufferQueue> buffer_queue;
+};
+
+struct Display {
+ Display(u64 id, std::string name);
+ ~Display() = default;
+
+ u64 id;
+ std::string name;
+
+ std::vector<Layer> layers;
+ Kernel::SharedPtr<Kernel::Event> vsync_event;
+};
+
+class NVFlinger final {
+public:
+ NVFlinger();
+ ~NVFlinger();
+
+ /// Opens the specified display and returns the id.
+ u64 OpenDisplay(const std::string& name);
+
+ /// Creates a layer on the specified display and returns the layer id.
+ u64 CreateLayer(u64 display_id);
+
+ /// Gets the buffer queue id of the specified layer in the specified display.
+ u32 GetBufferQueueId(u64 display_id, u64 layer_id);
+
+ /// Gets the vsync event for the specified display.
+ Kernel::SharedPtr<Kernel::Event> GetVsyncEvent(u64 display_id);
+
+ /// Obtains a buffer queue identified by the id.
+ std::shared_ptr<BufferQueue> GetBufferQueue(u32 id) const;
+
+ /// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when
+ /// finished.
+ void Compose();
+
+private:
+ /// Returns the display identified by the specified id.
+ Display& GetDisplay(u64 display_id);
+
+ /// Returns the layer identified by the specified id in the desired display.
+ Layer& GetLayer(u64 display_id, u64 layer_id);
+
+ std::vector<Display> displays;
+ std::vector<std::shared_ptr<BufferQueue>> buffer_queues;
+
+ /// Id to use for the next layer that is created, this counter is shared among all displays.
+ u64 next_layer_id = 1;
+ /// Id to use for the next buffer queue that is created, this counter is shared among all
+ /// layers.
+ u32 next_buffer_queue_id = 1;
+
+ /// CoreTiming event that handles screen composition.
+ CoreTiming::EventType* composition_event;
+};
+
+} // namespace NVFlinger
+} // namespace Service
diff --git a/src/core/hle/service/pctl/pctl_a.cpp b/src/core/hle/service/pctl/pctl_a.cpp
index 7978aecb8..c65fffa07 100644
--- a/src/core/hle/service/pctl/pctl_a.cpp
+++ b/src/core/hle/service/pctl/pctl_a.cpp
@@ -15,10 +15,10 @@ public:
};
void PCTL_A::GetService(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 2, 0, 0, 1};
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IParentalControlService>();
- LOG_DEBUG(Service, "called");
+ LOG_DEBUG(Service_PCTL, "called");
}
PCTL_A::PCTL_A() : ServiceFramework("pctl:a") {
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index fe76b381c..8011d0d71 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -7,6 +7,7 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/string_util.h"
+#include "core/core.h"
#include "core/hle/ipc.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
@@ -19,13 +20,23 @@
#include "core/hle/service/aoc/aoc_u.h"
#include "core/hle/service/apm/apm.h"
#include "core/hle/service/audio/audio.h"
+#include "core/hle/service/fatal/fatal.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/friend/friend.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/lm/lm.h"
+#include "core/hle/service/nfp/nfp.h"
+#include "core/hle/service/nifm/nifm.h"
+#include "core/hle/service/ns/ns.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/pctl/pctl.h"
#include "core/hle/service/service.h"
+#include "core/hle/service/set/settings.h"
#include "core/hle/service/sm/controller.h"
#include "core/hle/service/sm/sm.h"
+#include "core/hle/service/sockets/sockets.h"
+#include "core/hle/service/spl/module.h"
+#include "core/hle/service/ssl/ssl.h"
#include "core/hle/service/time/time.h"
#include "core/hle/service/vi/vi.h"
@@ -129,7 +140,7 @@ void ServiceFrameworkBase::InvokeRequest(Kernel::HLERequestContext& ctx) {
ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& context) {
switch (context.GetCommandType()) {
case IPC::CommandType::Close: {
- IPC::RequestBuilder rb{context, 1};
+ IPC::ResponseBuilder rb{context, 2};
rb.Push(RESULT_SUCCESS);
return ResultCode(ErrorModule::HIPC, ErrorDescription::RemoteProcessDead);
}
@@ -142,12 +153,10 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
break;
}
default:
- UNIMPLEMENTED_MSG("command_type=%d", context.GetCommandType());
+ UNIMPLEMENTED_MSG("command_type=%d", static_cast<int>(context.GetCommandType()));
}
- u32* cmd_buf = (u32*)Memory::GetPointer(Kernel::GetCurrentThread()->GetTLSAddress());
- context.WriteToOutgoingCommandBuffer(cmd_buf, *Kernel::g_current_process,
- Kernel::g_handle_table);
+ context.WriteToOutgoingCommandBuffer(*Kernel::GetCurrentThread());
return RESULT_SUCCESS;
}
@@ -162,20 +171,34 @@ void AddNamedPort(std::string name, SharedPtr<ClientPort> port) {
/// Initialize ServiceManager
void Init() {
+ // NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it
+ // here and pass it into the respective InstallInterfaces functions.
+ auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>();
+
SM::g_service_manager = std::make_shared<SM::ServiceManager>();
SM::ServiceManager::InstallInterfaces(SM::g_service_manager);
Account::InstallInterfaces(*SM::g_service_manager);
- AM::InstallInterfaces(*SM::g_service_manager);
+ AM::InstallInterfaces(*SM::g_service_manager, nv_flinger);
AOC::InstallInterfaces(*SM::g_service_manager);
APM::InstallInterfaces(*SM::g_service_manager);
Audio::InstallInterfaces(*SM::g_service_manager);
+ Fatal::InstallInterfaces(*SM::g_service_manager);
+ FileSystem::InstallInterfaces(*SM::g_service_manager);
+ Friend::InstallInterfaces(*SM::g_service_manager);
HID::InstallInterfaces(*SM::g_service_manager);
LM::InstallInterfaces(*SM::g_service_manager);
+ NFP::InstallInterfaces(*SM::g_service_manager);
+ NIFM::InstallInterfaces(*SM::g_service_manager);
+ NS::InstallInterfaces(*SM::g_service_manager);
Nvidia::InstallInterfaces(*SM::g_service_manager);
PCTL::InstallInterfaces(*SM::g_service_manager);
+ Sockets::InstallInterfaces(*SM::g_service_manager);
+ SPL::InstallInterfaces(*SM::g_service_manager);
+ SSL::InstallInterfaces(*SM::g_service_manager);
Time::InstallInterfaces(*SM::g_service_manager);
- VI::InstallInterfaces(*SM::g_service_manager);
+ VI::InstallInterfaces(*SM::g_service_manager, nv_flinger);
+ Set::InstallInterfaces(*SM::g_service_manager);
LOG_DEBUG(Service, "initialized OK");
}
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index 8e1c5b399..9c2e826da 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -21,7 +21,7 @@ class ClientPort;
class ServerPort;
class ServerSession;
class HLERequestContext;
-}
+} // namespace Kernel
namespace Service {
@@ -189,4 +189,4 @@ extern std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> g_
/// Adds a port to the named port table
void AddNamedPort(std::string name, Kernel::SharedPtr<Kernel::ClientPort> port);
-} // namespace
+} // namespace Service
diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp
new file mode 100644
index 000000000..aa7c924e7
--- /dev/null
+++ b/src/core/hle/service/set/set.cpp
@@ -0,0 +1,44 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <chrono>
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/client_port.h"
+#include "core/hle/kernel/client_session.h"
+#include "core/hle/service/set/set.h"
+
+namespace Service {
+namespace Set {
+
+void SET::GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ u32 id = rp.Pop<u32>();
+ constexpr std::array<u8, 13> lang_codes{};
+
+ ctx.WriteBuffer(lang_codes.data(), lang_codes.size());
+
+ IPC::ResponseBuilder rb{ctx, 2};
+
+ rb.Push(RESULT_SUCCESS);
+
+ LOG_WARNING(Service_SET, "(STUBBED) called");
+}
+
+SET::SET() : ServiceFramework("set") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetLanguageCode"},
+ {1, &SET::GetAvailableLanguageCodes, "GetAvailableLanguageCodes"},
+ {2, nullptr, "MakeLanguageCode"},
+ {3, nullptr, "GetAvailableLanguageCodeCount"},
+ {4, nullptr, "GetRegionCode"},
+ {5, nullptr, "GetAvailableLanguageCodes2"},
+ {6, nullptr, "GetAvailableLanguageCodeCount2"},
+ {7, nullptr, "GetKeyCodeMap"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Set
+} // namespace Service
diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h
new file mode 100644
index 000000000..7b7814ed1
--- /dev/null
+++ b/src/core/hle/service/set/set.h
@@ -0,0 +1,22 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace Set {
+
+class SET final : public ServiceFramework<SET> {
+public:
+ explicit SET();
+ ~SET() = default;
+
+private:
+ void GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx);
+};
+
+} // namespace Set
+} // namespace Service
diff --git a/src/core/hle/service/set/set_cal.cpp b/src/core/hle/service/set/set_cal.cpp
new file mode 100644
index 000000000..6231acd96
--- /dev/null
+++ b/src/core/hle/service/set/set_cal.cpp
@@ -0,0 +1,40 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/set/set_cal.h"
+
+namespace Service {
+namespace Set {
+
+SET_CAL::SET_CAL() : ServiceFramework("set:cal") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetBluetoothBdAddress"},
+ {1, nullptr, "GetConfigurationId1"},
+ {2, nullptr, "GetAccelerometerOffset"},
+ {3, nullptr, "GetAccelerometerScale"},
+ {4, nullptr, "GetGyroscopeOffset"},
+ {5, nullptr, "GetGyroscopeScale"},
+ {6, nullptr, "GetWirelessLanMacAddress"},
+ {7, nullptr, "GetWirelessLanCountryCodeCount"},
+ {8, nullptr, "GetWirelessLanCountryCodes"},
+ {9, nullptr, "GetSerialNumber"},
+ {10, nullptr, "SetInitialSystemAppletProgramId"},
+ {11, nullptr, "SetOverlayDispProgramId"},
+ {12, nullptr, "GetBatteryLot"},
+ {14, nullptr, "GetEciDeviceCertificate"},
+ {15, nullptr, "GetEticketDeviceCertificate"},
+ {16, nullptr, "GetSslKey"},
+ {17, nullptr, "GetSslCertificate"},
+ {18, nullptr, "GetGameCardKey"},
+ {19, nullptr, "GetGameCardCertificate"},
+ {20, nullptr, "GetEciDeviceKey"},
+ {21, nullptr, "GetEticketDeviceKey"},
+ {22, nullptr, "GetSpeakerParameter"},
+ {23, nullptr, "GetLcdVendorId"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Set
+} // namespace Service
diff --git a/src/core/hle/service/set/set_cal.h b/src/core/hle/service/set/set_cal.h
new file mode 100644
index 000000000..9c0b851d0
--- /dev/null
+++ b/src/core/hle/service/set/set_cal.h
@@ -0,0 +1,19 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace Set {
+
+class SET_CAL final : public ServiceFramework<SET_CAL> {
+public:
+ explicit SET_CAL();
+ ~SET_CAL() = default;
+};
+
+} // namespace Set
+} // namespace Service
diff --git a/src/core/hle/service/set/set_fd.cpp b/src/core/hle/service/set/set_fd.cpp
new file mode 100644
index 000000000..8320d4250
--- /dev/null
+++ b/src/core/hle/service/set/set_fd.cpp
@@ -0,0 +1,25 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/set/set_fd.h"
+
+namespace Service {
+namespace Set {
+
+SET_FD::SET_FD() : ServiceFramework("set:fd") {
+ static const FunctionInfo functions[] = {
+ {2, nullptr, "SetSettingsItemValue"},
+ {3, nullptr, "ResetSettingsItemValue"},
+ {4, nullptr, "CreateSettingsItemKeyIterator"},
+ {10, nullptr, "ReadSettings"},
+ {11, nullptr, "ResetSettings"},
+ {20, nullptr, "SetWebInspectorFlag"},
+ {21, nullptr, "SetAllowedSslHosts"},
+ {22, nullptr, "SetHostFsMountPoint"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Set
+} // namespace Service
diff --git a/src/core/hle/service/set/set_fd.h b/src/core/hle/service/set/set_fd.h
new file mode 100644
index 000000000..65b36bcb3
--- /dev/null
+++ b/src/core/hle/service/set/set_fd.h
@@ -0,0 +1,19 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace Set {
+
+class SET_FD final : public ServiceFramework<SET_FD> {
+public:
+ explicit SET_FD();
+ ~SET_FD() = default;
+};
+
+} // namespace Set
+} // namespace Service
diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp
new file mode 100644
index 000000000..363abd10a
--- /dev/null
+++ b/src/core/hle/service/set/set_sys.cpp
@@ -0,0 +1,167 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/client_port.h"
+#include "core/hle/service/set/set_sys.h"
+
+namespace Service {
+namespace Set {
+
+void SET_SYS::GetColorSetId(Kernel::HLERequestContext& ctx) {
+
+ IPC::ResponseBuilder rb{ctx, 3};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0);
+
+ LOG_WARNING(Service_SET, "(STUBBED) called");
+}
+
+SET_SYS::SET_SYS() : ServiceFramework("set:sys") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "SetLanguageCode"},
+ {1, nullptr, "SetNetworkSettings"},
+ {2, nullptr, "GetNetworkSettings"},
+ {3, nullptr, "GetFirmwareVersion"},
+ {4, nullptr, "GetFirmwareVersion2"},
+ {7, nullptr, "GetLockScreenFlag"},
+ {8, nullptr, "SetLockScreenFlag"},
+ {9, nullptr, "GetBacklightSettings"},
+ {10, nullptr, "SetBacklightSettings"},
+ {11, nullptr, "SetBluetoothDevicesSettings"},
+ {12, nullptr, "GetBluetoothDevicesSettings"},
+ {13, nullptr, "GetExternalSteadyClockSourceId"},
+ {14, nullptr, "SetExternalSteadyClockSourceId"},
+ {15, nullptr, "GetUserSystemClockContext"},
+ {16, nullptr, "SetUserSystemClockContext"},
+ {17, nullptr, "GetAccountSettings"},
+ {18, nullptr, "SetAccountSettings"},
+ {19, nullptr, "GetAudioVolume"},
+ {20, nullptr, "SetAudioVolume"},
+ {21, nullptr, "GetEulaVersions"},
+ {22, nullptr, "SetEulaVersions"},
+ {23, &SET_SYS::GetColorSetId, "GetColorSetId"},
+ {24, nullptr, "SetColorSetId"},
+ {25, nullptr, "GetConsoleInformationUploadFlag"},
+ {26, nullptr, "SetConsoleInformationUploadFlag"},
+ {27, nullptr, "GetAutomaticApplicationDownloadFlag"},
+ {28, nullptr, "SetAutomaticApplicationDownloadFlag"},
+ {29, nullptr, "GetNotificationSettings"},
+ {30, nullptr, "SetNotificationSettings"},
+ {31, nullptr, "GetAccountNotificationSettings"},
+ {32, nullptr, "SetAccountNotificationSettings"},
+ {35, nullptr, "GetVibrationMasterVolume"},
+ {36, nullptr, "SetVibrationMasterVolume"},
+ {37, nullptr, "GetSettingsItemValueSize"},
+ {38, nullptr, "GetSettingsItemValue"},
+ {39, nullptr, "GetTvSettings"},
+ {40, nullptr, "SetTvSettings"},
+ {41, nullptr, "GetEdid"},
+ {42, nullptr, "SetEdid"},
+ {43, nullptr, "GetAudioOutputMode"},
+ {44, nullptr, "SetAudioOutputMode"},
+ {45, nullptr, "IsForceMuteOnHeadphoneRemoved"},
+ {46, nullptr, "SetForceMuteOnHeadphoneRemoved"},
+ {47, nullptr, "GetQuestFlag"},
+ {48, nullptr, "SetQuestFlag"},
+ {49, nullptr, "GetDataDeletionSettings"},
+ {50, nullptr, "SetDataDeletionSettings"},
+ {51, nullptr, "GetInitialSystemAppletProgramId"},
+ {52, nullptr, "GetOverlayDispProgramId"},
+ {53, nullptr, "GetDeviceTimeZoneLocationName"},
+ {54, nullptr, "SetDeviceTimeZoneLocationName"},
+ {55, nullptr, "GetWirelessCertificationFileSize"},
+ {56, nullptr, "GetWirelessCertificationFile"},
+ {57, nullptr, "SetRegionCode"},
+ {58, nullptr, "GetNetworkSystemClockContext"},
+ {59, nullptr, "SetNetworkSystemClockContext"},
+ {60, nullptr, "IsUserSystemClockAutomaticCorrectionEnabled"},
+ {61, nullptr, "SetUserSystemClockAutomaticCorrectionEnabled"},
+ {62, nullptr, "GetDebugModeFlag"},
+ {63, nullptr, "GetPrimaryAlbumStorage"},
+ {64, nullptr, "SetPrimaryAlbumStorage"},
+ {65, nullptr, "GetUsb30EnableFlag"},
+ {66, nullptr, "SetUsb30EnableFlag"},
+ {67, nullptr, "GetBatteryLot"},
+ {68, nullptr, "GetSerialNumber"},
+ {69, nullptr, "GetNfcEnableFlag"},
+ {70, nullptr, "SetNfcEnableFlag"},
+ {71, nullptr, "GetSleepSettings"},
+ {72, nullptr, "SetSleepSettings"},
+ {73, nullptr, "GetWirelessLanEnableFlag"},
+ {74, nullptr, "SetWirelessLanEnableFlag"},
+ {75, nullptr, "GetInitialLaunchSettings"},
+ {76, nullptr, "SetInitialLaunchSettings"},
+ {77, nullptr, "GetDeviceNickName"},
+ {78, nullptr, "SetDeviceNickName"},
+ {79, nullptr, "GetProductModel"},
+ {80, nullptr, "GetLdnChannel"},
+ {81, nullptr, "SetLdnChannel"},
+ {82, nullptr, "AcquireTelemetryDirtyFlagEventHandle"},
+ {83, nullptr, "GetTelemetryDirtyFlags"},
+ {84, nullptr, "GetPtmBatteryLot"},
+ {85, nullptr, "SetPtmBatteryLot"},
+ {86, nullptr, "GetPtmFuelGaugeParameter"},
+ {87, nullptr, "SetPtmFuelGaugeParameter"},
+ {88, nullptr, "GetBluetoothEnableFlag"},
+ {89, nullptr, "SetBluetoothEnableFlag"},
+ {90, nullptr, "GetMiiAuthorId"},
+ {91, nullptr, "SetShutdownRtcValue"},
+ {92, nullptr, "GetShutdownRtcValue"},
+ {93, nullptr, "AcquireFatalDirtyFlagEventHandle"},
+ {94, nullptr, "GetFatalDirtyFlags"},
+ {95, nullptr, "GetAutoUpdateEnableFlag"},
+ {96, nullptr, "SetAutoUpdateEnableFlag"},
+ {97, nullptr, "GetNxControllerSettings"},
+ {98, nullptr, "SetNxControllerSettings"},
+ {99, nullptr, "GetBatteryPercentageFlag"},
+ {100, nullptr, "SetBatteryPercentageFlag"},
+ {101, nullptr, "GetExternalRtcResetFlag"},
+ {102, nullptr, "SetExternalRtcResetFlag"},
+ {103, nullptr, "GetUsbFullKeyEnableFlag"},
+ {104, nullptr, "SetUsbFullKeyEnableFlag"},
+ {105, nullptr, "SetExternalSteadyClockInternalOffset"},
+ {106, nullptr, "GetExternalSteadyClockInternalOffset"},
+ {107, nullptr, "GetBacklightSettingsEx"},
+ {108, nullptr, "SetBacklightSettingsEx"},
+ {109, nullptr, "GetHeadphoneVolumeWarningCount"},
+ {110, nullptr, "SetHeadphoneVolumeWarningCount"},
+ {111, nullptr, "GetBluetoothAfhEnableFlag"},
+ {112, nullptr, "SetBluetoothAfhEnableFlag"},
+ {113, nullptr, "GetBluetoothBoostEnableFlag"},
+ {114, nullptr, "SetBluetoothBoostEnableFlag"},
+ {115, nullptr, "GetInRepairProcessEnableFlag"},
+ {116, nullptr, "SetInRepairProcessEnableFlag"},
+ {117, nullptr, "GetHeadphoneVolumeUpdateFlag"},
+ {118, nullptr, "SetHeadphoneVolumeUpdateFlag"},
+ {119, nullptr, "NeedsToUpdateHeadphoneVolume"},
+ {120, nullptr, "GetPushNotificationActivityModeOnSleep"},
+ {121, nullptr, "SetPushNotificationActivityModeOnSleep"},
+ {122, nullptr, "GetServiceDiscoveryControlSettings"},
+ {123, nullptr, "SetServiceDiscoveryControlSettings"},
+ {124, nullptr, "GetErrorReportSharePermission"},
+ {125, nullptr, "SetErrorReportSharePermission"},
+ {126, nullptr, "GetAppletLaunchFlags"},
+ {127, nullptr, "SetAppletLaunchFlags"},
+ {128, nullptr, "GetConsoleSixAxisSensorAccelerationBias"},
+ {129, nullptr, "SetConsoleSixAxisSensorAccelerationBias"},
+ {130, nullptr, "GetConsoleSixAxisSensorAngularVelocityBias"},
+ {131, nullptr, "SetConsoleSixAxisSensorAngularVelocityBias"},
+ {132, nullptr, "GetConsoleSixAxisSensorAccelerationGain"},
+ {133, nullptr, "SetConsoleSixAxisSensorAccelerationGain"},
+ {134, nullptr, "GetConsoleSixAxisSensorAngularVelocityGain"},
+ {135, nullptr, "SetConsoleSixAxisSensorAngularVelocityGain"},
+ {136, nullptr, "GetKeyboardLayout"},
+ {137, nullptr, "SetKeyboardLayout"},
+ {138, nullptr, "GetWebInspectorFlag"},
+ {139, nullptr, "GetAllowedSslHosts"},
+ {140, nullptr, "GetHostFsMountPoint"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Set
+} // namespace Service
diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h
new file mode 100644
index 000000000..105f1a3c7
--- /dev/null
+++ b/src/core/hle/service/set/set_sys.h
@@ -0,0 +1,22 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace Set {
+
+class SET_SYS final : public ServiceFramework<SET_SYS> {
+public:
+ explicit SET_SYS();
+ ~SET_SYS() = default;
+
+private:
+ void GetColorSetId(Kernel::HLERequestContext& ctx);
+};
+
+} // namespace Set
+} // namespace Service
diff --git a/src/core/hle/service/set/settings.cpp b/src/core/hle/service/set/settings.cpp
new file mode 100644
index 000000000..c6bc9e240
--- /dev/null
+++ b/src/core/hle/service/set/settings.cpp
@@ -0,0 +1,22 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/set/set.h"
+#include "core/hle/service/set/set_cal.h"
+#include "core/hle/service/set/set_fd.h"
+#include "core/hle/service/set/set_sys.h"
+#include "core/hle/service/set/settings.h"
+
+namespace Service {
+namespace Set {
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ std::make_shared<SET>()->InstallAsService(service_manager);
+ std::make_shared<SET_CAL>()->InstallAsService(service_manager);
+ std::make_shared<SET_FD>()->InstallAsService(service_manager);
+ std::make_shared<SET_SYS>()->InstallAsService(service_manager);
+}
+
+} // namespace Set
+} // namespace Service
diff --git a/src/core/hle/service/set/settings.h b/src/core/hle/service/set/settings.h
new file mode 100644
index 000000000..6c8d5a58c
--- /dev/null
+++ b/src/core/hle/service/set/settings.h
@@ -0,0 +1,16 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace Set {
+
+/// Registers all Settings services with the specified service manager.
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace Set
+} // namespace Service
diff --git a/src/core/hle/service/sm/controller.cpp b/src/core/hle/service/sm/controller.cpp
index e8f5d0e4a..e12c53442 100644
--- a/src/core/hle/service/sm/controller.cpp
+++ b/src/core/hle/service/sm/controller.cpp
@@ -4,36 +4,43 @@
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
-#include "core/hle/kernel/domain.h"
+#include "core/hle/kernel/session.h"
#include "core/hle/service/sm/controller.h"
namespace Service {
namespace SM {
void Controller::ConvertSessionToDomain(Kernel::HLERequestContext& ctx) {
- auto domain = Kernel::Domain::CreateFromSession(*ctx.ServerSession()->parent).Unwrap();
+ ASSERT_MSG(!ctx.Session()->IsDomain(), "session is alread a domain");
+ ctx.Session()->ConvertToDomain();
- IPC::RequestBuilder rb{ctx, 3};
+ IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push(static_cast<u32>(domain->request_handlers.size()));
+ rb.Push<u32>(1); // Converted sessions start with 1 request handler
- LOG_DEBUG(Service, "called, domain=%d", domain->GetObjectId());
+ LOG_DEBUG(Service, "called, server_session=%d", ctx.Session()->GetObjectId());
}
void Controller::DuplicateSession(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 2, 0, 1};
+ // TODO(bunnei): This is just creating a new handle to the same Session. I assume this is wrong
+ // and that we probably want to actually make an entirely new Session, but we still need to
+ // verify this on hardware.
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
rb.Push(RESULT_SUCCESS);
- // TODO(Subv): Check if this is correct
- if (ctx.IsDomain())
- rb.PushMoveObjects(ctx.Domain());
- else
- rb.PushMoveObjects(ctx.ServerSession());
+ Kernel::SharedPtr<Kernel::ClientSession> session{ctx.Session()->parent->client};
+ rb.PushMoveObjects(session);
- LOG_DEBUG(Service, "called");
+ LOG_DEBUG(Service, "called, session=%u", session->GetObjectId());
+}
+
+void Controller::DuplicateSessionEx(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service, "(STUBBED) called, using DuplicateSession");
+
+ DuplicateSession(ctx);
}
void Controller::QueryPointerBufferSize(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 3};
+ IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(0x500);
@@ -46,7 +53,7 @@ Controller::Controller() : ServiceFramework("IpcController") {
{0x00000001, nullptr, "ConvertDomainToSession"},
{0x00000002, &Controller::DuplicateSession, "DuplicateSession"},
{0x00000003, &Controller::QueryPointerBufferSize, "QueryPointerBufferSize"},
- {0x00000004, nullptr, "DuplicateSessionEx"},
+ {0x00000004, &Controller::DuplicateSessionEx, "DuplicateSessionEx"},
};
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/sm/controller.h b/src/core/hle/service/sm/controller.h
index 0b873b492..7b4bc4b75 100644
--- a/src/core/hle/service/sm/controller.h
+++ b/src/core/hle/service/sm/controller.h
@@ -17,6 +17,7 @@ public:
private:
void ConvertSessionToDomain(Kernel::HLERequestContext& ctx);
void DuplicateSession(Kernel::HLERequestContext& ctx);
+ void DuplicateSessionEx(Kernel::HLERequestContext& ctx);
void QueryPointerBufferSize(Kernel::HLERequestContext& ctx);
};
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index f3bffac54..bc72512a0 100644
--- a/src/core/hle/service/sm/sm.cpp
+++ b/src/core/hle/service/sm/sm.cpp
@@ -83,7 +83,7 @@ std::shared_ptr<ServiceManager> g_service_manager;
* 0: ResultCode
*/
void SM::Initialize(Kernel::HLERequestContext& ctx) {
- IPC::RequestBuilder rb{ctx, 1};
+ IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_SM, "called");
}
@@ -99,10 +99,13 @@ void SM::GetService(Kernel::HLERequestContext& ctx) {
auto client_port = service_manager->GetServicePort(name);
if (client_port.Failed()) {
- IPC::RequestBuilder rb = rp.MakeBuilder(2, 0, 0, 0);
+ IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
rb.Push(client_port.Code());
LOG_ERROR(Service_SM, "called service=%s -> error 0x%08X", name.c_str(),
client_port.Code().raw);
+ if (name.length() == 0)
+ return; // LibNX Fix
+ UNIMPLEMENTED();
return;
}
@@ -111,7 +114,8 @@ void SM::GetService(Kernel::HLERequestContext& ctx) {
if (session.Succeeded()) {
LOG_DEBUG(Service_SM, "called service=%s -> session=%u", name.c_str(),
(*session)->GetObjectId());
- IPC::RequestBuilder rb = rp.MakeBuilder(2, 0, 1, 0);
+ IPC::ResponseBuilder rb =
+ rp.MakeBuilder(2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles);
rb.Push(session.Code());
rb.PushMoveObjects(std::move(session).Unwrap());
}
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
new file mode 100644
index 000000000..790ff82b3
--- /dev/null
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -0,0 +1,90 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/service/sockets/bsd.h"
+
+namespace Service {
+namespace Sockets {
+
+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
+}
+
+void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0); // bsd errno
+}
+
+void BSD::Socket(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ u32 domain = rp.Pop<u32>();
+ u32 type = rp.Pop<u32>();
+ u32 protocol = rp.Pop<u32>();
+
+ LOG_WARNING(Service, "(STUBBED) called domain=%u type=%u protocol=%u", domain, type, protocol);
+
+ u32 fd = next_fd++;
+
+ IPC::ResponseBuilder rb{ctx, 4};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(fd);
+ rb.Push<u32>(0); // bsd errno
+}
+
+void BSD::Connect(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0); // ret
+ rb.Push<u32>(0); // bsd errno
+}
+
+void BSD::SendTo(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0); // ret
+ rb.Push<u32>(0); // bsd errno
+}
+
+void BSD::Close(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0); // ret
+ rb.Push<u32>(0); // bsd errno
+}
+
+BSD::BSD(const char* name) : ServiceFramework(name) {
+ static const FunctionInfo functions[] = {
+ {0, &BSD::RegisterClient, "RegisterClient"},
+ {1, &BSD::StartMonitoring, "StartMonitoring"},
+ {2, &BSD::Socket, "Socket"},
+ {11, &BSD::SendTo, "SendTo"},
+ {14, &BSD::Connect, "Connect"},
+ {26, &BSD::Close, "Close"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Sockets
+} // namespace Service
diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h
new file mode 100644
index 000000000..32d949e95
--- /dev/null
+++ b/src/core/hle/service/sockets/bsd.h
@@ -0,0 +1,31 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace Sockets {
+
+class BSD final : public ServiceFramework<BSD> {
+public:
+ explicit BSD(const char* name);
+ ~BSD() = default;
+
+private:
+ void RegisterClient(Kernel::HLERequestContext& ctx);
+ void StartMonitoring(Kernel::HLERequestContext& ctx);
+ void Socket(Kernel::HLERequestContext& ctx);
+ void Connect(Kernel::HLERequestContext& ctx);
+ void SendTo(Kernel::HLERequestContext& ctx);
+ void Close(Kernel::HLERequestContext& ctx);
+
+ /// Id to use for the next open file descriptor.
+ u32 next_fd = 1;
+};
+
+} // namespace Sockets
+} // namespace Service
diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp
new file mode 100644
index 000000000..e3542d325
--- /dev/null
+++ b/src/core/hle/service/sockets/nsd.cpp
@@ -0,0 +1,34 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/sockets/nsd.h"
+
+namespace Service {
+namespace Sockets {
+
+NSD::NSD(const char* name) : ServiceFramework(name) {
+ static const FunctionInfo functions[] = {
+ {10, nullptr, "GetSettingName"},
+ {11, nullptr, "GetEnvironmentIdentifier"},
+ {12, nullptr, "GetDeviceId"},
+ {13, nullptr, "DeleteSettings"},
+ {14, nullptr, "ImportSettings"},
+ {20, nullptr, "Resolve"},
+ {21, nullptr, "ResolveEx"},
+ {30, nullptr, "GetNasServiceSetting"},
+ {31, nullptr, "GetNasServiceSettingEx"},
+ {40, nullptr, "GetNasRequestFqdn"},
+ {41, nullptr, "GetNasRequestFqdnEx"},
+ {42, nullptr, "GetNasApiFqdn"},
+ {43, nullptr, "GetNasApiFqdnEx"},
+ {50, nullptr, "GetCurrentSetting"},
+ {60, nullptr, "ReadSaveDataFromFsForTest"},
+ {61, nullptr, "WriteSaveDataToFsForTest"},
+ {62, nullptr, "DeleteSaveDataOfFsForTest"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Sockets
+} // namespace Service
diff --git a/src/core/hle/service/sockets/nsd.h b/src/core/hle/service/sockets/nsd.h
new file mode 100644
index 000000000..a7c15a860
--- /dev/null
+++ b/src/core/hle/service/sockets/nsd.h
@@ -0,0 +1,20 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace Sockets {
+
+class NSD final : public ServiceFramework<NSD> {
+public:
+ explicit NSD(const char* name);
+ ~NSD() = default;
+};
+
+} // namespace Sockets
+} // namespace Service
diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp
new file mode 100644
index 000000000..eb4b5fa57
--- /dev/null
+++ b/src/core/hle/service/sockets/sfdnsres.cpp
@@ -0,0 +1,38 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/service/sockets/sfdnsres.h"
+
+namespace Service {
+namespace Sockets {
+
+void SFDNSRES::GetAddrInfo(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ LOG_WARNING(Service, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+
+ rb.Push(RESULT_SUCCESS);
+}
+
+SFDNSRES::SFDNSRES() : ServiceFramework("sfdnsres") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "SetDnsAddressesPrivate"},
+ {1, nullptr, "GetDnsAddressPrivate"},
+ {2, nullptr, "GetHostByName"},
+ {3, nullptr, "GetHostByAddr"},
+ {4, nullptr, "GetHostStringError"},
+ {5, nullptr, "GetGaiStringError"},
+ {6, &SFDNSRES::GetAddrInfo, "GetAddrInfo"},
+ {7, nullptr, "GetNameInfo"},
+ {8, nullptr, "RequestCancelHandle"},
+ {9, nullptr, "CancelSocketCall"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Sockets
+} // namespace Service
diff --git a/src/core/hle/service/sockets/sfdnsres.h b/src/core/hle/service/sockets/sfdnsres.h
new file mode 100644
index 000000000..c07cc1594
--- /dev/null
+++ b/src/core/hle/service/sockets/sfdnsres.h
@@ -0,0 +1,23 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace Sockets {
+
+class SFDNSRES final : public ServiceFramework<SFDNSRES> {
+public:
+ explicit SFDNSRES();
+ ~SFDNSRES() = default;
+
+private:
+ void GetAddrInfo(Kernel::HLERequestContext& ctx);
+};
+
+} // namespace Sockets
+} // namespace Service
diff --git a/src/core/hle/service/sockets/sockets.cpp b/src/core/hle/service/sockets/sockets.cpp
new file mode 100644
index 000000000..cedc276d9
--- /dev/null
+++ b/src/core/hle/service/sockets/sockets.cpp
@@ -0,0 +1,22 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/sockets/bsd.h"
+#include "core/hle/service/sockets/nsd.h"
+#include "core/hle/service/sockets/sfdnsres.h"
+#include "core/hle/service/sockets/sockets.h"
+
+namespace Service {
+namespace 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);
+ std::make_shared<NSD>("nsd:a")->InstallAsService(service_manager);
+ std::make_shared<NSD>("nsd:u")->InstallAsService(service_manager);
+ std::make_shared<SFDNSRES>()->InstallAsService(service_manager);
+}
+
+} // namespace Sockets
+} // namespace Service
diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h
new file mode 100644
index 000000000..7e89c8d2c
--- /dev/null
+++ b/src/core/hle/service/sockets/sockets.h
@@ -0,0 +1,16 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace Sockets {
+
+/// Registers all Sockets services with the specified service manager.
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace Sockets
+} // namespace Service
diff --git a/src/core/hle/service/spl/csrng.cpp b/src/core/hle/service/spl/csrng.cpp
new file mode 100644
index 000000000..cde05717a
--- /dev/null
+++ b/src/core/hle/service/spl/csrng.cpp
@@ -0,0 +1,18 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/spl/csrng.h"
+
+namespace Service {
+namespace SPL {
+
+CSRNG::CSRNG(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "csrng") {
+ static const FunctionInfo functions[] = {
+ {0, &CSRNG::GetRandomBytes, "GetRandomBytes"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace SPL
+} // namespace Service
diff --git a/src/core/hle/service/spl/csrng.h b/src/core/hle/service/spl/csrng.h
new file mode 100644
index 000000000..59ca794dd
--- /dev/null
+++ b/src/core/hle/service/spl/csrng.h
@@ -0,0 +1,18 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/spl/module.h"
+
+namespace Service {
+namespace SPL {
+
+class CSRNG final : public Module::Interface {
+public:
+ explicit CSRNG(std::shared_ptr<Module> module);
+};
+
+} // namespace SPL
+} // namespace Service
diff --git a/src/core/hle/service/spl/module.cpp b/src/core/hle/service/spl/module.cpp
new file mode 100644
index 000000000..fc1bcd94c
--- /dev/null
+++ b/src/core/hle/service/spl/module.cpp
@@ -0,0 +1,42 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <cstdlib>
+#include <vector>
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/service/spl/csrng.h"
+#include "core/hle/service/spl/module.h"
+#include "core/hle/service/spl/spl.h"
+
+namespace Service {
+namespace SPL {
+
+Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
+ : ServiceFramework(name), module(std::move(module)) {}
+
+void Module::Interface::GetRandomBytes(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ size_t size = ctx.GetWriteBufferSize();
+
+ std::vector<u8> data(size);
+ std::generate(data.begin(), data.end(), std::rand);
+
+ ctx.WriteBuffer(data);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_DEBUG(Service_SPL, "called");
+}
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ auto module = std::make_shared<Module>();
+ std::make_shared<CSRNG>(module)->InstallAsService(service_manager);
+ std::make_shared<SPL>(module)->InstallAsService(service_manager);
+}
+
+} // namespace SPL
+} // namespace Service
diff --git a/src/core/hle/service/spl/module.h b/src/core/hle/service/spl/module.h
new file mode 100644
index 000000000..12cdb2980
--- /dev/null
+++ b/src/core/hle/service/spl/module.h
@@ -0,0 +1,29 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace SPL {
+
+class Module final {
+public:
+ class Interface : public ServiceFramework<Interface> {
+ public:
+ Interface(std::shared_ptr<Module> module, const char* name);
+
+ void GetRandomBytes(Kernel::HLERequestContext& ctx);
+
+ protected:
+ std::shared_ptr<Module> module;
+ };
+};
+
+/// Registers all SPL services with the specified service manager.
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace SPL
+} // namespace Service
diff --git a/src/core/hle/service/spl/spl.cpp b/src/core/hle/service/spl/spl.cpp
new file mode 100644
index 000000000..deab29b91
--- /dev/null
+++ b/src/core/hle/service/spl/spl.cpp
@@ -0,0 +1,41 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/spl/spl.h"
+
+namespace Service {
+namespace SPL {
+
+SPL::SPL(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "spl:") {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetConfig"},
+ {1, nullptr, "UserExpMod"},
+ {2, nullptr, "GenerateAesKek"},
+ {3, nullptr, "LoadAesKey"},
+ {4, nullptr, "GenerateAesKey"},
+ {5, nullptr, "SetConfig"},
+ {7, &SPL::GetRandomBytes, "GetRandomBytes"},
+ {9, nullptr, "LoadSecureExpModKey"},
+ {10, nullptr, "SecureExpMod"},
+ {11, nullptr, "IsDevelopment"},
+ {12, nullptr, "GenerateSpecificAesKey"},
+ {13, nullptr, "DecryptPrivk"},
+ {14, nullptr, "DecryptAesKey"},
+ {15, nullptr, "DecryptAesCtr"},
+ {16, nullptr, "ComputeCmac"},
+ {17, nullptr, "LoadRsaOaepKey"},
+ {18, nullptr, "UnwrapRsaOaepWrappedTitleKey"},
+ {19, nullptr, "LoadTitleKey"},
+ {20, nullptr, "UnwrapAesWrappedTitleKey"},
+ {21, nullptr, "LockAesEngine"},
+ {22, nullptr, "UnlockAesEngine"},
+ {23, nullptr, "GetSplWaitEvent"},
+ {24, nullptr, "SetSharedData"},
+ {25, nullptr, "GetSharedData"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace SPL
+} // namespace Service
diff --git a/src/core/hle/service/spl/spl.h b/src/core/hle/service/spl/spl.h
new file mode 100644
index 000000000..9fd6059af
--- /dev/null
+++ b/src/core/hle/service/spl/spl.h
@@ -0,0 +1,18 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/spl/module.h"
+
+namespace Service {
+namespace SPL {
+
+class SPL final : public Module::Interface {
+public:
+ explicit SPL(std::shared_ptr<Module> module);
+};
+
+} // namespace SPL
+} // namespace Service
diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp
new file mode 100644
index 000000000..afa8d5d79
--- /dev/null
+++ b/src/core/hle/service/ssl/ssl.cpp
@@ -0,0 +1,17 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/ssl/ssl.h"
+
+namespace Service {
+namespace SSL {
+
+SSL::SSL() : ServiceFramework("ssl") {}
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ std::make_shared<SSL>()->InstallAsService(service_manager);
+}
+
+} // namespace SSL
+} // namespace Service
diff --git a/src/core/hle/service/ssl/ssl.h b/src/core/hle/service/ssl/ssl.h
new file mode 100644
index 000000000..645dad003
--- /dev/null
+++ b/src/core/hle/service/ssl/ssl.h
@@ -0,0 +1,22 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace SSL {
+
+class SSL final : public ServiceFramework<SSL> {
+public:
+ explicit SSL();
+ ~SSL() = default;
+};
+
+/// Registers all SSL services with the specified service manager.
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace SSL
+} // namespace Service
diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp
index e3d58aa60..c3e46f866 100644
--- a/src/core/hle/service/time/time.cpp
+++ b/src/core/hle/service/time/time.cpp
@@ -2,14 +2,164 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <chrono>
+#include "common/logging/log.h"
+#include "core/core_timing.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/client_port.h"
+#include "core/hle/kernel/client_session.h"
#include "core/hle/service/time/time.h"
#include "core/hle/service/time/time_s.h"
+#include "core/hle/service/time/time_u.h"
namespace Service {
namespace Time {
+class ISystemClock final : public ServiceFramework<ISystemClock> {
+public:
+ ISystemClock() : ServiceFramework("ISystemClock") {
+ static const FunctionInfo functions[] = {
+ {0, &ISystemClock::GetCurrentTime, "GetCurrentTime"},
+ {2, &ISystemClock::GetSystemClockContext, "GetSystemClockContext"}};
+ RegisterHandlers(functions);
+ }
+
+private:
+ void GetCurrentTime(Kernel::HLERequestContext& ctx) {
+ const s64 time_since_epoch{std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count()};
+ LOG_DEBUG(Service_Time, "called");
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(time_since_epoch);
+ }
+
+ void GetSystemClockContext(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_Time, "(STUBBED) called");
+ SystemClockContext system_clock_ontext{};
+ IPC::ResponseBuilder rb{ctx, (sizeof(SystemClockContext) / 4) + 2};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(system_clock_ontext);
+ }
+};
+
+class ISteadyClock final : public ServiceFramework<ISteadyClock> {
+public:
+ ISteadyClock() : ServiceFramework("ISteadyClock") {
+ static const FunctionInfo functions[] = {
+ {0, &ISteadyClock::GetCurrentTimePoint, "GetCurrentTimePoint"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ void GetCurrentTimePoint(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called");
+ SteadyClockTimePoint steady_clock_time_point{cyclesToMs(CoreTiming::GetTicks()) / 1000};
+ IPC::ResponseBuilder rb{ctx, (sizeof(SteadyClockTimePoint) / 4) + 2};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(steady_clock_time_point);
+ }
+};
+
+class ITimeZoneService final : public ServiceFramework<ITimeZoneService> {
+public:
+ ITimeZoneService() : ServiceFramework("ITimeZoneService") {
+ static const FunctionInfo functions[] = {
+ {0, &ITimeZoneService::GetDeviceLocationName, "GetDeviceLocationName"},
+ {1, nullptr, "SetDeviceLocationName"},
+ {2, &ITimeZoneService::GetTotalLocationNameCount, "GetTotalLocationNameCount"},
+ {3, nullptr, "LoadLocationNameList"},
+ {4, &ITimeZoneService::LoadTimeZoneRule, "LoadTimeZoneRule"},
+ {5, nullptr, "GetTimeZoneRuleVersion"},
+ {100, nullptr, "ToCalendarTime"},
+ {101, &ITimeZoneService::ToCalendarTimeWithMyRule, "ToCalendarTimeWithMyRule"},
+ {200, nullptr, "ToPosixTime"},
+ {201, nullptr, "ToPosixTimeWithMyRule"},
+ };
+ RegisterHandlers(functions);
+ }
+
+private:
+ void GetDeviceLocationName(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_Time, "(STUBBED) called");
+ LocationName location_name{};
+ IPC::ResponseBuilder rb{ctx, (sizeof(LocationName) / 4) + 2};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(location_name);
+ }
+
+ void GetTotalLocationNameCount(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_Time, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0);
+ }
+
+ void LoadTimeZoneRule(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_Time, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void ToCalendarTimeWithMyRule(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ u64 posix_time = rp.Pop<u64>();
+
+ LOG_WARNING(Service_Time, "(STUBBED) called, posix_time=0x%016lX", posix_time);
+
+ CalendarTime calendar_time{2018, 1, 1, 0, 0, 0};
+ CalendarAdditionalInfo additional_info{};
+ IPC::ResponseBuilder rb{ctx, 10};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(calendar_time);
+ rb.PushRaw(additional_info);
+ }
+};
+
+void Module::Interface::GetStandardUserSystemClock(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<ISystemClock>();
+ LOG_DEBUG(Service_Time, "called");
+}
+
+void Module::Interface::GetStandardNetworkSystemClock(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<ISystemClock>();
+ LOG_DEBUG(Service_Time, "called");
+}
+
+void Module::Interface::GetStandardSteadyClock(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<ISteadyClock>();
+ LOG_DEBUG(Service_Time, "called");
+}
+
+void Module::Interface::GetTimeZoneService(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<ITimeZoneService>();
+ LOG_DEBUG(Service_Time, "called");
+}
+
+void Module::Interface::GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<ISystemClock>();
+ LOG_DEBUG(Service_Time, "called");
+}
+
+Module::Interface::Interface(std::shared_ptr<Module> time, const char* name)
+ : ServiceFramework(name), time(std::move(time)) {}
+
void InstallInterfaces(SM::ServiceManager& service_manager) {
- std::make_shared<TimeS>()->InstallAsService(service_manager);
+ auto time = std::make_shared<Module>();
+ std::make_shared<TIME_S>(time)->InstallAsService(service_manager);
+ std::make_shared<TIME_U>(time)->InstallAsService(service_manager);
}
} // namespace Time
diff --git a/src/core/hle/service/time/time.h b/src/core/hle/service/time/time.h
index 7d0803e24..197029e7a 100644
--- a/src/core/hle/service/time/time.h
+++ b/src/core/hle/service/time/time.h
@@ -9,6 +9,60 @@
namespace Service {
namespace Time {
+// TODO(Rozelette) RE this structure
+struct LocationName {
+ INSERT_PADDING_BYTES(0x24);
+};
+static_assert(sizeof(LocationName) == 0x24, "LocationName is incorrect size");
+
+struct CalendarTime {
+ u16_le year;
+ u8 month; // Starts at 1
+ u8 day; // Starts at 1
+ u8 hour;
+ u8 minute;
+ u8 second;
+ INSERT_PADDING_BYTES(1);
+};
+static_assert(sizeof(CalendarTime) == 0x8, "CalendarTime structure has incorrect size");
+
+// TODO(Rozelette) RE this structure
+struct CalendarAdditionalInfo {
+ INSERT_PADDING_BYTES(0x18);
+};
+static_assert(sizeof(CalendarAdditionalInfo) == 0x18,
+ "CalendarAdditionalInfo structure has incorrect size");
+
+// TODO(bunnei) RE this structure
+struct SystemClockContext {
+ INSERT_PADDING_BYTES(0x20);
+};
+static_assert(sizeof(SystemClockContext) == 0x20,
+ "SystemClockContext structure has incorrect size");
+
+struct SteadyClockTimePoint {
+ u64 value;
+ INSERT_PADDING_WORDS(4);
+};
+static_assert(sizeof(SteadyClockTimePoint) == 0x18, "SteadyClockTimePoint is incorrect size");
+
+class Module final {
+public:
+ class Interface : public ServiceFramework<Interface> {
+ public:
+ Interface(std::shared_ptr<Module> time, const char* name);
+
+ void GetStandardUserSystemClock(Kernel::HLERequestContext& ctx);
+ void GetStandardNetworkSystemClock(Kernel::HLERequestContext& ctx);
+ void GetStandardSteadyClock(Kernel::HLERequestContext& ctx);
+ void GetTimeZoneService(Kernel::HLERequestContext& ctx);
+ void GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx);
+
+ protected:
+ std::shared_ptr<Module> time;
+ };
+};
+
/// Registers all Time services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager);
diff --git a/src/core/hle/service/time/time_s.cpp b/src/core/hle/service/time/time_s.cpp
index 6b0597d8e..b172b2bd6 100644
--- a/src/core/hle/service/time/time_s.cpp
+++ b/src/core/hle/service/time/time_s.cpp
@@ -2,54 +2,18 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <chrono>
-#include "common/logging/log.h"
-#include "core/hle/ipc_helpers.h"
-#include "core/hle/kernel/client_port.h"
-#include "core/hle/kernel/client_session.h"
#include "core/hle/service/time/time_s.h"
namespace Service {
namespace Time {
-class ISystemClock final : public ServiceFramework<ISystemClock> {
-public:
- ISystemClock() : ServiceFramework("ISystemClock") {
- static const FunctionInfo functions[] = {
- {0, &ISystemClock::GetCurrentTime, "GetCurrentTime"},
- };
- RegisterHandlers(functions);
- }
-
-private:
- void GetCurrentTime(Kernel::HLERequestContext& ctx) {
- const s64 time_since_epoch{std::chrono::duration_cast<std::chrono::milliseconds>(
- std::chrono::system_clock::now().time_since_epoch())
- .count()};
- IPC::RequestBuilder rb{ctx, 4};
- rb.Push(RESULT_SUCCESS);
- rb.Push<u64>(time_since_epoch);
- LOG_DEBUG(Service, "called");
- }
-};
-
-void TimeS::GetStandardUserSystemClock(Kernel::HLERequestContext& ctx) {
- auto client_port = std::make_shared<ISystemClock>()->CreatePort();
- auto session = client_port->Connect();
- if (session.Succeeded()) {
- LOG_DEBUG(Service, "called, initialized ISystemClock -> session=%u",
- (*session)->GetObjectId());
- IPC::RequestBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushMoveObjects(std::move(session).Unwrap());
- } else {
- UNIMPLEMENTED();
- }
-}
-
-TimeS::TimeS() : ServiceFramework("time:s") {
+TIME_S::TIME_S(std::shared_ptr<Module> time) : Module::Interface(std::move(time), "time:s") {
static const FunctionInfo functions[] = {
- {0x00000000, &TimeS::GetStandardUserSystemClock, "GetStandardUserSystemClock"},
+ {0, &TIME_S::GetStandardUserSystemClock, "GetStandardUserSystemClock"},
+ {1, &TIME_S::GetStandardNetworkSystemClock, "GetStandardNetworkSystemClock"},
+ {2, &TIME_S::GetStandardSteadyClock, "GetStandardSteadyClock"},
+ {3, &TIME_S::GetTimeZoneService, "GetTimeZoneService"},
+ {4, &TIME_S::GetStandardLocalSystemClock, "GetStandardLocalSystemClock"},
};
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/time/time_s.h b/src/core/hle/service/time/time_s.h
index 073227910..abc2a8c5a 100644
--- a/src/core/hle/service/time/time_s.h
+++ b/src/core/hle/service/time/time_s.h
@@ -4,19 +4,14 @@
#pragma once
-#include "core/hle/kernel/hle_ipc.h"
-#include "core/hle/service/service.h"
+#include "core/hle/service/time/time.h"
namespace Service {
namespace Time {
-class TimeS final : public ServiceFramework<TimeS> {
+class TIME_S final : public Module::Interface {
public:
- TimeS();
- ~TimeS() = default;
-
-private:
- void GetStandardUserSystemClock(Kernel::HLERequestContext& ctx);
+ explicit TIME_S(std::shared_ptr<Module> time);
};
} // namespace Time
diff --git a/src/core/hle/service/time/time_u.cpp b/src/core/hle/service/time/time_u.cpp
new file mode 100644
index 000000000..fc1ace325
--- /dev/null
+++ b/src/core/hle/service/time/time_u.cpp
@@ -0,0 +1,22 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/time/time_u.h"
+
+namespace Service {
+namespace Time {
+
+TIME_U::TIME_U(std::shared_ptr<Module> time) : Module::Interface(std::move(time), "time:u") {
+ static const FunctionInfo functions[] = {
+ {0, &TIME_U::GetStandardUserSystemClock, "GetStandardUserSystemClock"},
+ {1, &TIME_U::GetStandardNetworkSystemClock, "GetStandardNetworkSystemClock"},
+ {2, &TIME_U::GetStandardSteadyClock, "GetStandardSteadyClock"},
+ {3, &TIME_U::GetTimeZoneService, "GetTimeZoneService"},
+ {4, &TIME_U::GetStandardLocalSystemClock, "GetStandardLocalSystemClock"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace Time
+} // namespace Service
diff --git a/src/core/hle/service/time/time_u.h b/src/core/hle/service/time/time_u.h
new file mode 100644
index 000000000..f99d25057
--- /dev/null
+++ b/src/core/hle/service/time/time_u.h
@@ -0,0 +1,18 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/time/time.h"
+
+namespace Service {
+namespace Time {
+
+class TIME_U final : public Module::Interface {
+public:
+ explicit TIME_U(std::shared_ptr<Module> time);
+};
+
+} // namespace Time
+} // namespace Service
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index cae2c4466..06c34e979 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -3,34 +3,46 @@
// Refer to the license.txt file included.
#include <algorithm>
-
+#include <array>
+#include <memory>
+#include <boost/optional.hpp>
#include "common/alignment.h"
#include "common/scope_exit.h"
#include "core/core_timing.h"
#include "core/hle/ipc_helpers.h"
-#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
+#include "core/hle/kernel/event.h"
#include "core/hle/service/nvdrv/nvdrv.h"
+#include "core/hle/service/nvflinger/buffer_queue.h"
#include "core/hle/service/vi/vi.h"
#include "core/hle/service/vi/vi_m.h"
+#include "core/hle/service/vi/vi_s.h"
+#include "core/hle/service/vi/vi_u.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
namespace Service {
namespace VI {
-constexpr size_t SCREEN_REFRESH_RATE = 60;
-constexpr u64 frame_ticks = static_cast<u64>(BASE_CLOCK_RATE / SCREEN_REFRESH_RATE);
+struct DisplayInfo {
+ char display_name[0x40]{"Default"};
+ u64 unknown_1{1};
+ u64 unknown_2{1};
+ u64 width{1920};
+ u64 height{1080};
+};
+static_assert(sizeof(DisplayInfo) == 0x60, "DisplayInfo has wrong size");
class Parcel {
public:
// This default size was chosen arbitrarily.
static constexpr size_t DefaultBufferSize = 0x40;
Parcel() : buffer(DefaultBufferSize) {}
- Parcel(std::vector<u8> data) : buffer(std::move(data)) {}
+ explicit Parcel(std::vector<u8> data) : buffer(std::move(data)) {}
virtual ~Parcel() = default;
template <typename T>
T Read() {
+ ASSERT(read_index + sizeof(T) <= buffer.size());
T val;
std::memcpy(&val, buffer.data() + read_index, sizeof(T));
read_index += sizeof(T);
@@ -40,6 +52,7 @@ public:
template <typename T>
T ReadUnaligned() {
+ ASSERT(read_index + sizeof(T) <= buffer.size());
T val;
std::memcpy(&val, buffer.data() + read_index, sizeof(T));
read_index += sizeof(T);
@@ -47,8 +60,10 @@ public:
}
std::vector<u8> ReadBlock(size_t length) {
- std::vector<u8> data(length);
- std::memcpy(data.data(), buffer.data() + read_index, length);
+ ASSERT(read_index + length <= buffer.size());
+ const u8* const begin = buffer.data() + read_index;
+ const u8* const end = begin + length;
+ std::vector<u8> data(begin, end);
read_index += length;
read_index = Common::AlignUp(read_index, 4);
return data;
@@ -78,7 +93,18 @@ public:
write_index = Common::AlignUp(write_index, 4);
}
+ template <typename T>
+ void WriteObject(const T& val) {
+ u32_le size = static_cast<u32>(sizeof(val));
+ Write(size);
+ // TODO(Subv): Support file descriptors.
+ Write<u32_le>(0); // Fd count.
+ Write(val);
+ }
+
void Deserialize() {
+ ASSERT(buffer.size() > sizeof(Header));
+
Header header{};
std::memcpy(&header, buffer.data(), sizeof(Header));
@@ -93,17 +119,19 @@ public:
SerializeData();
Header header{};
+ header.data_size = static_cast<u32_le>(write_index - sizeof(Header));
header.data_offset = sizeof(Header);
- header.data_size = write_index - sizeof(Header);
+ header.objects_size = 4;
+ header.objects_offset = sizeof(Header) + header.data_size;
std::memcpy(buffer.data(), &header, sizeof(Header));
return buffer;
}
protected:
- virtual void SerializeData(){};
+ virtual void SerializeData() {}
- virtual void DeserializeData(){};
+ virtual void DeserializeData() {}
private:
struct Header {
@@ -121,7 +149,7 @@ private:
class NativeWindow : public Parcel {
public:
- NativeWindow(u32 id) : Parcel() {
+ explicit NativeWindow(u32 id) : Parcel() {
data.id = id;
}
~NativeWindow() override = default;
@@ -134,11 +162,11 @@ protected:
private:
struct Data {
u32_le magic = 2;
- u32_le process_id;
+ u32_le process_id = 1;
u32_le id;
- INSERT_PADDING_BYTES(0xC);
- std::array<u8, 8> dspdrv = {'d', 's', 'p', 'd', 'r', 'v'};
- INSERT_PADDING_BYTES(8);
+ INSERT_PADDING_WORDS(3);
+ std::array<u8, 8> dispdrv = {'d', 'i', 's', 'p', 'd', 'r', 'v', '\0'};
+ INSERT_PADDING_WORDS(2);
};
static_assert(sizeof(Data) == 0x28, "ParcelData has wrong size");
@@ -147,12 +175,12 @@ private:
class IGBPConnectRequestParcel : public Parcel {
public:
- IGBPConnectRequestParcel(const std::vector<u8>& buffer) : Parcel(buffer) {
+ explicit IGBPConnectRequestParcel(const std::vector<u8>& buffer) : Parcel(buffer) {
Deserialize();
}
~IGBPConnectRequestParcel() override = default;
- void DeserializeData() {
+ void DeserializeData() override {
std::u16string token = ReadInterfaceToken();
data = Read<Data>();
}
@@ -168,7 +196,7 @@ public:
class IGBPConnectResponseParcel : public Parcel {
public:
- IGBPConnectResponseParcel(u32 width, u32 height) : Parcel() {
+ explicit IGBPConnectResponseParcel(u32 width, u32 height) : Parcel() {
data.width = width;
data.height = height;
}
@@ -194,16 +222,16 @@ private:
class IGBPSetPreallocatedBufferRequestParcel : public Parcel {
public:
- IGBPSetPreallocatedBufferRequestParcel(const std::vector<u8>& buffer) : Parcel(buffer) {
+ explicit IGBPSetPreallocatedBufferRequestParcel(const std::vector<u8>& buffer)
+ : Parcel(buffer) {
Deserialize();
}
~IGBPSetPreallocatedBufferRequestParcel() override = default;
- void DeserializeData() {
+ void DeserializeData() override {
std::u16string token = ReadInterfaceToken();
data = Read<Data>();
- ASSERT(data.graphic_buffer_length == sizeof(IGBPBuffer));
- buffer = Read<IGBPBuffer>();
+ buffer = Read<NVFlinger::IGBPBuffer>();
}
struct Data {
@@ -214,7 +242,7 @@ public:
};
Data data;
- IGBPBuffer buffer;
+ NVFlinger::IGBPBuffer buffer;
};
class IGBPSetPreallocatedBufferResponseParcel : public Parcel {
@@ -231,12 +259,12 @@ protected:
class IGBPDequeueBufferRequestParcel : public Parcel {
public:
- IGBPDequeueBufferRequestParcel(const std::vector<u8>& buffer) : Parcel(buffer) {
+ explicit IGBPDequeueBufferRequestParcel(const std::vector<u8>& buffer) : Parcel(buffer) {
Deserialize();
}
~IGBPDequeueBufferRequestParcel() override = default;
- void DeserializeData() {
+ void DeserializeData() override {
std::u16string token = ReadInterfaceToken();
data = Read<Data>();
}
@@ -252,17 +280,28 @@ public:
Data data;
};
+struct BufferProducerFence {
+ u32 is_valid;
+ std::array<Nvidia::IoctlFence, 4> fences;
+};
+static_assert(sizeof(BufferProducerFence) == 36, "BufferProducerFence has wrong size");
+
class IGBPDequeueBufferResponseParcel : public Parcel {
public:
- IGBPDequeueBufferResponseParcel(u32 slot) : Parcel(), slot(slot) {}
+ explicit IGBPDequeueBufferResponseParcel(u32 slot) : Parcel(), slot(slot) {}
~IGBPDequeueBufferResponseParcel() override = default;
protected:
void SerializeData() override {
- Write(slot);
// TODO(Subv): Find out how this Fence is used.
- std::array<u32_le, 11> fence = {};
- Write(fence);
+ BufferProducerFence fence = {};
+ fence.is_valid = 1;
+ for (auto& fence_ : fence.fences)
+ fence_.id = -1;
+
+ Write(slot);
+ Write<u32_le>(1);
+ WriteObject(fence);
Write<u32_le>(0);
}
@@ -271,12 +310,12 @@ protected:
class IGBPRequestBufferRequestParcel : public Parcel {
public:
- IGBPRequestBufferRequestParcel(const std::vector<u8>& buffer) : Parcel(buffer) {
+ explicit IGBPRequestBufferRequestParcel(const std::vector<u8>& buffer) : Parcel(buffer) {
Deserialize();
}
~IGBPRequestBufferRequestParcel() override = default;
- void DeserializeData() {
+ void DeserializeData() override {
std::u16string token = ReadInterfaceToken();
slot = Read<u32_le>();
}
@@ -286,51 +325,64 @@ public:
class IGBPRequestBufferResponseParcel : public Parcel {
public:
- IGBPRequestBufferResponseParcel(IGBPBuffer buffer) : Parcel(), buffer(buffer) {}
+ explicit IGBPRequestBufferResponseParcel(NVFlinger::IGBPBuffer buffer)
+ : Parcel(), buffer(buffer) {}
~IGBPRequestBufferResponseParcel() override = default;
protected:
void SerializeData() override {
- // TODO(Subv): Find out what this all means
+ // TODO(Subv): Figure out what this value means, writing non-zero here will make libnx try
+ // to read an IGBPBuffer object from the parcel.
Write<u32_le>(1);
-
- Write<u32_le>(sizeof(IGBPBuffer));
- Write<u32_le>(0); // Unknown
-
- Write(buffer);
-
+ WriteObject(buffer);
Write<u32_le>(0);
}
- IGBPBuffer buffer;
+ NVFlinger::IGBPBuffer buffer;
};
class IGBPQueueBufferRequestParcel : public Parcel {
public:
- IGBPQueueBufferRequestParcel(const std::vector<u8>& buffer) : Parcel(buffer) {
+ explicit IGBPQueueBufferRequestParcel(const std::vector<u8>& buffer) : Parcel(buffer) {
Deserialize();
}
~IGBPQueueBufferRequestParcel() override = default;
- void DeserializeData() {
+ void DeserializeData() override {
std::u16string token = ReadInterfaceToken();
data = Read<Data>();
}
+ struct Fence {
+ u32_le id;
+ u32_le value;
+ };
+ static_assert(sizeof(Fence) == 8, "Fence has wrong size");
+
struct Data {
u32_le slot;
- INSERT_PADDING_WORDS(2);
+ INSERT_PADDING_WORDS(3);
u32_le timestamp;
- INSERT_PADDING_WORDS(20);
+ s32_le is_auto_timestamp;
+ s32_le crop_left;
+ s32_le crop_top;
+ s32_le crop_right;
+ s32_le crop_bottom;
+ s32_le scaling_mode;
+ NVFlinger::BufferQueue::BufferTransformFlags transform;
+ u32_le sticky_transform;
+ INSERT_PADDING_WORDS(2);
+ u32_le fence_is_valid;
+ std::array<Fence, 2> fences;
};
- static_assert(sizeof(Data) == 96, "ParcelData has wrong size");
+ static_assert(sizeof(Data) == 80, "ParcelData has wrong size");
Data data;
};
class IGBPQueueBufferResponseParcel : public Parcel {
public:
- IGBPQueueBufferResponseParcel(u32 width, u32 height) : Parcel() {
+ explicit IGBPQueueBufferResponseParcel(u32 width, u32 height) : Parcel() {
data.width = width;
data.height = height;
}
@@ -354,15 +406,44 @@ private:
Data data{};
};
+class IGBPQueryRequestParcel : public Parcel {
+public:
+ explicit IGBPQueryRequestParcel(const std::vector<u8>& buffer) : Parcel(buffer) {
+ Deserialize();
+ }
+ ~IGBPQueryRequestParcel() override = default;
+
+ void DeserializeData() override {
+ std::u16string token = ReadInterfaceToken();
+ type = Read<u32_le>();
+ }
+
+ u32 type;
+};
+
+class IGBPQueryResponseParcel : public Parcel {
+public:
+ explicit IGBPQueryResponseParcel(u32 value) : Parcel(), value(value) {}
+ ~IGBPQueryResponseParcel() override = default;
+
+protected:
+ void SerializeData() override {
+ Write(value);
+ }
+
+private:
+ u32_le value;
+};
+
class IHOSBinderDriver final : public ServiceFramework<IHOSBinderDriver> {
public:
- IHOSBinderDriver(std::shared_ptr<NVFlinger> nv_flinger)
+ explicit IHOSBinderDriver(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
: ServiceFramework("IHOSBinderDriver"), nv_flinger(std::move(nv_flinger)) {
static const FunctionInfo functions[] = {
{0, &IHOSBinderDriver::TransactParcel, "TransactParcel"},
{1, &IHOSBinderDriver::AdjustRefcount, "AdjustRefcount"},
{2, &IHOSBinderDriver::GetNativeHandle, "GetNativeHandle"},
- {3, nullptr, "TransactParcelAuto"},
+ {3, &IHOSBinderDriver::TransactParcel, "TransactParcelAuto"},
};
RegisterHandlers(functions);
}
@@ -391,64 +472,76 @@ private:
u32 id = rp.Pop<u32>();
auto transaction = static_cast<TransactionId>(rp.Pop<u32>());
u32 flags = rp.Pop<u32>();
-
- auto& input_buffer = ctx.BufferDescriptorA()[0];
- std::vector<u8> input_data(input_buffer.Size());
- Memory::ReadBlock(input_buffer.Address(), input_data.data(), input_buffer.Size());
-
- auto& output_buffer = ctx.BufferDescriptorB()[0];
-
auto buffer_queue = nv_flinger->GetBufferQueue(id);
+ LOG_DEBUG(Service_VI, "called, transaction=%x", static_cast<u32>(transaction));
+
if (transaction == TransactionId::Connect) {
- IGBPConnectRequestParcel request{input_data};
+ IGBPConnectRequestParcel request{ctx.ReadBuffer()};
IGBPConnectResponseParcel response{1280, 720};
- auto response_buffer = response.Serialize();
- Memory::WriteBlock(output_buffer.Address(), response_buffer.data(),
- output_buffer.Size());
+ ctx.WriteBuffer(response.Serialize());
} else if (transaction == TransactionId::SetPreallocatedBuffer) {
- IGBPSetPreallocatedBufferRequestParcel request{input_data};
+ IGBPSetPreallocatedBufferRequestParcel request{ctx.ReadBuffer()};
buffer_queue->SetPreallocatedBuffer(request.data.slot, request.buffer);
IGBPSetPreallocatedBufferResponseParcel response{};
- auto response_buffer = response.Serialize();
- Memory::WriteBlock(output_buffer.Address(), response_buffer.data(),
- output_buffer.Size());
+ ctx.WriteBuffer(response.Serialize());
} else if (transaction == TransactionId::DequeueBuffer) {
- IGBPDequeueBufferRequestParcel request{input_data};
-
- u32 slot = buffer_queue->DequeueBuffer(request.data.pixel_format, request.data.width,
- request.data.height);
-
- IGBPDequeueBufferResponseParcel response{slot};
- auto response_buffer = response.Serialize();
- Memory::WriteBlock(output_buffer.Address(), response_buffer.data(),
- output_buffer.Size());
+ IGBPDequeueBufferRequestParcel request{ctx.ReadBuffer()};
+ const u32 width{request.data.width};
+ const u32 height{request.data.height};
+ boost::optional<u32> slot = buffer_queue->DequeueBuffer(width, height);
+
+ if (slot != boost::none) {
+ // Buffer is available
+ IGBPDequeueBufferResponseParcel response{*slot};
+ ctx.WriteBuffer(response.Serialize());
+ } else {
+ // Wait the current thread until a buffer becomes available
+ auto wait_event = ctx.SleepClientThread(
+ Kernel::GetCurrentThread(), "IHOSBinderDriver::DequeueBuffer", -1,
+ [=](Kernel::SharedPtr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx,
+ ThreadWakeupReason reason) {
+ // Repeat TransactParcel DequeueBuffer when a buffer is available
+ auto buffer_queue = nv_flinger->GetBufferQueue(id);
+ boost::optional<u32> slot = buffer_queue->DequeueBuffer(width, height);
+ IGBPDequeueBufferResponseParcel response{*slot};
+ ctx.WriteBuffer(response.Serialize());
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ });
+ buffer_queue->SetBufferWaitEvent(std::move(wait_event));
+ }
} else if (transaction == TransactionId::RequestBuffer) {
- IGBPRequestBufferRequestParcel request{input_data};
+ IGBPRequestBufferRequestParcel request{ctx.ReadBuffer()};
auto& buffer = buffer_queue->RequestBuffer(request.slot);
IGBPRequestBufferResponseParcel response{buffer};
- auto response_buffer = response.Serialize();
- Memory::WriteBlock(output_buffer.Address(), response_buffer.data(),
- output_buffer.Size());
+ ctx.WriteBuffer(response.Serialize());
} else if (transaction == TransactionId::QueueBuffer) {
- IGBPQueueBufferRequestParcel request{input_data};
+ IGBPQueueBufferRequestParcel request{ctx.ReadBuffer()};
- buffer_queue->QueueBuffer(request.data.slot);
+ buffer_queue->QueueBuffer(request.data.slot, request.data.transform);
IGBPQueueBufferResponseParcel response{1280, 720};
- auto response_buffer = response.Serialize();
- Memory::WriteBlock(output_buffer.Address(), response_buffer.data(),
- output_buffer.Size());
+ ctx.WriteBuffer(response.Serialize());
+ } else if (transaction == TransactionId::Query) {
+ IGBPQueryRequestParcel request{ctx.ReadBuffer()};
+
+ u32 value =
+ buffer_queue->Query(static_cast<NVFlinger::BufferQueue::QueryType>(request.type));
+
+ IGBPQueryResponseParcel response{value};
+ ctx.WriteBuffer(response.Serialize());
+ } else if (transaction == TransactionId::CancelBuffer) {
+ LOG_WARNING(Service_VI, "(STUBBED) called, transaction=CancelBuffer");
} else {
ASSERT_MSG(false, "Unimplemented");
}
- LOG_WARNING(Service, "(STUBBED) called");
- IPC::RequestBuilder rb{ctx, 2};
+ IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
@@ -458,8 +551,8 @@ private:
s32 addval = rp.PopRaw<s32>();
u32 type = rp.Pop<u32>();
- LOG_WARNING(Service, "(STUBBED) called id=%u, addval=%08X, type=%08X", id, addval, type);
- IPC::RequestBuilder rb{ctx, 2};
+ LOG_WARNING(Service_VI, "(STUBBED) called id=%u, addval=%08X, type=%08X", id, addval, type);
+ IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
@@ -472,14 +565,14 @@ private:
// TODO(Subv): Find out what this actually is.
- LOG_WARNING(Service, "(STUBBED) called id=%u, unknown=%08X", id, unknown);
- IPC::RequestBuilder rb{ctx, 2, 1};
+ LOG_WARNING(Service_VI, "(STUBBED) called id=%u, unknown=%08X", id, unknown);
+ IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(buffer_queue->GetNativeHandle());
}
- std::shared_ptr<NVFlinger> nv_flinger;
-};
+ std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
+}; // namespace VI
class ISystemDisplayService final : public ServiceFramework<ISystemDisplayService> {
public:
@@ -494,19 +587,19 @@ public:
private:
void SetLayerZ(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ LOG_WARNING(Service_VI, "(STUBBED) called");
IPC::RequestParser rp{ctx};
u64 layer_id = rp.Pop<u64>();
u64 z_value = rp.Pop<u64>();
- IPC::RequestBuilder rb = rp.MakeBuilder(2, 0, 0, 0);
+ IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
rb.Push(RESULT_SUCCESS);
}
};
class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> {
public:
- IManagerDisplayService(std::shared_ptr<NVFlinger> nv_flinger)
+ explicit IManagerDisplayService(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
: ServiceFramework("IManagerDisplayService"), nv_flinger(std::move(nv_flinger)) {
static const FunctionInfo functions[] = {
{1020, &IManagerDisplayService::CloseDisplay, "CloseDisplay"},
@@ -520,16 +613,16 @@ public:
private:
void CloseDisplay(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ LOG_WARNING(Service_VI, "(STUBBED) called");
IPC::RequestParser rp{ctx};
u64 display = rp.Pop<u64>();
- IPC::RequestBuilder rb = rp.MakeBuilder(2, 0, 0, 0);
+ IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
rb.Push(RESULT_SUCCESS);
}
void CreateManagedLayer(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ LOG_WARNING(Service_VI, "(STUBBED) called");
IPC::RequestParser rp{ctx};
u32 unknown = rp.Pop<u32>();
rp.Skip(1, false);
@@ -538,163 +631,173 @@ private:
u64 layer_id = nv_flinger->CreateLayer(display);
- IPC::RequestBuilder rb = rp.MakeBuilder(4, 0, 0, 0);
+ IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0);
rb.Push(RESULT_SUCCESS);
rb.Push(layer_id);
}
void AddToLayerStack(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ LOG_WARNING(Service_VI, "(STUBBED) called");
IPC::RequestParser rp{ctx};
u32 stack = rp.Pop<u32>();
u64 layer_id = rp.Pop<u64>();
- IPC::RequestBuilder rb = rp.MakeBuilder(2, 0, 0, 0);
+ IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
rb.Push(RESULT_SUCCESS);
}
- std::shared_ptr<NVFlinger> nv_flinger;
+ std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
};
-void IApplicationDisplayService::GetRelayService(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
-
- IPC::RequestBuilder rb{ctx, 2, 0, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IHOSBinderDriver>(nv_flinger);
-}
+class IApplicationDisplayService final : public ServiceFramework<IApplicationDisplayService> {
+public:
+ IApplicationDisplayService(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
+ ~IApplicationDisplayService() = default;
-void IApplicationDisplayService::GetSystemDisplayService(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+private:
+ void GetRelayService(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_VI, "(STUBBED) called");
- IPC::RequestBuilder rb{ctx, 2, 0, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISystemDisplayService>();
-}
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IHOSBinderDriver>(nv_flinger);
+ }
-void IApplicationDisplayService::GetManagerDisplayService(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ void GetSystemDisplayService(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_VI, "(STUBBED) called");
- IPC::RequestBuilder rb{ctx, 2, 0, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IManagerDisplayService>(nv_flinger);
-}
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<ISystemDisplayService>();
+ }
-void IApplicationDisplayService::GetIndirectDisplayTransactionService(
- Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ void GetManagerDisplayService(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_VI, "(STUBBED) called");
- IPC::RequestBuilder rb{ctx, 2, 0, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IHOSBinderDriver>(nv_flinger);
-}
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IManagerDisplayService>(nv_flinger);
+ }
-void IApplicationDisplayService::OpenDisplay(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
- IPC::RequestParser rp{ctx};
- auto name_buf = rp.PopRaw<std::array<u8, 0x40>>();
- auto end = std::find(name_buf.begin(), name_buf.end(), '\0');
+ void GetIndirectDisplayTransactionService(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_VI, "(STUBBED) called");
- std::string name(name_buf.begin(), end);
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IHOSBinderDriver>(nv_flinger);
+ }
- ASSERT_MSG(name == "Default", "Non-default displays aren't supported yet");
+ void OpenDisplay(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_VI, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ auto name_buf = rp.PopRaw<std::array<u8, 0x40>>();
+ auto end = std::find(name_buf.begin(), name_buf.end(), '\0');
- IPC::RequestBuilder rb = rp.MakeBuilder(4, 0, 0, 0);
- rb.Push(RESULT_SUCCESS);
- rb.Push<u64>(nv_flinger->OpenDisplay(name));
-}
+ std::string name(name_buf.begin(), end);
-void IApplicationDisplayService::CloseDisplay(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
- IPC::RequestParser rp{ctx};
- u64 display_id = rp.Pop<u64>();
+ ASSERT_MSG(name == "Default", "Non-default displays aren't supported yet");
- IPC::RequestBuilder rb = rp.MakeBuilder(4, 0, 0, 0);
- rb.Push(RESULT_SUCCESS);
-}
+ IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(nv_flinger->OpenDisplay(name));
+ }
-void IApplicationDisplayService::OpenLayer(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
- IPC::RequestParser rp{ctx};
- auto name_buf = rp.PopRaw<std::array<u8, 0x40>>();
- auto end = std::find(name_buf.begin(), name_buf.end(), '\0');
+ void CloseDisplay(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_VI, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ u64 display_id = rp.Pop<u64>();
- std::string display_name(name_buf.begin(), end);
+ IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ }
- u64 layer_id = rp.Pop<u64>();
- u64 aruid = rp.Pop<u64>();
+ void SetLayerScalingMode(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_VI, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ u32 scaling_mode = rp.Pop<u32>();
+ u64 unknown = rp.Pop<u64>();
- auto& buffer = ctx.BufferDescriptorB()[0];
+ IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ }
- u64 display_id = nv_flinger->OpenDisplay(display_name);
- u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id);
+ void ListDisplays(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ DisplayInfo display_info;
+ ctx.WriteBuffer(&display_info, sizeof(DisplayInfo));
+ IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(1);
+ LOG_WARNING(Service_VI, "(STUBBED) called");
+ }
- NativeWindow native_window{buffer_queue_id};
- auto data = native_window.Serialize();
- Memory::WriteBlock(buffer.Address(), data.data(), data.size());
+ void OpenLayer(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_VI, "called");
+ IPC::RequestParser rp{ctx};
+ auto name_buf = rp.PopRaw<std::array<u8, 0x40>>();
+ auto end = std::find(name_buf.begin(), name_buf.end(), '\0');
- IPC::RequestBuilder rb = rp.MakeBuilder(4, 0, 0, 0);
- rb.Push(RESULT_SUCCESS);
- rb.Push<u64>(data.size());
-}
+ std::string display_name(name_buf.begin(), end);
-void IApplicationDisplayService::CreateStrayLayer(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ u64 layer_id = rp.Pop<u64>();
+ u64 aruid = rp.Pop<u64>();
- IPC::RequestParser rp{ctx};
- u32 flags = rp.Pop<u32>();
- u64 display_id = rp.Pop<u64>();
+ u64 display_id = nv_flinger->OpenDisplay(display_name);
+ u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id);
- auto& buffer = ctx.BufferDescriptorB()[0];
+ NativeWindow native_window{buffer_queue_id};
+ IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(ctx.WriteBuffer(native_window.Serialize()));
+ }
- // TODO(Subv): What's the difference between a Stray and a Managed layer?
+ void CreateStrayLayer(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_VI, "called");
- u64 layer_id = nv_flinger->CreateLayer(display_id);
- u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id);
+ IPC::RequestParser rp{ctx};
+ u32 flags = rp.Pop<u32>();
+ rp.Pop<u32>(); // padding
+ u64 display_id = rp.Pop<u64>();
- NativeWindow native_window{buffer_queue_id};
- auto data = native_window.Serialize();
- Memory::WriteBlock(buffer.Address(), data.data(), data.size());
+ // TODO(Subv): What's the difference between a Stray and a Managed layer?
- IPC::RequestBuilder rb = rp.MakeBuilder(6, 0, 0, 0);
- rb.Push(RESULT_SUCCESS);
- rb.Push(layer_id);
- rb.Push<u64>(data.size());
-}
+ u64 layer_id = nv_flinger->CreateLayer(display_id);
+ u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id);
-void IApplicationDisplayService::DestroyStrayLayer(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ NativeWindow native_window{buffer_queue_id};
+ IPC::ResponseBuilder rb = rp.MakeBuilder(6, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(layer_id);
+ rb.Push<u64>(ctx.WriteBuffer(native_window.Serialize()));
+ }
- IPC::RequestParser rp{ctx};
- u64 layer_id = rp.Pop<u64>();
+ void DestroyStrayLayer(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_VI, "(STUBBED) called");
- IPC::RequestBuilder rb = rp.MakeBuilder(2, 0, 0, 0);
- rb.Push(RESULT_SUCCESS);
-}
+ IPC::RequestParser rp{ctx};
+ u64 layer_id = rp.Pop<u64>();
-void IApplicationDisplayService::SetLayerScalingMode(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
- IPC::RequestParser rp{ctx};
- u32 scaling_mode = rp.Pop<u32>();
- u64 unknown = rp.Pop<u64>();
+ IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
+ rb.Push(RESULT_SUCCESS);
+ }
- IPC::RequestBuilder rb = rp.MakeBuilder(2, 0, 0, 0);
- rb.Push(RESULT_SUCCESS);
-}
+ void GetDisplayVsyncEvent(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_VI, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ u64 display_id = rp.Pop<u64>();
-void IApplicationDisplayService::GetDisplayVsyncEvent(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
- IPC::RequestParser rp{ctx};
- u64 display_id = rp.Pop<u64>();
+ auto vsync_event = nv_flinger->GetVsyncEvent(display_id);
- auto vsync_event = nv_flinger->GetVsyncEvent(display_id);
+ IPC::ResponseBuilder rb = rp.MakeBuilder(2, 1, 0);
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(vsync_event);
+ }
- IPC::RequestBuilder rb = rp.MakeBuilder(2, 1, 0, 0);
- rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(vsync_event);
-}
+ std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
+};
-IApplicationDisplayService::IApplicationDisplayService(std::shared_ptr<NVFlinger> nv_flinger)
+IApplicationDisplayService::IApplicationDisplayService(
+ std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
: ServiceFramework("IApplicationDisplayService"), nv_flinger(std::move(nv_flinger)) {
static const FunctionInfo functions[] = {
{100, &IApplicationDisplayService::GetRelayService, "GetRelayService"},
@@ -702,7 +805,7 @@ IApplicationDisplayService::IApplicationDisplayService(std::shared_ptr<NVFlinger
{102, &IApplicationDisplayService::GetManagerDisplayService, "GetManagerDisplayService"},
{103, &IApplicationDisplayService::GetIndirectDisplayTransactionService,
"GetIndirectDisplayTransactionService"},
- {1000, nullptr, "ListDisplays"},
+ {1000, &IApplicationDisplayService::ListDisplays, "ListDisplays"},
{1010, &IApplicationDisplayService::OpenDisplay, "OpenDisplay"},
{1020, &IApplicationDisplayService::CloseDisplay, "CloseDisplay"},
{2101, &IApplicationDisplayService::SetLayerScalingMode, "SetLayerScalingMode"},
@@ -714,212 +817,24 @@ IApplicationDisplayService::IApplicationDisplayService(std::shared_ptr<NVFlinger
RegisterHandlers(functions);
}
-void InstallInterfaces(SM::ServiceManager& service_manager) {
- std::make_shared<VI_M>()->InstallAsService(service_manager);
-}
-
-NVFlinger::NVFlinger() {
- // Add the different displays to the list of displays.
- Display default_{0, "Default"};
- Display external{1, "External"};
- Display edid{2, "Edid"};
- Display internal{3, "Internal"};
-
- displays.emplace_back(default_);
- displays.emplace_back(external);
- displays.emplace_back(edid);
- displays.emplace_back(internal);
-
- // Schedule the screen composition events
- composition_event =
- CoreTiming::RegisterEvent("ScreenCompositioin", [this](u64 userdata, int cycles_late) {
- Compose();
- CoreTiming::ScheduleEvent(frame_ticks - cycles_late, composition_event);
- });
-
- CoreTiming::ScheduleEvent(frame_ticks, composition_event);
-}
-
-NVFlinger::~NVFlinger() {
- CoreTiming::UnscheduleEvent(composition_event, 0);
-}
-
-u64 NVFlinger::OpenDisplay(const std::string& name) {
- LOG_WARNING(Service, "Opening display %s", name.c_str());
+Module::Interface::Interface(std::shared_ptr<Module> module, const char* name,
+ std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
+ : ServiceFramework(name), module(std::move(module)), nv_flinger(std::move(nv_flinger)) {}
- // TODO(Subv): Currently we only support the Default display.
- ASSERT(name == "Default");
-
- auto itr = std::find_if(displays.begin(), displays.end(),
- [&](const Display& display) { return display.name == name; });
-
- ASSERT(itr != displays.end());
-
- return itr->id;
-}
-
-u64 NVFlinger::CreateLayer(u64 display_id) {
- auto& display = GetDisplay(display_id);
-
- ASSERT_MSG(display.layers.empty(), "Only one layer is supported per display at the moment");
-
- u64 layer_id = next_layer_id++;
- u32 buffer_queue_id = next_buffer_queue_id++;
- auto buffer_queue = std::make_shared<BufferQueue>(buffer_queue_id, layer_id);
- display.layers.emplace_back(layer_id, buffer_queue);
- buffer_queues.emplace_back(std::move(buffer_queue));
- return layer_id;
-}
-
-u32 NVFlinger::GetBufferQueueId(u64 display_id, u64 layer_id) {
- const auto& layer = GetLayer(display_id, layer_id);
- return layer.buffer_queue->GetId();
-}
+void Module::Interface::GetDisplayService(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_VI, "(STUBBED) called");
-Kernel::SharedPtr<Kernel::Event> NVFlinger::GetVsyncEvent(u64 display_id) {
- const auto& display = GetDisplay(display_id);
- return display.vsync_event;
-}
-
-std::shared_ptr<BufferQueue> NVFlinger::GetBufferQueue(u32 id) const {
- auto itr = std::find_if(buffer_queues.begin(), buffer_queues.end(),
- [&](const auto& queue) { return queue->GetId() == id; });
-
- ASSERT(itr != buffer_queues.end());
- return *itr;
-}
-
-Display& NVFlinger::GetDisplay(u64 display_id) {
- auto itr = std::find_if(displays.begin(), displays.end(),
- [&](const Display& display) { return display.id == display_id; });
-
- ASSERT(itr != displays.end());
- return *itr;
-}
-
-Layer& NVFlinger::GetLayer(u64 display_id, u64 layer_id) {
- auto& display = GetDisplay(display_id);
-
- auto itr = std::find_if(display.layers.begin(), display.layers.end(),
- [&](const Layer& layer) { return layer.id == layer_id; });
-
- ASSERT(itr != display.layers.end());
- return *itr;
-}
-
-void NVFlinger::Compose() {
- for (auto& display : displays) {
- // Trigger vsync for this display at the end of drawing
- SCOPE_EXIT({ display.vsync_event->Signal(); });
-
- // Don't do anything for displays without layers.
- if (display.layers.empty())
- continue;
-
- // TODO(Subv): Support more than 1 layer.
- ASSERT_MSG(display.layers.size() == 1, "Max 1 layer per display is supported");
-
- Layer& layer = display.layers[0];
- auto& buffer_queue = layer.buffer_queue;
-
- // Search for a queued buffer and acquire it
- auto buffer = buffer_queue->AcquireBuffer();
-
- if (buffer == boost::none) {
- // There was no queued buffer to draw, render previous frame
- VideoCore::g_renderer->SwapBuffers({});
- continue;
- }
-
- auto& igbp_buffer = buffer->igbp_buffer;
-
- // Now send the buffer to the GPU for drawing.
- auto nvdrv = Nvidia::nvdrv.lock();
- ASSERT(nvdrv);
-
- // TODO(Subv): Support more than just disp0. The display device selection is probably based
- // on which display we're drawing (Default, Internal, External, etc)
- auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>("/dev/nvdisp_disp0");
- ASSERT(nvdisp);
-
- nvdisp->flip(igbp_buffer.gpu_buffer_id, igbp_buffer.offset, igbp_buffer.format,
- igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride);
-
- buffer_queue->ReleaseBuffer(buffer->slot);
- }
-}
-
-BufferQueue::BufferQueue(u32 id, u64 layer_id) : id(id), layer_id(layer_id) {
- native_handle = Kernel::Event::Create(Kernel::ResetType::OneShot, "BufferQueue NativeHandle");
-}
-
-void BufferQueue::SetPreallocatedBuffer(u32 slot, IGBPBuffer& igbp_buffer) {
- Buffer buffer{};
- buffer.slot = slot;
- buffer.igbp_buffer = igbp_buffer;
- buffer.status = Buffer::Status::Free;
-
- LOG_WARNING(Service, "Adding graphics buffer %u", slot);
-
- queue.emplace_back(buffer);
-}
-
-u32 BufferQueue::DequeueBuffer(u32 pixel_format, u32 width, u32 height) {
- auto itr = std::find_if(queue.begin(), queue.end(), [&](const Buffer& buffer) {
- // Only consider free buffers. Buffers become free once again after they've been Acquired
- // and Released by the compositor, see the NVFlinger::Compose method.
- if (buffer.status != Buffer::Status::Free)
- return false;
-
- // Make sure that the parameters match.
- auto& igbp_buffer = buffer.igbp_buffer;
- return igbp_buffer.format == pixel_format && igbp_buffer.width == width &&
- igbp_buffer.height == height;
- });
- ASSERT(itr != queue.end());
-
- itr->status = Buffer::Status::Dequeued;
- return itr->slot;
-}
-
-const IGBPBuffer& BufferQueue::RequestBuffer(u32 slot) const {
- auto itr = std::find_if(queue.begin(), queue.end(),
- [&](const Buffer& buffer) { return buffer.slot == slot; });
- ASSERT(itr != queue.end());
- ASSERT(itr->status == Buffer::Status::Dequeued);
- return itr->igbp_buffer;
-}
-
-void BufferQueue::QueueBuffer(u32 slot) {
- auto itr = std::find_if(queue.begin(), queue.end(),
- [&](const Buffer& buffer) { return buffer.slot == slot; });
- ASSERT(itr != queue.end());
- ASSERT(itr->status == Buffer::Status::Dequeued);
- itr->status = Buffer::Status::Queued;
-}
-
-boost::optional<const BufferQueue::Buffer&> BufferQueue::AcquireBuffer() {
- auto itr = std::find_if(queue.begin(), queue.end(), [](const Buffer& buffer) {
- return buffer.status == Buffer::Status::Queued;
- });
- if (itr == queue.end())
- return boost::none;
- itr->status = Buffer::Status::Acquired;
- return *itr;
-}
-
-void BufferQueue::ReleaseBuffer(u32 slot) {
- auto itr = std::find_if(queue.begin(), queue.end(),
- [&](const Buffer& buffer) { return buffer.slot == slot; });
- ASSERT(itr != queue.end());
- ASSERT(itr->status == Buffer::Status::Acquired);
- itr->status = Buffer::Status::Free;
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IApplicationDisplayService>(nv_flinger);
}
-Layer::Layer(u64 id, std::shared_ptr<BufferQueue> queue) : id(id), buffer_queue(std::move(queue)) {}
-
-Display::Display(u64 id, std::string name) : id(id), name(std::move(name)) {
- vsync_event = Kernel::Event::Create(Kernel::ResetType::Pulse, "Display VSync Event");
+void InstallInterfaces(SM::ServiceManager& service_manager,
+ std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) {
+ auto module = std::make_shared<Module>();
+ std::make_shared<VI_M>(module, nv_flinger)->InstallAsService(service_manager);
+ std::make_shared<VI_S>(module, nv_flinger)->InstallAsService(service_manager);
+ std::make_shared<VI_U>(module, nv_flinger)->InstallAsService(service_manager);
}
} // namespace VI
diff --git a/src/core/hle/service/vi/vi.h b/src/core/hle/service/vi/vi.h
index 81d4f3daa..985c9d27c 100644
--- a/src/core/hle/service/vi/vi.h
+++ b/src/core/hle/service/vi/vi.h
@@ -4,9 +4,7 @@
#pragma once
-#include <memory>
-#include <boost/optional.hpp>
-#include "core/hle/kernel/event.h"
+#include "core/hle/service/nvflinger/nvflinger.h"
#include "core/hle/service/service.h"
namespace CoreTiming {
@@ -16,147 +14,24 @@ struct EventType;
namespace Service {
namespace VI {
-struct IGBPBuffer {
- u32_le magic;
- u32_le width;
- u32_le height;
- u32_le stride;
- u32_le format;
- u32_le usage;
- INSERT_PADDING_WORDS(1);
- u32_le index;
- INSERT_PADDING_WORDS(3);
- u32_le gpu_buffer_id;
- INSERT_PADDING_WORDS(17);
- u32_le nvmap_handle;
- u32_le offset;
- INSERT_PADDING_WORDS(60);
-};
-
-static_assert(sizeof(IGBPBuffer) == 0x16C, "IGBPBuffer has wrong size");
-
-class BufferQueue {
+class Module final {
public:
- BufferQueue(u32 id, u64 layer_id);
- ~BufferQueue() = default;
+ class Interface : public ServiceFramework<Interface> {
+ public:
+ Interface(std::shared_ptr<Module> module, const char* name,
+ std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
- struct Buffer {
- enum class Status { Free = 0, Queued = 1, Dequeued = 2, Acquired = 3 };
+ void GetDisplayService(Kernel::HLERequestContext& ctx);
- u32 slot;
- Status status = Status::Free;
- IGBPBuffer igbp_buffer;
+ protected:
+ std::shared_ptr<Module> module;
+ std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
};
-
- void SetPreallocatedBuffer(u32 slot, IGBPBuffer& buffer);
- u32 DequeueBuffer(u32 pixel_format, u32 width, u32 height);
- const IGBPBuffer& RequestBuffer(u32 slot) const;
- void QueueBuffer(u32 slot);
- boost::optional<const Buffer&> AcquireBuffer();
- void ReleaseBuffer(u32 slot);
-
- u32 GetId() const {
- return id;
- }
-
- Kernel::SharedPtr<Kernel::Event> GetNativeHandle() const {
- return native_handle;
- }
-
-private:
- u32 id;
- u64 layer_id;
-
- std::vector<Buffer> queue;
- Kernel::SharedPtr<Kernel::Event> native_handle;
-};
-
-struct Layer {
- Layer(u64 id, std::shared_ptr<BufferQueue> queue);
- ~Layer() = default;
-
- u64 id;
- std::shared_ptr<BufferQueue> buffer_queue;
-};
-
-struct Display {
- Display(u64 id, std::string name);
- ~Display() = default;
-
- u64 id;
- std::string name;
-
- std::vector<Layer> layers;
- Kernel::SharedPtr<Kernel::Event> vsync_event;
-};
-
-class NVFlinger {
-public:
- NVFlinger();
- ~NVFlinger();
-
- /// Opens the specified display and returns the id.
- u64 OpenDisplay(const std::string& name);
-
- /// Creates a layer on the specified display and returns the layer id.
- u64 CreateLayer(u64 display_id);
-
- /// Gets the buffer queue id of the specified layer in the specified display.
- u32 GetBufferQueueId(u64 display_id, u64 layer_id);
-
- /// Gets the vsync event for the specified display.
- Kernel::SharedPtr<Kernel::Event> GetVsyncEvent(u64 display_id);
-
- /// Obtains a buffer queue identified by the id.
- std::shared_ptr<BufferQueue> GetBufferQueue(u32 id) const;
-
- /// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when
- /// finished.
- void Compose();
-
-private:
- /// Returns the display identified by the specified id.
- Display& GetDisplay(u64 display_id);
-
- /// Returns the layer identified by the specified id in the desired display.
- Layer& GetLayer(u64 display_id, u64 layer_id);
-
- std::vector<Display> displays;
- std::vector<std::shared_ptr<BufferQueue>> buffer_queues;
-
- /// Id to use for the next layer that is created, this counter is shared among all displays.
- u64 next_layer_id = 1;
- /// Id to use for the next buffer queue that is created, this counter is shared among all
- /// layers.
- u32 next_buffer_queue_id = 1;
-
- /// CoreTiming event that handles screen composition.
- CoreTiming::EventType* composition_event;
-};
-
-class IApplicationDisplayService final : public ServiceFramework<IApplicationDisplayService> {
-public:
- IApplicationDisplayService(std::shared_ptr<NVFlinger> nv_flinger);
- ~IApplicationDisplayService() = default;
-
-private:
- void GetRelayService(Kernel::HLERequestContext& ctx);
- void GetSystemDisplayService(Kernel::HLERequestContext& ctx);
- void GetManagerDisplayService(Kernel::HLERequestContext& ctx);
- void GetIndirectDisplayTransactionService(Kernel::HLERequestContext& ctx);
- void OpenDisplay(Kernel::HLERequestContext& ctx);
- void CloseDisplay(Kernel::HLERequestContext& ctx);
- void SetLayerScalingMode(Kernel::HLERequestContext& ctx);
- void OpenLayer(Kernel::HLERequestContext& ctx);
- void CreateStrayLayer(Kernel::HLERequestContext& ctx);
- void DestroyStrayLayer(Kernel::HLERequestContext& ctx);
- void GetDisplayVsyncEvent(Kernel::HLERequestContext& ctx);
-
- std::shared_ptr<NVFlinger> nv_flinger;
};
/// Registers all VI services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager,
+ std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
} // namespace VI
} // namespace Service
diff --git a/src/core/hle/service/vi/vi_m.cpp b/src/core/hle/service/vi/vi_m.cpp
index 1a5a28b0d..5781fa9ec 100644
--- a/src/core/hle/service/vi/vi_m.cpp
+++ b/src/core/hle/service/vi/vi_m.cpp
@@ -2,28 +2,18 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "common/logging/log.h"
-#include "core/hle/ipc_helpers.h"
-#include "core/hle/service/vi/vi.h"
#include "core/hle/service/vi/vi_m.h"
namespace Service {
namespace VI {
-void VI_M::GetDisplayService(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
-
- IPC::RequestBuilder rb{ctx, 2, 0, 0, 1};
- rb.PushIpcInterface<IApplicationDisplayService>(nv_flinger);
-}
-
-VI_M::VI_M() : ServiceFramework("vi:m") {
+VI_M::VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
+ : Module::Interface(std::move(module), "vi:m", std::move(nv_flinger)) {
static const FunctionInfo functions[] = {
{2, &VI_M::GetDisplayService, "GetDisplayService"},
{3, nullptr, "GetDisplayServiceWithProxyNameExchange"},
};
RegisterHandlers(functions);
- nv_flinger = std::make_shared<NVFlinger>();
}
} // namespace VI
diff --git a/src/core/hle/service/vi/vi_m.h b/src/core/hle/service/vi/vi_m.h
index 70ff7a2f3..0f7b799d6 100644
--- a/src/core/hle/service/vi/vi_m.h
+++ b/src/core/hle/service/vi/vi_m.h
@@ -4,21 +4,14 @@
#pragma once
-#include <memory>
-#include "core/hle/service/service.h"
+#include "core/hle/service/vi/vi.h"
namespace Service {
namespace VI {
-class VI_M final : public ServiceFramework<VI_M> {
+class VI_M final : public Module::Interface {
public:
- VI_M();
- ~VI_M() = default;
-
-private:
- void GetDisplayService(Kernel::HLERequestContext& ctx);
-
- std::shared_ptr<NVFlinger> nv_flinger;
+ explicit VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
};
} // namespace VI
diff --git a/src/core/hle/service/vi/vi_s.cpp b/src/core/hle/service/vi/vi_s.cpp
new file mode 100644
index 000000000..1f937b2a8
--- /dev/null
+++ b/src/core/hle/service/vi/vi_s.cpp
@@ -0,0 +1,20 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/vi/vi_s.h"
+
+namespace Service {
+namespace VI {
+
+VI_S::VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
+ : Module::Interface(std::move(module), "vi:s", std::move(nv_flinger)) {
+ static const FunctionInfo functions[] = {
+ {1, &VI_S::GetDisplayService, "GetDisplayService"},
+ {3, nullptr, "GetDisplayServiceWithProxyNameExchange"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace VI
+} // namespace Service
diff --git a/src/core/hle/service/vi/vi_s.h b/src/core/hle/service/vi/vi_s.h
new file mode 100644
index 000000000..7b32fdddc
--- /dev/null
+++ b/src/core/hle/service/vi/vi_s.h
@@ -0,0 +1,18 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/vi/vi.h"
+
+namespace Service {
+namespace VI {
+
+class VI_S final : public Module::Interface {
+public:
+ explicit VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
+};
+
+} // namespace VI
+} // namespace Service
diff --git a/src/core/hle/service/vi/vi_u.cpp b/src/core/hle/service/vi/vi_u.cpp
new file mode 100644
index 000000000..14e375b86
--- /dev/null
+++ b/src/core/hle/service/vi/vi_u.cpp
@@ -0,0 +1,20 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/vi/vi_u.h"
+
+namespace Service {
+namespace VI {
+
+VI_U::VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
+ : Module::Interface(std::move(module), "vi:u", std::move(nv_flinger)) {
+ static const FunctionInfo functions[] = {
+ {0, &VI_U::GetDisplayService, "GetDisplayService"},
+ {3, nullptr, "GetDisplayServiceWithProxyNameExchange"},
+ };
+ RegisterHandlers(functions);
+}
+
+} // namespace VI
+} // namespace Service
diff --git a/src/core/hle/service/vi/vi_u.h b/src/core/hle/service/vi/vi_u.h
new file mode 100644
index 000000000..c557a2235
--- /dev/null
+++ b/src/core/hle/service/vi/vi_u.h
@@ -0,0 +1,18 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/vi/vi.h"
+
+namespace Service {
+namespace VI {
+
+class VI_U final : public Module::Interface {
+public:
+ explicit VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
+};
+
+} // namespace VI
+} // namespace Service
diff --git a/src/core/hle/shared_page.cpp b/src/core/hle/shared_page.cpp
index 9ce8af961..bba4a0715 100644
--- a/src/core/hle/shared_page.cpp
+++ b/src/core/hle/shared_page.cpp
@@ -82,4 +82,4 @@ void Init() {
CoreTiming::ScheduleEvent(0, update_time_event);
}
-} // namespace
+} // namespace SharedPage
diff --git a/src/core/hle/shared_page.h b/src/core/hle/shared_page.h
index 864695ae1..a58259888 100644
--- a/src/core/hle/shared_page.h
+++ b/src/core/hle/shared_page.h
@@ -66,4 +66,4 @@ extern SharedPageDef shared_page;
void Init();
-} // namespace
+} // namespace SharedPage
diff --git a/src/core/hw/hw.cpp b/src/core/hw/hw.cpp
index a751b1d62..0db604c76 100644
--- a/src/core/hw/hw.cpp
+++ b/src/core/hw/hw.cpp
@@ -91,4 +91,4 @@ void Shutdown() {
LCD::Shutdown();
LOG_DEBUG(HW, "shutdown OK");
}
-}
+} // namespace HW
diff --git a/src/core/hw/hw.h b/src/core/hw/hw.h
index a3c5d2ea3..5890d2b5c 100644
--- a/src/core/hw/hw.h
+++ b/src/core/hw/hw.h
@@ -47,4 +47,4 @@ void Init();
/// Shutdown hardware
void Shutdown();
-} // namespace
+} // namespace HW
diff --git a/src/core/hw/lcd.cpp b/src/core/hw/lcd.cpp
index 763ac1c4d..690079b65 100644
--- a/src/core/hw/lcd.cpp
+++ b/src/core/hw/lcd.cpp
@@ -64,4 +64,4 @@ void Shutdown() {
LOG_DEBUG(HW_LCD, "shutdown OK");
}
-} // namespace
+} // namespace LCD
diff --git a/src/core/hw/lcd.h b/src/core/hw/lcd.h
index 191fd44af..d2db9700f 100644
--- a/src/core/hw/lcd.h
+++ b/src/core/hw/lcd.h
@@ -83,4 +83,4 @@ void Init();
/// Shutdown hardware
void Shutdown();
-} // namespace
+} // namespace LCD
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
new file mode 100644
index 000000000..aa09ed323
--- /dev/null
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -0,0 +1,183 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cinttypes>
+#include "common/common_funcs.h"
+#include "common/common_paths.h"
+#include "common/file_util.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "core/file_sys/romfs_factory.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/resource_limit.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/loader/deconstructed_rom_directory.h"
+#include "core/loader/nso.h"
+#include "core/memory.h"
+
+namespace Loader {
+
+static std::string FindRomFS(const std::string& directory) {
+ std::string filepath_romfs;
+ const auto callback = [&filepath_romfs](unsigned*, const std::string& directory,
+ const std::string& virtual_name) -> bool {
+ const std::string physical_name = directory + virtual_name;
+ if (FileUtil::IsDirectory(physical_name)) {
+ // Skip directories
+ return true;
+ }
+
+ // Verify extension
+ const std::string extension = physical_name.substr(physical_name.find_last_of(".") + 1);
+ if (Common::ToLower(extension) != "romfs") {
+ return true;
+ }
+
+ // Found it - we are done
+ filepath_romfs = std::move(physical_name);
+ return false;
+ };
+
+ // Search the specified directory recursively, looking for the first .romfs file, which will
+ // be used for the RomFS
+ FileUtil::ForeachDirectoryEntry(nullptr, directory, callback);
+
+ return filepath_romfs;
+}
+
+AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileUtil::IOFile&& file,
+ std::string filepath)
+ : AppLoader(std::move(file)), filepath(std::move(filepath)) {}
+
+FileType AppLoader_DeconstructedRomDirectory::IdentifyType(FileUtil::IOFile& file,
+ const std::string& filepath) {
+ bool is_main_found{};
+ bool is_npdm_found{};
+ bool is_rtld_found{};
+ bool is_sdk_found{};
+
+ const auto callback = [&](unsigned* num_entries_out, const std::string& directory,
+ const std::string& virtual_name) -> bool {
+ // Skip directories
+ std::string physical_name = directory + virtual_name;
+ if (FileUtil::IsDirectory(physical_name)) {
+ return true;
+ }
+
+ // Verify filename
+ if (Common::ToLower(virtual_name) == "main") {
+ is_main_found = true;
+ } else if (Common::ToLower(virtual_name) == "main.npdm") {
+ is_npdm_found = true;
+ return true;
+ } else if (Common::ToLower(virtual_name) == "rtld") {
+ is_rtld_found = true;
+ } else if (Common::ToLower(virtual_name) == "sdk") {
+ is_sdk_found = true;
+ } else {
+ // Contrinue searching
+ return true;
+ }
+
+ // Verify file is an NSO
+ FileUtil::IOFile file(physical_name, "rb");
+ if (AppLoader_NSO::IdentifyType(file, physical_name) != FileType::NSO) {
+ return false;
+ }
+
+ // We are done if we've found and verified all required NSOs
+ return !(is_main_found && is_npdm_found && is_rtld_found && is_sdk_found);
+ };
+
+ // Search the directory recursively, looking for the required modules
+ const std::string directory = filepath.substr(0, filepath.find_last_of("/\\")) + DIR_SEP;
+ FileUtil::ForeachDirectoryEntry(nullptr, directory, callback);
+
+ if (is_main_found && is_npdm_found && is_rtld_found && is_sdk_found) {
+ return FileType::DeconstructedRomDirectory;
+ }
+
+ return FileType::Error;
+}
+
+ResultStatus AppLoader_DeconstructedRomDirectory::Load(
+ Kernel::SharedPtr<Kernel::Process>& process) {
+ if (is_loaded) {
+ return ResultStatus::ErrorAlreadyLoaded;
+ }
+ if (!file.IsOpen()) {
+ return ResultStatus::Error;
+ }
+
+ const std::string directory = filepath.substr(0, filepath.find_last_of("/\\")) + DIR_SEP;
+ const std::string npdm_path = directory + DIR_SEP + "main.npdm";
+
+ ResultStatus result = metadata.Load(npdm_path);
+ if (result != ResultStatus::Success) {
+ return result;
+ }
+ metadata.Print();
+
+ // Load NSO modules
+ VAddr next_load_addr{Memory::PROCESS_IMAGE_VADDR};
+ for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3",
+ "subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) {
+ const std::string path = directory + DIR_SEP + module;
+ const VAddr load_addr = next_load_addr;
+ next_load_addr = AppLoader_NSO::LoadModule(path, load_addr);
+ if (next_load_addr) {
+ LOG_DEBUG(Loader, "loaded module %s @ 0x%" PRIx64, module, load_addr);
+ } else {
+ next_load_addr = load_addr;
+ }
+ }
+
+ process->program_id = metadata.GetTitleID();
+ process->svc_access_mask.set();
+ process->address_mappings = default_address_mappings;
+ process->resource_limit =
+ Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);
+ process->Run(Memory::PROCESS_IMAGE_VADDR, metadata.GetMainThreadPriority(),
+ metadata.GetMainThreadStackSize());
+
+ // Find the RomFS by searching for a ".romfs" file in this directory
+ filepath_romfs = FindRomFS(directory);
+
+ // Register the RomFS if a ".romfs" file was found
+ if (!filepath_romfs.empty()) {
+ Service::FileSystem::RegisterFileSystem(std::make_unique<FileSys::RomFS_Factory>(*this),
+ Service::FileSystem::Type::RomFS);
+ }
+
+ is_loaded = true;
+ return ResultStatus::Success;
+}
+
+ResultStatus AppLoader_DeconstructedRomDirectory::ReadRomFS(
+ std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, u64& size) {
+
+ if (filepath_romfs.empty()) {
+ LOG_DEBUG(Loader, "No RomFS available");
+ return ResultStatus::ErrorNotUsed;
+ }
+
+ // We reopen the file, to allow its position to be independent
+ romfs_file = std::make_shared<FileUtil::IOFile>(filepath_romfs, "rb");
+ if (!romfs_file->IsOpen()) {
+ return ResultStatus::Error;
+ }
+
+ offset = 0;
+ size = romfs_file->GetSize();
+
+ LOG_DEBUG(Loader, "RomFS offset: 0x%016" PRIX64, offset);
+ LOG_DEBUG(Loader, "RomFS size: 0x%016" PRIX64, size);
+
+ // Reset read pointer
+ file.Seek(0, SEEK_SET);
+
+ return ResultStatus::Success;
+}
+
+} // namespace Loader
diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h
new file mode 100644
index 000000000..23295d911
--- /dev/null
+++ b/src/core/loader/deconstructed_rom_directory.h
@@ -0,0 +1,48 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include "common/common_types.h"
+#include "core/file_sys/program_metadata.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/loader/loader.h"
+
+namespace Loader {
+
+/**
+ * This class loads a "deconstructed ROM directory", which are the typical format we see for Switch
+ * game dumps. The path should be a "main" NSO, which must be in a directory that contains the other
+ * standard ExeFS NSOs (e.g. rtld, sdk, etc.). It will automatically find and load these.
+ * Furthermore, it will look for the first .romfs file (optionally) and use this for the RomFS.
+ */
+class AppLoader_DeconstructedRomDirectory final : public AppLoader {
+public:
+ AppLoader_DeconstructedRomDirectory(FileUtil::IOFile&& file, std::string filepath);
+
+ /**
+ * Returns the type of the file
+ * @param file FileUtil::IOFile open file
+ * @param filepath Path of the file that we are opening.
+ * @return FileType found, or FileType::Error if this loader doesn't know it
+ */
+ static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath);
+
+ FileType GetFileType() override {
+ return IdentifyType(file, filepath);
+ }
+
+ ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
+
+ ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset,
+ u64& size) override;
+
+private:
+ std::string filepath_romfs;
+ std::string filepath;
+ FileSys::ProgramMetadata metadata;
+};
+
+} // namespace Loader
diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp
index 9ba913dbe..e9f462196 100644
--- a/src/core/loader/elf.cpp
+++ b/src/core/loader/elf.cpp
@@ -300,7 +300,7 @@ SharedPtr<CodeSet> ElfReader::LoadInto(u32 vaddr) {
std::vector<u8> program_image(total_image_size);
size_t current_image_position = 0;
- SharedPtr<CodeSet> codeset = CodeSet::Create("", 0);
+ SharedPtr<CodeSet> codeset = CodeSet::Create("");
for (unsigned int i = 0; i < header->e_phnum; ++i) {
Elf32_Phdr* p = &segments[i];
@@ -323,8 +323,9 @@ SharedPtr<CodeSet> ElfReader::LoadInto(u32 vaddr) {
}
if (codeset_segment->size != 0) {
- LOG_ERROR(Loader, "ELF has more than one segment of the same type. Skipping extra "
- "segment (id %i)",
+ LOG_ERROR(Loader,
+ "ELF has more than one segment of the same type. Skipping extra "
+ "segment (id %i)",
i);
continue;
}
@@ -364,7 +365,10 @@ SectionID ElfReader::GetSectionByName(const char* name, int firstSection) const
namespace Loader {
-FileType AppLoader_ELF::IdentifyType(FileUtil::IOFile& file) {
+AppLoader_ELF::AppLoader_ELF(FileUtil::IOFile&& file, std::string filename)
+ : AppLoader(std::move(file)), filename(std::move(filename)) {}
+
+FileType AppLoader_ELF::IdentifyType(FileUtil::IOFile& file, const std::string&) {
static constexpr u16 ELF_MACHINE_ARM{0x28};
u32 magic = 0;
@@ -402,7 +406,6 @@ ResultStatus AppLoader_ELF::Load(Kernel::SharedPtr<Kernel::Process>& process) {
SharedPtr<CodeSet> codeset = elf_reader.LoadInto(Memory::PROCESS_IMAGE_VADDR);
codeset->name = filename;
- process = Kernel::Process::Create("main");
process->LoadModule(codeset, codeset->entrypoint);
process->svc_access_mask.set();
process->address_mappings = default_address_mappings;
@@ -411,7 +414,7 @@ ResultStatus AppLoader_ELF::Load(Kernel::SharedPtr<Kernel::Process>& process) {
process->resource_limit =
Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);
- process->Run(codeset->entrypoint, 48, Kernel::DEFAULT_STACK_SIZE);
+ process->Run(codeset->entrypoint, 48, Memory::DEFAULT_STACK_SIZE);
is_loaded = true;
return ResultStatus::Success;
diff --git a/src/core/loader/elf.h b/src/core/loader/elf.h
index 113da5917..ee741a789 100644
--- a/src/core/loader/elf.h
+++ b/src/core/loader/elf.h
@@ -16,18 +16,18 @@ namespace Loader {
/// Loads an ELF/AXF file
class AppLoader_ELF final : public AppLoader {
public:
- AppLoader_ELF(FileUtil::IOFile&& file, std::string filename)
- : AppLoader(std::move(file)), filename(std::move(filename)) {}
+ AppLoader_ELF(FileUtil::IOFile&& file, std::string filename);
/**
* Returns the type of the file
* @param file FileUtil::IOFile open file
+ * @param filepath Path of the file that we are opening.
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
- static FileType IdentifyType(FileUtil::IOFile& file);
+ static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath);
FileType GetFileType() override {
- return IdentifyType(file);
+ return IdentifyType(file, filename);
}
ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
diff --git a/src/core/loader/linker.cpp b/src/core/loader/linker.cpp
index 87cc65e91..69198e3e3 100644
--- a/src/core/loader/linker.cpp
+++ b/src/core/loader/linker.cpp
@@ -84,7 +84,7 @@ void Linker::WriteRelocations(std::vector<u8>& program_image, const std::vector<
}
break;
default:
- LOG_CRITICAL(Loader, "Unknown relocation type: %d", rela.type);
+ LOG_CRITICAL(Loader, "Unknown relocation type: %d", static_cast<int>(rela.type));
break;
}
}
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 92defd381..2ec08506d 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -1,4 +1,4 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -7,12 +7,11 @@
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/hle/kernel/process.h"
+#include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/elf.h"
#include "core/loader/nro.h"
#include "core/loader/nso.h"
-////////////////////////////////////////////////////////////////////////////////////////////////////
-
namespace Loader {
const std::initializer_list<Kernel::AddressMapping> default_address_mappings = {
@@ -21,14 +20,15 @@ const std::initializer_list<Kernel::AddressMapping> default_address_mappings = {
{0x1F000000, 0x600000, false}, // entire VRAM
};
-FileType IdentifyFile(FileUtil::IOFile& file) {
+FileType IdentifyFile(FileUtil::IOFile& file, const std::string& filepath) {
FileType type;
#define CHECK_TYPE(loader) \
- type = AppLoader_##loader::IdentifyType(file); \
+ type = AppLoader_##loader::IdentifyType(file, filepath); \
if (FileType::Error != type) \
return type;
+ CHECK_TYPE(DeconstructedRomDirectory)
CHECK_TYPE(ELF)
CHECK_TYPE(NSO)
CHECK_TYPE(NRO)
@@ -45,13 +45,13 @@ FileType IdentifyFile(const std::string& file_name) {
return FileType::Unknown;
}
- return IdentifyFile(file);
+ return IdentifyFile(file, file_name);
}
FileType GuessFromExtension(const std::string& extension_) {
std::string extension = Common::ToLower(extension_);
- if (extension == ".elf" || extension == ".axf")
+ if (extension == ".elf")
return FileType::ELF;
else if (extension == ".nro")
return FileType::NRO;
@@ -69,6 +69,8 @@ const char* GetFileTypeString(FileType type) {
return "NRO";
case FileType::NSO:
return "NSO";
+ case FileType::DeconstructedRomDirectory:
+ return "Directory";
case FileType::Error:
case FileType::Unknown:
break;
@@ -102,6 +104,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileUtil::IOFile&& file, FileTyp
case FileType::NRO:
return std::make_unique<AppLoader_NRO>(std::move(file), filepath);
+ // NX deconstructed ROM directory.
+ case FileType::DeconstructedRomDirectory:
+ return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file), filepath);
+
default:
return nullptr;
}
@@ -117,7 +123,7 @@ std::unique_ptr<AppLoader> GetLoader(const std::string& filename) {
std::string filename_filename, filename_extension;
Common::SplitPath(filename, nullptr, &filename_filename, &filename_extension);
- FileType type = IdentifyFile(file);
+ FileType type = IdentifyFile(file, filename);
FileType filename_type = GuessFromExtension(filename_extension);
if (type != filename_type) {
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index dd6bb4e64..dd44ee9a6 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -1,4 +1,4 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -20,9 +20,6 @@ struct AddressMapping;
class Process;
} // namespace Kernel
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// Loader namespace
-
namespace Loader {
/// File types supported by CTR
@@ -32,14 +29,16 @@ enum class FileType {
ELF,
NSO,
NRO,
+ DeconstructedRomDirectory,
};
/**
* Identifies the type of a bootable file based on the magic value in its header.
* @param file open file
+ * @param filepath Path of the file that we are opening.
* @return FileType of file
*/
-FileType IdentifyFile(FileUtil::IOFile& file);
+FileType IdentifyFile(FileUtil::IOFile& file, const std::string& filepath);
/**
* Identifies the type of a bootable file based on the magic value in its header.
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index a5d09512b..b5133e4d6 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -5,8 +5,10 @@
#include <vector>
#include "common/common_funcs.h"
+#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/swap.h"
+#include "core/core.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/loader/nro.h"
@@ -45,7 +47,10 @@ struct ModHeader {
};
static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size.");
-FileType AppLoader_NRO::IdentifyType(FileUtil::IOFile& file) {
+AppLoader_NRO::AppLoader_NRO(FileUtil::IOFile&& file, std::string filepath)
+ : AppLoader(std::move(file)), filepath(std::move(filepath)) {}
+
+FileType AppLoader_NRO::IdentifyType(FileUtil::IOFile& file, const std::string&) {
// Read NSO header
NroHeader nro_header{};
file.Seek(0, SEEK_SET);
@@ -62,20 +67,6 @@ static constexpr u32 PageAlignSize(u32 size) {
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
}
-static std::vector<u8> ReadSegment(FileUtil::IOFile& file, const NroSegmentHeader& header) {
- std::vector<u8> data;
- data.resize(header.size);
-
- file.Seek(header.offset + sizeof(NroHeader), SEEK_SET);
- size_t bytes_read{file.ReadBytes(data.data(), header.size)};
- if (header.size != PageAlignSize(static_cast<u32>(bytes_read))) {
- LOG_CRITICAL(Loader, "Failed to read NRO segment bytes", header.size);
- return {};
- }
-
- return data;
-}
-
bool AppLoader_NRO::LoadNro(const std::string& path, VAddr load_base) {
FileUtil::IOFile file(path, "rb");
if (!file.IsOpen()) {
@@ -93,9 +84,9 @@ bool AppLoader_NRO::LoadNro(const std::string& path, VAddr load_base) {
}
// Build program image
- Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create("", 0);
+ Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create("");
std::vector<u8> program_image;
- program_image.resize(PageAlignSize(nro_header.file_size + nro_header.bss_size));
+ program_image.resize(PageAlignSize(nro_header.file_size));
file.Seek(0, SEEK_SET);
file.ReadBytes(program_image.data(), nro_header.file_size);
@@ -107,28 +98,22 @@ bool AppLoader_NRO::LoadNro(const std::string& path, VAddr load_base) {
// Read MOD header
ModHeader mod_header{};
- u32 bss_size{Memory::PAGE_SIZE}; // Default .bss to page size if MOD0 section doesn't exist
+ // Default .bss to NRO header bss size if MOD0 section doesn't exist
+ u32 bss_size{PageAlignSize(nro_header.bss_size)};
std::memcpy(&mod_header, program_image.data() + nro_header.module_header_offset,
sizeof(ModHeader));
const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')};
if (has_mod_header) {
// Resize program image to include .bss section and page align each section
bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset);
- codeset->data.size += bss_size;
- }
- program_image.resize(PageAlignSize(static_cast<u32>(program_image.size()) + bss_size));
-
- // Relocate symbols if there was a proper MOD header - This must happen after the image has been
- // loaded into memory
- if (has_mod_header) {
- Relocate(program_image, nro_header.module_header_offset + mod_header.dynamic_offset,
- load_base);
}
+ codeset->data.size += bss_size;
+ program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
// Load codeset for current process
codeset->name = path;
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
- Kernel::g_current_process->LoadModule(codeset, load_base);
+ Core::CurrentProcess()->LoadModule(codeset, load_base);
return true;
}
@@ -141,9 +126,9 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
return ResultStatus::Error;
}
- // Load and relocate "main" and "sdk" NSO
+ // Load NRO
static constexpr VAddr base_addr{Memory::PROCESS_IMAGE_VADDR};
- process = Kernel::Process::Create("main");
+
if (!LoadNro(filepath, base_addr)) {
return ResultStatus::ErrorInvalidFormat;
}
@@ -152,9 +137,7 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
process->address_mappings = default_address_mappings;
process->resource_limit =
Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);
- process->Run(base_addr, 48, Kernel::DEFAULT_STACK_SIZE);
-
- ResolveImports();
+ process->Run(base_addr, 48, Memory::DEFAULT_STACK_SIZE);
is_loaded = true;
return ResultStatus::Success;
diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h
index e20fa1555..599adb253 100644
--- a/src/core/loader/nro.h
+++ b/src/core/loader/nro.h
@@ -4,10 +4,8 @@
#pragma once
-#include <map>
#include <string>
#include "common/common_types.h"
-#include "common/file_util.h"
#include "core/hle/kernel/kernel.h"
#include "core/loader/linker.h"
#include "core/loader/loader.h"
@@ -17,18 +15,18 @@ namespace Loader {
/// Loads an NRO file
class AppLoader_NRO final : public AppLoader, Linker {
public:
- AppLoader_NRO(FileUtil::IOFile&& file, std::string filepath)
- : AppLoader(std::move(file)), filepath(std::move(filepath)) {}
+ AppLoader_NRO(FileUtil::IOFile&& file, std::string filepath);
/**
* Returns the type of the file
* @param file FileUtil::IOFile open file
+ * @param filepath Path of the file that we are opening.
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
- static FileType IdentifyType(FileUtil::IOFile& file);
+ static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath);
FileType GetFileType() override {
- return IdentifyType(file);
+ return IdentifyType(file, filepath);
}
ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index ff96e129b..3bc10ed0d 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -2,12 +2,14 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cinttypes>
#include <vector>
#include <lz4.h>
-
#include "common/common_funcs.h"
+#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/swap.h"
+#include "core/core.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/loader/nso.h"
@@ -47,7 +49,10 @@ struct ModHeader {
};
static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size.");
-FileType AppLoader_NSO::IdentifyType(FileUtil::IOFile& file) {
+AppLoader_NSO::AppLoader_NSO(FileUtil::IOFile&& file, std::string filepath)
+ : AppLoader(std::move(file)), filepath(std::move(filepath)) {}
+
+FileType AppLoader_NSO::IdentifyType(FileUtil::IOFile& file, const std::string&) {
u32 magic = 0;
file.Seek(0, SEEK_SET);
if (1 != file.ReadArray<u32>(&magic, 1)) {
@@ -79,7 +84,7 @@ static std::vector<u8> ReadSegment(FileUtil::IOFile& file, const NsoSegmentHeade
reinterpret_cast<char*>(uncompressed_data.data()), compressed_size, header.size);
ASSERT_MSG(bytes_uncompressed == header.size && bytes_uncompressed == uncompressed_data.size(),
- "%d != %d != %d", bytes_uncompressed, header.size, uncompressed_data.size());
+ "%d != %u != %zu", bytes_uncompressed, header.size, uncompressed_data.size());
return uncompressed_data;
}
@@ -88,7 +93,7 @@ static constexpr u32 PageAlignSize(u32 size) {
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
}
-VAddr AppLoader_NSO::LoadNso(const std::string& path, VAddr load_base, bool relocate) {
+VAddr AppLoader_NSO::LoadModule(const std::string& path, VAddr load_base) {
FileUtil::IOFile file(path, "rb");
if (!file.IsOpen()) {
return {};
@@ -105,7 +110,7 @@ VAddr AppLoader_NSO::LoadNso(const std::string& path, VAddr load_base, bool relo
}
// Build program image
- Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create("", 0);
+ Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create("");
std::vector<u8> program_image;
for (int i = 0; i < nso_header.segments.size(); ++i) {
std::vector<u8> data =
@@ -135,16 +140,10 @@ VAddr AppLoader_NSO::LoadNso(const std::string& path, VAddr load_base, bool relo
const u32 image_size{PageAlignSize(static_cast<u32>(program_image.size()) + bss_size)};
program_image.resize(image_size);
- // Relocate symbols if there was a proper MOD header - This must happen after the image has been
- // loaded into memory
- if (has_mod_header && relocate) {
- Relocate(program_image, module_offset + mod_header.dynamic_offset, load_base);
- }
-
// Load codeset for current process
codeset->name = path;
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
- Kernel::g_current_process->LoadModule(codeset, load_base);
+ Core::CurrentProcess()->LoadModule(codeset, load_base);
return load_base + image_size;
}
@@ -157,31 +156,16 @@ ResultStatus AppLoader_NSO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
return ResultStatus::Error;
}
- process = Kernel::Process::Create("main");
-
- // Load NSO modules
- VAddr next_load_addr{Memory::PROCESS_IMAGE_VADDR};
- for (const auto& module :
- {"rtld", "sdk", "subsdk0", "subsdk1", "subsdk2", "subsdk3", "subsdk4"}) {
- const std::string path = filepath.substr(0, filepath.find_last_of("/\\")) + "/" + module;
- const VAddr load_addr = next_load_addr;
- next_load_addr = LoadNso(path, load_addr);
- if (next_load_addr) {
- LOG_DEBUG(Loader, "loaded module %s @ 0x%llx", module, load_addr);
- } else {
- next_load_addr = load_addr;
- }
- }
- // Load "main" module
- LoadNso(filepath, next_load_addr);
+ // Load module
+ LoadModule(filepath, Memory::PROCESS_IMAGE_VADDR);
+ LOG_DEBUG(Loader, "loaded module %s @ 0x%" PRIx64, filepath.c_str(),
+ Memory::PROCESS_IMAGE_VADDR);
process->svc_access_mask.set();
process->address_mappings = default_address_mappings;
process->resource_limit =
Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);
- process->Run(Memory::PROCESS_IMAGE_VADDR, 48, Kernel::DEFAULT_STACK_SIZE);
-
- ResolveImports();
+ process->Run(Memory::PROCESS_IMAGE_VADDR, 48, Memory::DEFAULT_STACK_SIZE);
is_loaded = true;
return ResultStatus::Success;
diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h
index 03424e70d..1ae30a824 100644
--- a/src/core/loader/nso.h
+++ b/src/core/loader/nso.h
@@ -4,10 +4,8 @@
#pragma once
-#include <map>
#include <string>
#include "common/common_types.h"
-#include "common/file_util.h"
#include "core/hle/kernel/kernel.h"
#include "core/loader/linker.h"
#include "core/loader/loader.h"
@@ -17,25 +15,25 @@ namespace Loader {
/// Loads an NSO file
class AppLoader_NSO final : public AppLoader, Linker {
public:
- AppLoader_NSO(FileUtil::IOFile&& file, std::string filepath)
- : AppLoader(std::move(file)), filepath(std::move(filepath)) {}
+ AppLoader_NSO(FileUtil::IOFile&& file, std::string filepath);
/**
* Returns the type of the file
* @param file FileUtil::IOFile open file
+ * @param filepath Path of the file that we are opening.
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
- static FileType IdentifyType(FileUtil::IOFile& file);
+ static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath);
FileType GetFileType() override {
- return IdentifyType(file);
+ return IdentifyType(file, filepath);
}
+ static VAddr LoadModule(const std::string& path, VAddr load_base);
+
ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
private:
- VAddr LoadNso(const std::string& path, VAddr load_base, bool relocate = false);
-
std::string filepath;
};
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 74a598852..291bf066f 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -2,8 +2,11 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <array>
+#include <cinttypes>
#include <cstring>
+#include <boost/optional.hpp>
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
@@ -21,7 +24,6 @@
namespace Memory {
static std::array<u8, Memory::VRAM_SIZE> vram;
-static std::array<u8, Memory::N3DS_EXTRA_RAM_SIZE> n3ds_extra_ram;
static PageTable* current_page_table = nullptr;
@@ -37,7 +39,7 @@ PageTable* GetCurrentPageTable() {
}
static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, PageType type) {
- LOG_DEBUG(HW_Memory, "Mapping %p onto %08X-%08X", memory, base * PAGE_SIZE,
+ LOG_DEBUG(HW_Memory, "Mapping %p onto %016" PRIX64 "-%016" PRIX64, memory, base * PAGE_SIZE,
(base + size) * PAGE_SIZE);
RasterizerFlushVirtualRegion(base << PAGE_BITS, size * PAGE_SIZE,
@@ -45,11 +47,10 @@ static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, Pa
VAddr end = base + size;
while (base != end) {
- ASSERT_MSG(base < PAGE_TABLE_NUM_ENTRIES, "out of range mapping at %08X", base);
+ ASSERT_MSG(base < PAGE_TABLE_NUM_ENTRIES, "out of range mapping at %016" PRIX64, base);
page_table.attributes[base] = type;
page_table.pointers[base] = memory;
- page_table.cached_res_count[base] = 0;
base += 1;
if (memory != nullptr)
@@ -58,23 +59,61 @@ static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, Pa
}
void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target) {
- ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %08X", size);
- ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %08X", base);
+ ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %016" PRIX64, size);
+ ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %016" PRIX64, base);
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, PageType::Memory);
}
-void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MMIORegionPointer mmio_handler) {
- ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %08X", size);
- ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %08X", base);
+void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer mmio_handler) {
+ ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %016" PRIX64, size);
+ ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %016" PRIX64, base);
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Special);
- page_table.special_regions.emplace_back(SpecialRegion{base, size, mmio_handler});
+ auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
+ SpecialRegion region{SpecialRegion::Type::IODevice, mmio_handler};
+ page_table.special_regions.add(std::make_pair(interval, std::set<SpecialRegion>{region}));
}
void UnmapRegion(PageTable& page_table, VAddr base, u64 size) {
- ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %08X", size);
- ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %08X", base);
+ ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %016" PRIX64, size);
+ ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %016" PRIX64, base);
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped);
+
+ auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
+ page_table.special_regions.erase(interval);
+}
+
+void AddDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook) {
+ auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
+ SpecialRegion region{SpecialRegion::Type::DebugHook, hook};
+ page_table.special_regions.add(std::make_pair(interval, std::set<SpecialRegion>{region}));
+}
+
+void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook) {
+ auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
+ SpecialRegion region{SpecialRegion::Type::DebugHook, hook};
+ page_table.special_regions.subtract(std::make_pair(interval, std::set<SpecialRegion>{region}));
+}
+
+/**
+ * This function should only be called for virtual addreses with attribute `PageType::Special`.
+ */
+static std::set<MemoryHookPointer> GetSpecialHandlers(const PageTable& page_table, VAddr vaddr,
+ u64 size) {
+ std::set<MemoryHookPointer> result;
+ auto interval = boost::icl::discrete_interval<VAddr>::closed(vaddr, vaddr + size - 1);
+ auto interval_list = page_table.special_regions.equal_range(interval);
+ for (auto it = interval_list.first; it != interval_list.second; ++it) {
+ for (const auto& region : it->second) {
+ result.insert(region.handler);
+ }
+ }
+ return result;
+}
+
+static std::set<MemoryHookPointer> GetSpecialHandlers(VAddr vaddr, u64 size) {
+ const PageTable& page_table = Core::CurrentProcess()->vm_manager.page_table;
+ return GetSpecialHandlers(page_table, vaddr, size);
}
/**
@@ -111,30 +150,9 @@ static u8* GetPointerFromVMA(const Kernel::Process& process, VAddr vaddr) {
* using a VMA from the current process.
*/
static u8* GetPointerFromVMA(VAddr vaddr) {
- return GetPointerFromVMA(*Kernel::g_current_process, vaddr);
+ return GetPointerFromVMA(*Core::CurrentProcess(), vaddr);
}
-/**
- * This function should only be called for virtual addreses with attribute `PageType::Special`.
- */
-static MMIORegionPointer GetMMIOHandler(const PageTable& page_table, VAddr vaddr) {
- for (const auto& region : page_table.special_regions) {
- if (vaddr >= region.base && vaddr < (region.base + region.size)) {
- return region.handler;
- }
- }
- ASSERT_MSG(false, "Mapped IO page without a handler @ %08X", vaddr);
- return nullptr; // Should never happen
-}
-
-static MMIORegionPointer GetMMIOHandler(VAddr vaddr) {
- const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table;
- return GetMMIOHandler(page_table, vaddr);
-}
-
-template <typename T>
-T ReadMMIO(MMIORegionPointer mmio_handler, VAddr addr);
-
template <typename T>
T Read(const VAddr vaddr) {
const u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
@@ -151,7 +169,7 @@ T Read(const VAddr vaddr) {
PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
switch (type) {
case PageType::Unmapped:
- LOG_ERROR(HW_Memory, "unmapped Read%lu @ 0x%llx", sizeof(T) * 8, vaddr);
+ LOG_ERROR(HW_Memory, "unmapped Read%lu @ 0x%08X", sizeof(T) * 8, vaddr);
return 0;
case PageType::Memory:
ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr);
@@ -163,21 +181,12 @@ T Read(const VAddr vaddr) {
std::memcpy(&value, GetPointerFromVMA(vaddr), sizeof(T));
return value;
}
- case PageType::Special:
- return ReadMMIO<T>(GetMMIOHandler(vaddr), vaddr);
- case PageType::RasterizerCachedSpecial: {
- RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush);
- return ReadMMIO<T>(GetMMIOHandler(vaddr), vaddr);
- }
default:
UNREACHABLE();
}
}
template <typename T>
-void WriteMMIO(MMIORegionPointer mmio_handler, VAddr addr, const T data);
-
-template <typename T>
void Write(const VAddr vaddr, const T data) {
u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
if (page_pointer) {
@@ -199,18 +208,10 @@ void Write(const VAddr vaddr, const T data) {
ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr);
break;
case PageType::RasterizerCachedMemory: {
- RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::FlushAndInvalidate);
+ RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Invalidate);
std::memcpy(GetPointerFromVMA(vaddr), &data, sizeof(T));
break;
}
- case PageType::Special:
- WriteMMIO<T>(GetMMIOHandler(vaddr), vaddr, data);
- break;
- case PageType::RasterizerCachedSpecial: {
- RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::FlushAndInvalidate);
- WriteMMIO<T>(GetMMIOHandler(vaddr), vaddr, data);
- break;
- }
default:
UNREACHABLE();
}
@@ -229,16 +230,11 @@ bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) {
if (page_table.attributes[vaddr >> PAGE_BITS] != PageType::Special)
return false;
- MMIORegionPointer mmio_region = GetMMIOHandler(page_table, vaddr);
- if (mmio_region) {
- return mmio_region->IsValidAddress(vaddr);
- }
-
return false;
}
bool IsValidVirtualAddress(const VAddr vaddr) {
- return IsValidVirtualAddress(*Kernel::g_current_process, vaddr);
+ return IsValidVirtualAddress(*Core::CurrentProcess(), vaddr);
}
bool IsValidPhysicalAddress(const PAddr paddr) {
@@ -284,7 +280,6 @@ u8* GetPhysicalPointer(PAddr address) {
{IO_AREA_PADDR, IO_AREA_SIZE},
{DSP_RAM_PADDR, DSP_RAM_SIZE},
{FCRAM_PADDR, FCRAM_N3DS_SIZE},
- {N3DS_EXTRA_RAM_PADDR, N3DS_EXTRA_RAM_SIZE},
};
const auto area =
@@ -293,12 +288,13 @@ u8* GetPhysicalPointer(PAddr address) {
});
if (area == std::end(memory_areas)) {
- LOG_ERROR(HW_Memory, "unknown GetPhysicalPointer @ 0x%08X", address);
+ LOG_ERROR(HW_Memory, "unknown GetPhysicalPointer @ 0x%016" PRIX64, address);
return nullptr;
}
if (area->paddr_base == IO_AREA_PADDR) {
- LOG_ERROR(HW_Memory, "MMIO mappings are not supported yet. phys_addr=0x%08X", address);
+ LOG_ERROR(HW_Memory, "MMIO mappings are not supported yet. phys_addr=0x%016" PRIX64,
+ address);
return nullptr;
}
@@ -322,9 +318,6 @@ u8* GetPhysicalPointer(PAddr address) {
}
ASSERT_MSG(target_pointer != nullptr, "Invalid FCRAM address");
break;
- case N3DS_EXTRA_RAM_PADDR:
- target_pointer = n3ds_extra_ram.data() + offset_into_region;
- break;
default:
UNREACHABLE();
}
@@ -332,35 +325,19 @@ u8* GetPhysicalPointer(PAddr address) {
return target_pointer;
}
-void RasterizerMarkRegionCached(PAddr start, u64 size, int count_delta) {
+void RasterizerMarkRegionCached(VAddr start, u64 size, bool cached) {
if (start == 0) {
return;
}
u64 num_pages = ((start + size - 1) >> PAGE_BITS) - (start >> PAGE_BITS) + 1;
- PAddr paddr = start;
-
- for (unsigned i = 0; i < num_pages; ++i, paddr += PAGE_SIZE) {
- boost::optional<VAddr> maybe_vaddr = PhysicalToVirtualAddress(paddr);
- // While the physical <-> virtual mapping is 1:1 for the regions supported by the cache,
- // some games (like Pokemon Super Mystery Dungeon) will try to use textures that go beyond
- // the end address of VRAM, causing the Virtual->Physical translation to fail when flushing
- // parts of the texture.
- if (!maybe_vaddr) {
- LOG_ERROR(HW_Memory,
- "Trying to flush a cached region to an invalid physical address %08X", paddr);
- continue;
- }
- VAddr vaddr = *maybe_vaddr;
+ VAddr vaddr = start;
- u8& res_count = current_page_table->cached_res_count[vaddr >> PAGE_BITS];
- ASSERT_MSG(count_delta <= UINT8_MAX - res_count,
- "Rasterizer resource cache counter overflow!");
- ASSERT_MSG(count_delta >= -res_count, "Rasterizer resource cache counter underflow!");
+ for (unsigned i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) {
+ PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
- // Switch page type to cached if now cached
- if (res_count == 0) {
- PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
+ if (cached) {
+ // Switch page type to cached if now cached
switch (page_type) {
case PageType::Unmapped:
// It is not necessary for a process to have this region mapped into its address
@@ -370,19 +347,11 @@ void RasterizerMarkRegionCached(PAddr start, u64 size, int count_delta) {
page_type = PageType::RasterizerCachedMemory;
current_page_table->pointers[vaddr >> PAGE_BITS] = nullptr;
break;
- case PageType::Special:
- page_type = PageType::RasterizerCachedSpecial;
- break;
default:
UNREACHABLE();
}
- }
-
- res_count += count_delta;
-
- // Switch page type to uncached if now uncached
- if (res_count == 0) {
- PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
+ } else {
+ // Switch page type to uncached if now uncached
switch (page_type) {
case PageType::Unmapped:
// It is not necessary for a process to have this region mapped into its address
@@ -391,7 +360,7 @@ void RasterizerMarkRegionCached(PAddr start, u64 size, int count_delta) {
case PageType::RasterizerCachedMemory: {
u8* pointer = GetPointerFromVMA(vaddr & ~PAGE_MASK);
if (pointer == nullptr) {
- // It's possible that this function has called been while updating the pagetable
+ // It's possible that this function has been called while updating the pagetable
// after unmapping a VMA. In that case the underlying VMA will no longer exist,
// and we should just leave the pagetable entry blank.
page_type = PageType::Unmapped;
@@ -401,9 +370,6 @@ void RasterizerMarkRegionCached(PAddr start, u64 size, int count_delta) {
}
break;
}
- case PageType::RasterizerCachedSpecial:
- page_type = PageType::Special;
- break;
default:
UNREACHABLE();
}
@@ -411,16 +377,41 @@ void RasterizerMarkRegionCached(PAddr start, u64 size, int count_delta) {
}
}
-void RasterizerFlushRegion(PAddr start, u64 size) {}
-
-void RasterizerFlushAndInvalidateRegion(PAddr start, u64 size) {
- // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
- // null here
-}
-
void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode) {
// Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
// null here
+ if (VideoCore::g_renderer == nullptr) {
+ return;
+ }
+
+ VAddr end = start + size;
+
+ auto CheckRegion = [&](VAddr region_start, VAddr region_end) {
+ if (start >= region_end || end <= region_start) {
+ // No overlap with region
+ return;
+ }
+
+ VAddr overlap_start = std::max(start, region_start);
+ VAddr overlap_end = std::min(end, region_end);
+ u64 overlap_size = overlap_end - overlap_start;
+
+ auto* rasterizer = VideoCore::g_renderer->Rasterizer();
+ switch (mode) {
+ case FlushMode::Flush:
+ rasterizer->FlushRegion(overlap_start, overlap_size);
+ break;
+ case FlushMode::Invalidate:
+ rasterizer->InvalidateRegion(overlap_start, overlap_size);
+ break;
+ case FlushMode::FlushAndInvalidate:
+ rasterizer->FlushAndInvalidateRegion(overlap_start, overlap_size);
+ break;
+ }
+ };
+
+ CheckRegion(PROCESS_IMAGE_VADDR, PROCESS_IMAGE_VADDR_END);
+ CheckRegion(HEAP_VADDR, HEAP_VADDR_END);
}
u8 Read8(const VAddr addr) {
@@ -448,12 +439,13 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_
size_t page_offset = src_addr & PAGE_MASK;
while (remaining_size > 0) {
- const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size);
+ const size_t copy_amount =
+ std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size);
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
switch (page_table.attributes[page_index]) {
case PageType::Unmapped: {
- LOG_ERROR(HW_Memory, "unmapped ReadBlock @ 0x%08X (start address = 0xllx, size = %zu)",
+ LOG_ERROR(HW_Memory, "unmapped ReadBlock @ 0x%08X (start address = 0x%08X, size = %zu)",
current_vaddr, src_addr, size);
std::memset(dest_buffer, 0, copy_amount);
break;
@@ -465,26 +457,12 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_
std::memcpy(dest_buffer, src_ptr, copy_amount);
break;
}
- case PageType::Special: {
- MMIORegionPointer handler = GetMMIOHandler(page_table, current_vaddr);
- DEBUG_ASSERT(handler);
- handler->ReadBlock(current_vaddr, dest_buffer, copy_amount);
- break;
- }
case PageType::RasterizerCachedMemory: {
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
FlushMode::Flush);
std::memcpy(dest_buffer, GetPointerFromVMA(process, current_vaddr), copy_amount);
break;
}
- case PageType::RasterizerCachedSpecial: {
- MMIORegionPointer handler = GetMMIOHandler(page_table, current_vaddr);
- DEBUG_ASSERT(handler);
- RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
- FlushMode::Flush);
- handler->ReadBlock(current_vaddr, dest_buffer, copy_amount);
- break;
- }
default:
UNREACHABLE();
}
@@ -497,7 +475,7 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_
}
void ReadBlock(const VAddr src_addr, void* dest_buffer, const size_t size) {
- ReadBlock(*Kernel::g_current_process, src_addr, dest_buffer, size);
+ ReadBlock(*Core::CurrentProcess(), src_addr, dest_buffer, size);
}
void Write8(const VAddr addr, const u8 data) {
@@ -524,7 +502,8 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi
size_t page_offset = dest_addr & PAGE_MASK;
while (remaining_size > 0) {
- const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size);
+ const size_t copy_amount =
+ std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size);
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
switch (page_table.attributes[page_index]) {
@@ -541,26 +520,12 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi
std::memcpy(dest_ptr, src_buffer, copy_amount);
break;
}
- case PageType::Special: {
- MMIORegionPointer handler = GetMMIOHandler(page_table, current_vaddr);
- DEBUG_ASSERT(handler);
- handler->WriteBlock(current_vaddr, src_buffer, copy_amount);
- break;
- }
case PageType::RasterizerCachedMemory: {
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
- FlushMode::FlushAndInvalidate);
+ FlushMode::Invalidate);
std::memcpy(GetPointerFromVMA(process, current_vaddr), src_buffer, copy_amount);
break;
}
- case PageType::RasterizerCachedSpecial: {
- MMIORegionPointer handler = GetMMIOHandler(page_table, current_vaddr);
- DEBUG_ASSERT(handler);
- RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
- FlushMode::FlushAndInvalidate);
- handler->WriteBlock(current_vaddr, src_buffer, copy_amount);
- break;
- }
default:
UNREACHABLE();
}
@@ -573,10 +538,11 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi
}
void WriteBlock(const VAddr dest_addr, const void* src_buffer, const size_t size) {
- WriteBlock(*Kernel::g_current_process, dest_addr, src_buffer, size);
+ WriteBlock(*Core::CurrentProcess(), dest_addr, src_buffer, size);
}
-void ZeroBlock(const VAddr dest_addr, const size_t size) {
+void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const size_t size) {
+ auto& page_table = process.vm_manager.page_table;
size_t remaining_size = size;
size_t page_index = dest_addr >> PAGE_BITS;
size_t page_offset = dest_addr & PAGE_MASK;
@@ -584,39 +550,27 @@ void ZeroBlock(const VAddr dest_addr, const size_t size) {
static const std::array<u8, PAGE_SIZE> zeros = {};
while (remaining_size > 0) {
- const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size);
+ const size_t copy_amount =
+ std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size);
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
- switch (current_page_table->attributes[page_index]) {
+ switch (page_table.attributes[page_index]) {
case PageType::Unmapped: {
LOG_ERROR(HW_Memory, "unmapped ZeroBlock @ 0x%08X (start address = 0x%08X, size = %zu)",
current_vaddr, dest_addr, size);
break;
}
case PageType::Memory: {
- DEBUG_ASSERT(current_page_table->pointers[page_index]);
+ DEBUG_ASSERT(page_table.pointers[page_index]);
- u8* dest_ptr = current_page_table->pointers[page_index] + page_offset;
+ u8* dest_ptr = page_table.pointers[page_index] + page_offset;
std::memset(dest_ptr, 0, copy_amount);
break;
}
- case PageType::Special: {
- DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
-
- GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, zeros.data(), copy_amount);
- break;
- }
case PageType::RasterizerCachedMemory: {
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
- FlushMode::FlushAndInvalidate);
- std::memset(GetPointerFromVMA(current_vaddr), 0, copy_amount);
- break;
- }
- case PageType::RasterizerCachedSpecial: {
- DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
- RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
- FlushMode::FlushAndInvalidate);
- GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, zeros.data(), copy_amount);
+ FlushMode::Invalidate);
+ std::memset(GetPointerFromVMA(process, current_vaddr), 0, copy_amount);
break;
}
default:
@@ -629,50 +583,34 @@ void ZeroBlock(const VAddr dest_addr, const size_t size) {
}
}
-void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) {
+void CopyBlock(const Kernel::Process& process, VAddr dest_addr, VAddr src_addr, const size_t size) {
+ auto& page_table = process.vm_manager.page_table;
size_t remaining_size = size;
size_t page_index = src_addr >> PAGE_BITS;
size_t page_offset = src_addr & PAGE_MASK;
while (remaining_size > 0) {
- const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size);
+ const size_t copy_amount =
+ std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size);
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
- switch (current_page_table->attributes[page_index]) {
+ switch (page_table.attributes[page_index]) {
case PageType::Unmapped: {
LOG_ERROR(HW_Memory, "unmapped CopyBlock @ 0x%08X (start address = 0x%08X, size = %zu)",
current_vaddr, src_addr, size);
- ZeroBlock(dest_addr, copy_amount);
+ ZeroBlock(process, dest_addr, copy_amount);
break;
}
case PageType::Memory: {
- DEBUG_ASSERT(current_page_table->pointers[page_index]);
- const u8* src_ptr = current_page_table->pointers[page_index] + page_offset;
- WriteBlock(dest_addr, src_ptr, copy_amount);
- break;
- }
- case PageType::Special: {
- DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
-
- std::vector<u8> buffer(copy_amount);
- GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, buffer.data(), buffer.size());
- WriteBlock(dest_addr, buffer.data(), buffer.size());
+ DEBUG_ASSERT(page_table.pointers[page_index]);
+ const u8* src_ptr = page_table.pointers[page_index] + page_offset;
+ WriteBlock(process, dest_addr, src_ptr, copy_amount);
break;
}
case PageType::RasterizerCachedMemory: {
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
FlushMode::Flush);
- WriteBlock(dest_addr, GetPointerFromVMA(current_vaddr), copy_amount);
- break;
- }
- case PageType::RasterizerCachedSpecial: {
- DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
- RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
- FlushMode::Flush);
-
- std::vector<u8> buffer(copy_amount);
- GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, buffer.data(), buffer.size());
- WriteBlock(dest_addr, buffer.data(), buffer.size());
+ WriteBlock(process, dest_addr, GetPointerFromVMA(process, current_vaddr), copy_amount);
break;
}
default:
@@ -687,46 +625,6 @@ void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) {
}
}
-template <>
-u8 ReadMMIO<u8>(MMIORegionPointer mmio_handler, VAddr addr) {
- return mmio_handler->Read8(addr);
-}
-
-template <>
-u16 ReadMMIO<u16>(MMIORegionPointer mmio_handler, VAddr addr) {
- return mmio_handler->Read16(addr);
-}
-
-template <>
-u32 ReadMMIO<u32>(MMIORegionPointer mmio_handler, VAddr addr) {
- return mmio_handler->Read32(addr);
-}
-
-template <>
-u64 ReadMMIO<u64>(MMIORegionPointer mmio_handler, VAddr addr) {
- return mmio_handler->Read64(addr);
-}
-
-template <>
-void WriteMMIO<u8>(MMIORegionPointer mmio_handler, VAddr addr, const u8 data) {
- mmio_handler->Write8(addr, data);
-}
-
-template <>
-void WriteMMIO<u16>(MMIORegionPointer mmio_handler, VAddr addr, const u16 data) {
- mmio_handler->Write16(addr, data);
-}
-
-template <>
-void WriteMMIO<u32>(MMIORegionPointer mmio_handler, VAddr addr, const u32 data) {
- mmio_handler->Write32(addr, data);
-}
-
-template <>
-void WriteMMIO<u64>(MMIORegionPointer mmio_handler, VAddr addr, const u64 data) {
- mmio_handler->Write64(addr, data);
-}
-
boost::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) {
if (addr == 0) {
return 0;
@@ -740,8 +638,6 @@ boost::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) {
return addr - DSP_RAM_VADDR + DSP_RAM_PADDR;
} else if (addr >= IO_AREA_VADDR && addr < IO_AREA_VADDR_END) {
return addr - IO_AREA_VADDR + IO_AREA_PADDR;
- } else if (addr >= N3DS_EXTRA_RAM_VADDR && addr < N3DS_EXTRA_RAM_VADDR_END) {
- return addr - N3DS_EXTRA_RAM_VADDR + N3DS_EXTRA_RAM_PADDR;
}
return boost::none;
@@ -750,7 +646,7 @@ boost::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) {
PAddr VirtualToPhysicalAddress(const VAddr addr) {
auto paddr = TryVirtualToPhysicalAddress(addr);
if (!paddr) {
- LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x%08X", addr);
+ LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x%016" PRIX64, addr);
// To help with debugging, set bit on address so that it's obviously invalid.
return addr | 0x80000000;
}
@@ -763,13 +659,11 @@ boost::optional<VAddr> PhysicalToVirtualAddress(const PAddr addr) {
} else if (addr >= VRAM_PADDR && addr < VRAM_PADDR_END) {
return addr - VRAM_PADDR + VRAM_VADDR;
} else if (addr >= FCRAM_PADDR && addr < FCRAM_PADDR_END) {
- return addr - FCRAM_PADDR + Kernel::g_current_process->GetLinearHeapAreaAddress();
+ return addr - FCRAM_PADDR + Core::CurrentProcess()->GetLinearHeapAreaAddress();
} else if (addr >= DSP_RAM_PADDR && addr < DSP_RAM_PADDR_END) {
return addr - DSP_RAM_PADDR + DSP_RAM_VADDR;
} else if (addr >= IO_AREA_PADDR && addr < IO_AREA_PADDR_END) {
return addr - IO_AREA_PADDR + IO_AREA_VADDR;
- } else if (addr >= N3DS_EXTRA_RAM_PADDR && addr < N3DS_EXTRA_RAM_PADDR_END) {
- return addr - N3DS_EXTRA_RAM_PADDR + N3DS_EXTRA_RAM_VADDR;
}
return boost::none;
diff --git a/src/core/memory.h b/src/core/memory.h
index 7e554f394..e9b8ca873 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -8,10 +8,12 @@
#include <cstddef>
#include <map>
#include <string>
+#include <tuple>
#include <vector>
+#include <boost/icl/interval_map.hpp>
#include <boost/optional.hpp>
#include "common/common_types.h"
-#include "core/mmio.h"
+#include "core/memory_hook.h"
namespace Kernel {
class Process;
@@ -23,12 +25,13 @@ namespace Memory {
* Page size used by the ARM architecture. This is the smallest granularity with which memory can
* be mapped.
*/
-const int PAGE_BITS = 12;
-const u64 PAGE_SIZE = 1 << PAGE_BITS;
-const u64 PAGE_MASK = PAGE_SIZE - 1;
-const size_t PAGE_TABLE_NUM_ENTRIES = 1ULL << (36 - PAGE_BITS);
+constexpr size_t PAGE_BITS = 12;
+constexpr u64 PAGE_SIZE = 1 << PAGE_BITS;
+constexpr u64 PAGE_MASK = PAGE_SIZE - 1;
+constexpr size_t ADDRESS_SPACE_BITS = 36;
+constexpr size_t PAGE_TABLE_NUM_ENTRIES = 1ULL << (ADDRESS_SPACE_BITS - PAGE_BITS);
-enum class PageType {
+enum class PageType : u8 {
/// Page is unmapped and should cause an access error.
Unmapped,
/// Page is mapped to regular memory. This is the only type you can get pointers to.
@@ -38,22 +41,28 @@ enum class PageType {
RasterizerCachedMemory,
/// Page is mapped to a I/O region. Writing and reading to this page is handled by functions.
Special,
- /// Page is mapped to a I/O region, but also needs to check for rasterizer cache flushing and
- /// invalidation
- RasterizerCachedSpecial,
};
struct SpecialRegion {
- VAddr base;
- u64 size;
- MMIORegionPointer handler;
+ enum class Type {
+ DebugHook,
+ IODevice,
+ } type;
+
+ MemoryHookPointer handler;
+
+ bool operator<(const SpecialRegion& other) const {
+ return std::tie(type, handler) < std::tie(other.type, other.handler);
+ }
+
+ bool operator==(const SpecialRegion& other) const {
+ return std::tie(type, handler) == std::tie(other.type, other.handler);
+ }
};
/**
* A (reasonably) fast way of allowing switchable and remappable process address spaces. It loosely
- * mimics the way a real CPU page table works, but instead is optimized for minimal decoding and
- * fetching requirements when accessing. In the usual case of an access to regular memory, it only
- * requires an indexed fetch and a check for NULL.
+ * mimics the way a real CPU page table works.
*/
struct PageTable {
/**
@@ -66,19 +75,13 @@ struct PageTable {
* Contains MMIO handlers that back memory regions whose entries in the `attribute` array is of
* type `Special`.
*/
- std::vector<SpecialRegion> special_regions;
+ boost::icl::interval_map<VAddr, std::set<SpecialRegion>> special_regions;
/**
* Array of fine grained page attributes. If it is set to any value other than `Memory`, then
* the corresponding entry in `pointers` MUST be set to null.
*/
std::array<PageType, PAGE_TABLE_NUM_ENTRIES> attributes;
-
- /**
- * Indicates the number of externally cached resources touching a page that should be
- * flushed before the memory is accessed
- */
- std::array<u8, PAGE_TABLE_NUM_ENTRIES> cached_res_count;
};
/// Physical memory regions as seen from the ARM11
@@ -98,12 +101,6 @@ enum : PAddr {
VRAM_SIZE = 0x00600000, ///< VRAM size (6MB)
VRAM_PADDR_END = VRAM_PADDR + VRAM_SIZE,
- /// New 3DS additional memory. Supposedly faster than regular FCRAM. Part of it can be used by
- /// applications and system modules if mapped via the ExHeader.
- N3DS_EXTRA_RAM_PADDR = 0x1F000000,
- N3DS_EXTRA_RAM_SIZE = 0x00400000, ///< New 3DS additional memory size (4MB)
- N3DS_EXTRA_RAM_PADDR_END = N3DS_EXTRA_RAM_PADDR + N3DS_EXTRA_RAM_SIZE,
-
/// DSP memory
DSP_RAM_PADDR = 0x1FF00000,
DSP_RAM_SIZE = 0x00080000, ///< DSP memory size (512KB)
@@ -119,7 +116,6 @@ enum : PAddr {
FCRAM_SIZE = 0x08000000, ///< FCRAM size on the Old 3DS (128MB)
FCRAM_N3DS_SIZE = 0x10000000, ///< FCRAM size on the New 3DS (256MB)
FCRAM_PADDR_END = FCRAM_PADDR + FCRAM_SIZE,
- FCRAM_N3DS_PADDR_END = FCRAM_PADDR + FCRAM_N3DS_SIZE,
};
/// Virtual user-space memory regions
@@ -129,31 +125,12 @@ enum : VAddr {
PROCESS_IMAGE_MAX_SIZE = 0x08000000,
PROCESS_IMAGE_VADDR_END = PROCESS_IMAGE_VADDR + PROCESS_IMAGE_MAX_SIZE,
- /// Area where IPC buffers are mapped onto.
- IPC_MAPPING_VADDR = 0x04000000,
- IPC_MAPPING_SIZE = 0x04000000,
- IPC_MAPPING_VADDR_END = IPC_MAPPING_VADDR + IPC_MAPPING_SIZE,
-
- /// Application heap (includes stack).
- HEAP_VADDR = 0x108000000,
- HEAP_SIZE = 0xF0000000,
- HEAP_VADDR_END = HEAP_VADDR + HEAP_SIZE,
-
- /// Area where shared memory buffers are mapped onto.
- SHARED_MEMORY_VADDR = 0x10000000,
- SHARED_MEMORY_SIZE = 0x04000000,
- SHARED_MEMORY_VADDR_END = SHARED_MEMORY_VADDR + SHARED_MEMORY_SIZE,
-
/// Maps 1:1 to an offset in FCRAM. Used for HW allocations that need to be linear in physical
/// memory.
LINEAR_HEAP_VADDR = 0x14000000,
LINEAR_HEAP_SIZE = 0x08000000,
LINEAR_HEAP_VADDR_END = LINEAR_HEAP_VADDR + LINEAR_HEAP_SIZE,
- /// Maps 1:1 to New 3DS additional memory
- N3DS_EXTRA_RAM_VADDR = 0x1E800000,
- N3DS_EXTRA_RAM_VADDR_END = N3DS_EXTRA_RAM_VADDR + N3DS_EXTRA_RAM_SIZE,
-
/// Maps 1:1 to the IO register area.
IO_AREA_VADDR = 0x1EC00000,
IO_AREA_VADDR_END = IO_AREA_VADDR + IO_AREA_SIZE,
@@ -176,14 +153,40 @@ enum : VAddr {
SHARED_PAGE_SIZE = 0x00001000,
SHARED_PAGE_VADDR_END = SHARED_PAGE_VADDR + SHARED_PAGE_SIZE,
- /// Area where TLS (Thread-Local Storage) buffers are allocated.
- TLS_AREA_VADDR = 0x228000000,
- TLS_ENTRY_SIZE = 0x200,
-
/// Equivalent to LINEAR_HEAP_VADDR, but expanded to cover the extra memory in the New 3DS.
NEW_LINEAR_HEAP_VADDR = 0x30000000,
NEW_LINEAR_HEAP_SIZE = 0x10000000,
NEW_LINEAR_HEAP_VADDR_END = NEW_LINEAR_HEAP_VADDR + NEW_LINEAR_HEAP_SIZE,
+
+ /// Area where TLS (Thread-Local Storage) buffers are allocated.
+ TLS_AREA_VADDR = NEW_LINEAR_HEAP_VADDR_END,
+ TLS_ENTRY_SIZE = 0x200,
+ TLS_AREA_SIZE = 0x10000000,
+ TLS_AREA_VADDR_END = TLS_AREA_VADDR + TLS_AREA_SIZE,
+
+ /// Application stack
+ STACK_AREA_VADDR = TLS_AREA_VADDR_END,
+ STACK_AREA_SIZE = 0x10000000,
+ STACK_AREA_VADDR_END = STACK_AREA_VADDR + STACK_AREA_SIZE,
+ DEFAULT_STACK_SIZE = 0x100000,
+
+ /// Application heap
+ /// Size is confirmed to be a static value on fw 3.0.0
+ HEAP_VADDR = 0x108000000,
+ HEAP_SIZE = 0x180000000,
+ HEAP_VADDR_END = HEAP_VADDR + HEAP_SIZE,
+
+ /// New map region
+ /// Size is confirmed to be a static value on fw 3.0.0
+ NEW_MAP_REGION_VADDR = HEAP_VADDR_END,
+ NEW_MAP_REGION_SIZE = 0x80000000,
+ NEW_MAP_REGION_VADDR_END = NEW_MAP_REGION_VADDR + NEW_MAP_REGION_SIZE,
+
+ /// Map region
+ /// Size is confirmed to be a static value on fw 3.0.0
+ MAP_REGION_VADDR = NEW_MAP_REGION_VADDR_END,
+ MAP_REGION_SIZE = 0x1000000000,
+ MAP_REGION_VADDR_END = MAP_REGION_VADDR + MAP_REGION_SIZE,
};
/// Currently active page table
@@ -243,30 +246,21 @@ boost::optional<VAddr> PhysicalToVirtualAddress(PAddr addr);
*/
u8* GetPhysicalPointer(PAddr address);
-/**
- * Adds the supplied value to the rasterizer resource cache counter of each
- * page touching the region.
- */
-void RasterizerMarkRegionCached(PAddr start, u64 size, int count_delta);
-
-/**
- * Flushes any externally cached rasterizer resources touching the given region.
- */
-void RasterizerFlushRegion(PAddr start, u64 size);
-
-/**
- * Flushes and invalidates any externally cached rasterizer resources touching the given region.
- */
-void RasterizerFlushAndInvalidateRegion(PAddr start, u64 size);
-
enum class FlushMode {
/// Write back modified surfaces to RAM
Flush,
+ /// Remove region from the cache
+ Invalidate,
/// Write back modified surfaces to RAM, and also remove them from the cache
FlushAndInvalidate,
};
/**
+ * Mark each page touching the region as cached.
+ */
+void RasterizerMarkRegionCached(VAddr start, u64 size, bool cached);
+
+/**
* Flushes and invalidates any externally cached rasterizer resources touching the given virtual
* address region.
*/
diff --git a/src/core/memory_hook.h b/src/core/memory_hook.h
new file mode 100644
index 000000000..feebd850a
--- /dev/null
+++ b/src/core/memory_hook.h
@@ -0,0 +1,46 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <boost/optional.hpp>
+#include "common/common_types.h"
+
+namespace Memory {
+
+/**
+ * Memory hooks have two purposes:
+ * 1. To allow reads and writes to a region of memory to be intercepted. This is used to implement
+ * texture forwarding and memory breakpoints for debugging.
+ * 2. To allow for the implementation of MMIO devices.
+ *
+ * A hook may be mapped to multiple regions of memory.
+ *
+ * If a boost::none or false is returned from a function, the read/write request is passed through
+ * to the underlying memory region.
+ */
+class MemoryHook {
+public:
+ virtual ~MemoryHook() = default;
+
+ virtual boost::optional<bool> IsValidAddress(VAddr addr) = 0;
+
+ virtual boost::optional<u8> Read8(VAddr addr) = 0;
+ virtual boost::optional<u16> Read16(VAddr addr) = 0;
+ virtual boost::optional<u32> Read32(VAddr addr) = 0;
+ virtual boost::optional<u64> Read64(VAddr addr) = 0;
+
+ virtual bool ReadBlock(VAddr src_addr, void* dest_buffer, size_t size) = 0;
+
+ virtual bool Write8(VAddr addr, u8 data) = 0;
+ virtual bool Write16(VAddr addr, u16 data) = 0;
+ virtual bool Write32(VAddr addr, u32 data) = 0;
+ virtual bool Write64(VAddr addr, u64 data) = 0;
+
+ virtual bool WriteBlock(VAddr dest_addr, const void* src_buffer, size_t size) = 0;
+};
+
+using MemoryHookPointer = std::shared_ptr<MemoryHook>;
+} // namespace Memory
diff --git a/src/core/memory_setup.h b/src/core/memory_setup.h
index ff4dcc936..9a1a4f4be 100644
--- a/src/core/memory_setup.h
+++ b/src/core/memory_setup.h
@@ -5,7 +5,7 @@
#pragma once
#include "common/common_types.h"
-#include "core/mmio.h"
+#include "core/memory_hook.h"
namespace Memory {
@@ -26,7 +26,11 @@ void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target);
* @param size The amount of bytes to map. Must be page-aligned.
* @param mmio_handler The handler that backs the mapping.
*/
-void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MMIORegionPointer mmio_handler);
+void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer mmio_handler);
void UnmapRegion(PageTable& page_table, VAddr base, u64 size);
-}
+
+void AddDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook);
+void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook);
+
+} // namespace Memory
diff --git a/src/core/mmio.h b/src/core/mmio.h
deleted file mode 100644
index f45126da8..000000000
--- a/src/core/mmio.h
+++ /dev/null
@@ -1,38 +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 "common/common_types.h"
-
-namespace Memory {
-
-/**
- * Represents a device with memory mapped IO.
- * A device may be mapped to multiple regions of memory.
- */
-class MMIORegion {
-public:
- virtual ~MMIORegion() = default;
-
- virtual bool IsValidAddress(VAddr addr) = 0;
-
- virtual u8 Read8(VAddr addr) = 0;
- virtual u16 Read16(VAddr addr) = 0;
- virtual u32 Read32(VAddr addr) = 0;
- virtual u64 Read64(VAddr addr) = 0;
-
- virtual bool ReadBlock(VAddr src_addr, void* dest_buffer, size_t size) = 0;
-
- virtual void Write8(VAddr addr, u8 data) = 0;
- virtual void Write16(VAddr addr, u16 data) = 0;
- virtual void Write32(VAddr addr, u32 data) = 0;
- virtual void Write64(VAddr addr, u64 data) = 0;
-
- virtual bool WriteBlock(VAddr dest_addr, const void* src_buffer, size_t size) = 0;
-};
-
-using MMIORegionPointer = std::shared_ptr<MMIORegion>;
-};
diff --git a/src/core/settings.h b/src/core/settings.h
index 56fb189ae..2c94caab7 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -100,16 +100,15 @@ enum Values {
};
static const std::array<const char*, NumAnalogs> mapping = {{
- "lstick", "rstick",
+ "lstick",
+ "rstick",
}};
} // namespace NativeAnalog
-enum class CpuCore {
- Unicorn,
- Dynarmic,
-};
-
struct Values {
+ // System
+ bool use_docked_mode;
+
// Controls
std::array<std::string, NativeButton::NumButtons> buttons;
std::array<std::string, NativeAnalog::NumAnalogs> analogs;
@@ -117,7 +116,7 @@ struct Values {
std::string touch_device;
// Core
- CpuCore cpu_core;
+ bool use_cpu_jit;
// Data Storage
bool use_virtual_sd;
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index a613889f0..b3ffcd443 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -7,13 +7,16 @@
#include "common/assert.h"
#include "common/file_util.h"
#include "common/scm_rev.h"
+#ifdef ARCHITECTURE_x86_64
#include "common/x64/cpu_detect.h"
+#endif
#include "core/core.h"
#include "core/settings.h"
#include "core/telemetry_session.h"
namespace Core {
+#ifdef ARCHITECTURE_x86_64
static const char* CpuVendorToStr(Common::CPUVendor vendor) {
switch (vendor) {
case Common::CPUVendor::INTEL:
@@ -25,6 +28,7 @@ static const char* CpuVendorToStr(Common::CPUVendor vendor) {
}
UNREACHABLE();
}
+#endif
static u64 GenerateTelemetryId() {
u64 telemetry_id{};
@@ -113,7 +117,8 @@ TelemetrySession::TelemetrySession() {
AddField(Telemetry::FieldType::App, "BuildDate", Common::g_build_date);
AddField(Telemetry::FieldType::App, "BuildName", Common::g_build_name);
- // Log user system information
+// Log user system information
+#ifdef ARCHITECTURE_x86_64
AddField(Telemetry::FieldType::UserSystem, "CPU_Model", Common::GetCPUCaps().cpu_string);
AddField(Telemetry::FieldType::UserSystem, "CPU_BrandString",
Common::GetCPUCaps().brand_string);
@@ -135,6 +140,9 @@ TelemetrySession::TelemetrySession() {
Common::GetCPUCaps().sse4_1);
AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE42",
Common::GetCPUCaps().sse4_2);
+#else
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Model", "Other");
+#endif
#ifdef __APPLE__
AddField(Telemetry::FieldType::UserSystem, "OsPlatform", "Apple");
#elif defined(_WIN32)
@@ -146,12 +154,13 @@ TelemetrySession::TelemetrySession() {
#endif
// Log user configuration information
- AddField(Telemetry::FieldType::UserConfig, "Core_CpuCore",
- static_cast<int>(Settings::values.cpu_core));
+ AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit);
AddField(Telemetry::FieldType::UserConfig, "Renderer_ResolutionFactor",
Settings::values.resolution_factor);
AddField(Telemetry::FieldType::UserConfig, "Renderer_ToggleFramelimit",
Settings::values.toggle_framelimit);
+ AddField(Telemetry::FieldType::UserConfig, "System_UseDockedMode",
+ Settings::values.use_docked_mode);
}
TelemetrySession::~TelemetrySession() {
diff --git a/src/core/tracer/citrace.h b/src/core/tracer/citrace.h
index 215f86359..21fdc127a 100644
--- a/src/core/tracer/citrace.h
+++ b/src/core/tracer/citrace.h
@@ -97,4 +97,4 @@ struct CTStreamElement {
};
#pragma pack()
-}
+} // namespace CiTrace
diff --git a/src/core/tracer/recorder.cpp b/src/core/tracer/recorder.cpp
index 55b3b5efc..f3b0d6a8f 100644
--- a/src/core/tracer/recorder.cpp
+++ b/src/core/tracer/recorder.cpp
@@ -205,4 +205,4 @@ template void Recorder::RegisterWritten(u32, u8);
template void Recorder::RegisterWritten(u32, u16);
template void Recorder::RegisterWritten(u32, u32);
template void Recorder::RegisterWritten(u32, u64);
-}
+} // namespace CiTrace
diff --git a/src/core/tracer/recorder.h b/src/core/tracer/recorder.h
index 39e6ec4fd..629c2f6d2 100644
--- a/src/core/tracer/recorder.h
+++ b/src/core/tracer/recorder.h
@@ -63,9 +63,9 @@ private:
CTStreamElement data;
/**
- * Extra data to store along "core" data.
- * This is e.g. used for data used in MemoryUpdates.
- */
+ * Extra data to store along "core" data.
+ * This is e.g. used for data used in MemoryUpdates.
+ */
std::vector<u8> extra_data;
/// Optional CRC hash (e.g. for hashing memory regions)
@@ -84,4 +84,4 @@ private:
std::unordered_map<boost::crc_32_type::value_type /*hash*/, u32 /*file_offset*/> memory_regions;
};
-} // namespace
+} // namespace CiTrace
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 92792a702..1c7db28c0 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -1,25 +1,18 @@
-set(SRCS
- analog_from_button.cpp
- keyboard.cpp
- main.cpp
- motion_emu.cpp
- )
+add_library(input_common STATIC
+ analog_from_button.cpp
+ analog_from_button.h
+ keyboard.cpp
+ keyboard.h
+ main.cpp
+ main.h
+ motion_emu.cpp
+ motion_emu.h
-set(HEADERS
- analog_from_button.h
- keyboard.h
- main.h
- motion_emu.h
- )
+ $<$<BOOL:${SDL2_FOUND}>:sdl/sdl.cpp sdl/sdl.h>
+)
-if(SDL2_FOUND)
- set(SRCS ${SRCS} sdl/sdl.cpp)
- set(HEADERS ${HEADERS} sdl/sdl.h)
-endif()
-
-create_directory_groups(${SRCS} ${HEADERS})
+create_target_directory_groups(input_common)
-add_library(input_common STATIC ${SRCS} ${HEADERS})
target_link_libraries(input_common PUBLIC core PRIVATE common)
if(SDL2_FOUND)
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 95d40f09f..b12623d55 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -52,7 +52,8 @@ MotionEmu* GetMotionEmu() {
std::string GenerateKeyboardParam(int key_code) {
Common::ParamPackage param{
- {"engine", "keyboard"}, {"code", std::to_string(key_code)},
+ {"engine", "keyboard"},
+ {"code", std::to_string(key_code)},
};
return param.Serialize();
}
diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp
index 88b557c5d..3b87d6b65 100644
--- a/src/input_common/sdl/sdl.cpp
+++ b/src/input_common/sdl/sdl.cpp
@@ -425,7 +425,7 @@ std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
pollers.push_back(std::make_unique<SDLButtonPoller>());
break;
}
- return std::move(pollers);
+ return pollers;
}
} // namespace Polling
} // namespace SDL
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index 1b8fb2a9f..12f1b93e0 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,20 +1,16 @@
-set(SRCS
- common/param_package.cpp
- core/arm/arm_test_common.cpp
- core/core_timing.cpp
- core/file_sys/path_parser.cpp
- core/memory/memory.cpp
- glad.cpp
- tests.cpp
- )
+add_executable(tests
+ common/param_package.cpp
+ core/arm/arm_test_common.cpp
+ core/arm/arm_test_common.h
+ core/core_timing.cpp
+ core/file_sys/path_parser.cpp
+ core/memory/memory.cpp
+ glad.cpp
+ tests.cpp
+)
-set(HEADERS
- core/arm/arm_test_common.h
- )
+create_target_directory_groups(tests)
-create_directory_groups(${SRCS} ${HEADERS})
-
-add_executable(tests ${SRCS} ${HEADERS})
target_link_libraries(tests PRIVATE common core)
target_link_libraries(tests PRIVATE glad) # To support linker work-around
target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} catch-single-include Threads::Threads)
diff --git a/src/tests/common/param_package.cpp b/src/tests/common/param_package.cpp
index efec2cc86..19d372236 100644
--- a/src/tests/common/param_package.cpp
+++ b/src/tests/common/param_package.cpp
@@ -10,7 +10,9 @@ namespace Common {
TEST_CASE("ParamPackage", "[common]") {
ParamPackage original{
- {"abc", "xyz"}, {"def", "42"}, {"jkl", "$$:1:$2$,3"},
+ {"abc", "xyz"},
+ {"def", "42"},
+ {"jkl", "$$:1:$2$,3"},
};
original.Set("ghi", 3.14f);
ParamPackage copy(original.Serialize());
diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp
index 2339bdfb8..7f9f27e19 100644
--- a/src/tests/core/arm/arm_test_common.cpp
+++ b/src/tests/core/arm/arm_test_common.cpp
@@ -15,12 +15,12 @@ static Memory::PageTable* page_table = nullptr;
TestEnvironment::TestEnvironment(bool mutable_memory_)
: mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) {
- Kernel::g_current_process = Kernel::Process::Create("");
- page_table = &Kernel::g_current_process->vm_manager.page_table;
+ Core::CurrentProcess() = Kernel::Process::Create("");
+ page_table = &Core::CurrentProcess()->vm_manager.page_table;
page_table->pointers.fill(nullptr);
+ page_table->special_regions.clear();
page_table->attributes.fill(Memory::PageType::Unmapped);
- page_table->cached_res_count.fill(0);
Memory::MapIoRegion(*page_table, 0x00000000, 0x80000000, test_memory);
Memory::MapIoRegion(*page_table, 0x80000000, 0x80000000, test_memory);
@@ -62,11 +62,11 @@ void TestEnvironment::ClearWriteRecords() {
TestEnvironment::TestMemory::~TestMemory() {}
-bool TestEnvironment::TestMemory::IsValidAddress(VAddr addr) {
+boost::optional<bool> TestEnvironment::TestMemory::IsValidAddress(VAddr addr) {
return true;
}
-u8 TestEnvironment::TestMemory::Read8(VAddr addr) {
+boost::optional<u8> TestEnvironment::TestMemory::Read8(VAddr addr) {
auto iter = data.find(addr);
if (iter == data.end()) {
return addr; // Some arbitrary data
@@ -74,16 +74,16 @@ u8 TestEnvironment::TestMemory::Read8(VAddr addr) {
return iter->second;
}
-u16 TestEnvironment::TestMemory::Read16(VAddr addr) {
- return Read8(addr) | static_cast<u16>(Read8(addr + 1)) << 8;
+boost::optional<u16> TestEnvironment::TestMemory::Read16(VAddr addr) {
+ return *Read8(addr) | static_cast<u16>(*Read8(addr + 1)) << 8;
}
-u32 TestEnvironment::TestMemory::Read32(VAddr addr) {
- return Read16(addr) | static_cast<u32>(Read16(addr + 2)) << 16;
+boost::optional<u32> TestEnvironment::TestMemory::Read32(VAddr addr) {
+ return *Read16(addr) | static_cast<u32>(*Read16(addr + 2)) << 16;
}
-u64 TestEnvironment::TestMemory::Read64(VAddr addr) {
- return Read32(addr) | static_cast<u64>(Read32(addr + 4)) << 32;
+boost::optional<u64> TestEnvironment::TestMemory::Read64(VAddr addr) {
+ return *Read32(addr) | static_cast<u64>(*Read32(addr + 4)) << 32;
}
bool TestEnvironment::TestMemory::ReadBlock(VAddr src_addr, void* dest_buffer, size_t size) {
@@ -91,34 +91,38 @@ bool TestEnvironment::TestMemory::ReadBlock(VAddr src_addr, void* dest_buffer, s
u8* data = static_cast<u8*>(dest_buffer);
for (size_t i = 0; i < size; i++, addr++, data++) {
- *data = Read8(addr);
+ *data = *Read8(addr);
}
return true;
}
-void TestEnvironment::TestMemory::Write8(VAddr addr, u8 data) {
+bool TestEnvironment::TestMemory::Write8(VAddr addr, u8 data) {
env->write_records.emplace_back(8, addr, data);
if (env->mutable_memory)
env->SetMemory8(addr, data);
+ return true;
}
-void TestEnvironment::TestMemory::Write16(VAddr addr, u16 data) {
+bool TestEnvironment::TestMemory::Write16(VAddr addr, u16 data) {
env->write_records.emplace_back(16, addr, data);
if (env->mutable_memory)
env->SetMemory16(addr, data);
+ return true;
}
-void TestEnvironment::TestMemory::Write32(VAddr addr, u32 data) {
+bool TestEnvironment::TestMemory::Write32(VAddr addr, u32 data) {
env->write_records.emplace_back(32, addr, data);
if (env->mutable_memory)
env->SetMemory32(addr, data);
+ return true;
}
-void TestEnvironment::TestMemory::Write64(VAddr addr, u64 data) {
+bool TestEnvironment::TestMemory::Write64(VAddr addr, u64 data) {
env->write_records.emplace_back(64, addr, data);
if (env->mutable_memory)
env->SetMemory64(addr, data);
+ return true;
}
bool TestEnvironment::TestMemory::WriteBlock(VAddr dest_addr, const void* src_buffer, size_t size) {
diff --git a/src/tests/core/arm/arm_test_common.h b/src/tests/core/arm/arm_test_common.h
index 592c28594..b66922d61 100644
--- a/src/tests/core/arm/arm_test_common.h
+++ b/src/tests/core/arm/arm_test_common.h
@@ -7,7 +7,7 @@
#include <vector>
#include "common/common_types.h"
-#include "core/mmio.h"
+#include "core/memory_hook.h"
namespace ArmTests {
@@ -51,25 +51,25 @@ public:
private:
friend struct TestMemory;
- struct TestMemory final : Memory::MMIORegion {
+ struct TestMemory final : Memory::MemoryHook {
explicit TestMemory(TestEnvironment* env_) : env(env_) {}
TestEnvironment* env;
~TestMemory() override;
- bool IsValidAddress(VAddr addr) override;
+ boost::optional<bool> IsValidAddress(VAddr addr) override;
- u8 Read8(VAddr addr) override;
- u16 Read16(VAddr addr) override;
- u32 Read32(VAddr addr) override;
- u64 Read64(VAddr addr) override;
+ boost::optional<u8> Read8(VAddr addr) override;
+ boost::optional<u16> Read16(VAddr addr) override;
+ boost::optional<u32> Read32(VAddr addr) override;
+ boost::optional<u64> Read64(VAddr addr) override;
bool ReadBlock(VAddr src_addr, void* dest_buffer, size_t size) override;
- void Write8(VAddr addr, u8 data) override;
- void Write16(VAddr addr, u16 data) override;
- void Write32(VAddr addr, u32 data) override;
- void Write64(VAddr addr, u64 data) override;
+ bool Write8(VAddr addr, u8 data) override;
+ bool Write16(VAddr addr, u16 data) override;
+ bool Write32(VAddr addr, u32 data) override;
+ bool Write64(VAddr addr, u64 data) override;
bool WriteBlock(VAddr dest_addr, const void* src_buffer, size_t size) override;
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 3fd177c46..a710c4bc5 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -1,23 +1,50 @@
-set(SRCS
- renderer_base.cpp
- renderer_opengl/gl_shader_util.cpp
- renderer_opengl/gl_state.cpp
- renderer_opengl/renderer_opengl.cpp
- video_core.cpp
- )
+add_library(video_core STATIC
+ command_processor.cpp
+ command_processor.h
+ debug_utils/debug_utils.cpp
+ debug_utils/debug_utils.h
+ engines/fermi_2d.cpp
+ engines/fermi_2d.h
+ engines/maxwell_3d.cpp
+ engines/maxwell_3d.h
+ engines/maxwell_compute.cpp
+ engines/maxwell_compute.h
+ gpu.cpp
+ gpu.h
+ macro_interpreter.cpp
+ macro_interpreter.h
+ memory_manager.cpp
+ memory_manager.h
+ rasterizer_interface.h
+ renderer_base.cpp
+ renderer_base.h
+ renderer_opengl/gl_rasterizer.cpp
+ renderer_opengl/gl_rasterizer.h
+ renderer_opengl/gl_rasterizer_cache.cpp
+ renderer_opengl/gl_rasterizer_cache.h
+ renderer_opengl/gl_resource_manager.h
+ renderer_opengl/gl_shader_decompiler.cpp
+ renderer_opengl/gl_shader_decompiler.h
+ renderer_opengl/gl_shader_gen.cpp
+ renderer_opengl/gl_shader_gen.h
+ renderer_opengl/gl_shader_util.cpp
+ renderer_opengl/gl_shader_util.h
+ renderer_opengl/gl_state.cpp
+ renderer_opengl/gl_state.h
+ renderer_opengl/gl_stream_buffer.cpp
+ renderer_opengl/gl_stream_buffer.h
+ renderer_opengl/maxwell_to_gl.h
+ renderer_opengl/renderer_opengl.cpp
+ renderer_opengl/renderer_opengl.h
+ textures/decoders.cpp
+ textures/decoders.h
+ textures/texture.h
+ utils.h
+ video_core.cpp
+ video_core.h
+)
-set(HEADERS
- renderer_base.h
- renderer_opengl/gl_resource_manager.h
- renderer_opengl/gl_shader_util.h
- renderer_opengl/gl_state.h
- renderer_opengl/renderer_opengl.h
- utils.h
- video_core.h
- )
+create_target_directory_groups(video_core)
-create_directory_groups(${SRCS} ${HEADERS})
-
-add_library(video_core STATIC ${SRCS} ${HEADERS})
target_link_libraries(video_core PUBLIC common core)
target_link_libraries(video_core PRIVATE glad)
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp
new file mode 100644
index 000000000..d4cdb4ab2
--- /dev/null
+++ b/src/video_core/command_processor.cpp
@@ -0,0 +1,149 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <cstddef>
+#include <memory>
+#include <utility>
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/microprofile.h"
+#include "common/vector_math.h"
+#include "core/memory.h"
+#include "core/tracer/recorder.h"
+#include "video_core/command_processor.h"
+#include "video_core/engines/fermi_2d.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/engines/maxwell_compute.h"
+#include "video_core/gpu.h"
+#include "video_core/renderer_base.h"
+#include "video_core/video_core.h"
+
+namespace Tegra {
+
+enum class BufferMethods {
+ BindObject = 0,
+ SetGraphMacroCode = 0x45,
+ SetGraphMacroCodeArg = 0x46,
+ SetGraphMacroEntry = 0x47,
+ CountBufferMethods = 0x100,
+};
+
+void GPU::WriteReg(u32 method, u32 subchannel, u32 value, u32 remaining_params) {
+ LOG_WARNING(HW_GPU, "Processing method %08X on subchannel %u value %08X remaining params %u",
+ method, subchannel, value, remaining_params);
+
+ if (method == static_cast<u32>(BufferMethods::SetGraphMacroEntry)) {
+ // Prepare to upload a new macro, reset the upload counter.
+ LOG_DEBUG(HW_GPU, "Uploading GPU macro %08X", value);
+ current_macro_entry = value;
+ current_macro_code.clear();
+ return;
+ }
+
+ if (method == static_cast<u32>(BufferMethods::SetGraphMacroCodeArg)) {
+ // Append a new code word to the current macro.
+ current_macro_code.push_back(value);
+
+ // There are no more params remaining, submit the code to the 3D engine.
+ if (remaining_params == 0) {
+ maxwell_3d->SubmitMacroCode(current_macro_entry, std::move(current_macro_code));
+ current_macro_entry = InvalidGraphMacroEntry;
+ current_macro_code.clear();
+ }
+
+ return;
+ }
+
+ if (method == static_cast<u32>(BufferMethods::BindObject)) {
+ // Bind the current subchannel to the desired engine id.
+ LOG_DEBUG(HW_GPU, "Binding subchannel %u to engine %u", subchannel, value);
+ ASSERT(bound_engines.find(subchannel) == bound_engines.end());
+ bound_engines[subchannel] = static_cast<EngineID>(value);
+ return;
+ }
+
+ if (method < static_cast<u32>(BufferMethods::CountBufferMethods)) {
+ // TODO(Subv): Research and implement these methods.
+ LOG_ERROR(HW_GPU, "Special buffer methods other than Bind are not implemented");
+ return;
+ }
+
+ ASSERT(bound_engines.find(subchannel) != bound_engines.end());
+
+ const EngineID engine = bound_engines[subchannel];
+
+ switch (engine) {
+ case EngineID::FERMI_TWOD_A:
+ fermi_2d->WriteReg(method, value);
+ break;
+ case EngineID::MAXWELL_B:
+ maxwell_3d->WriteReg(method, value, remaining_params);
+ break;
+ case EngineID::MAXWELL_COMPUTE_B:
+ maxwell_compute->WriteReg(method, value);
+ break;
+ default:
+ UNIMPLEMENTED();
+ }
+}
+
+void GPU::ProcessCommandList(GPUVAddr address, u32 size) {
+ // TODO(Subv): PhysicalToVirtualAddress is a misnomer, it converts a GPU VAddr into an
+ // application VAddr.
+ const VAddr head_address = memory_manager->PhysicalToVirtualAddress(address);
+ VAddr current_addr = head_address;
+ while (current_addr < head_address + size * sizeof(CommandHeader)) {
+ const CommandHeader header = {Memory::Read32(current_addr)};
+ current_addr += sizeof(u32);
+
+ switch (header.mode.Value()) {
+ case SubmissionMode::IncreasingOld:
+ case SubmissionMode::Increasing: {
+ // Increase the method value with each argument.
+ for (unsigned i = 0; i < header.arg_count; ++i) {
+ WriteReg(header.method + i, header.subchannel, Memory::Read32(current_addr),
+ header.arg_count - i - 1);
+ current_addr += sizeof(u32);
+ }
+ break;
+ }
+ case SubmissionMode::NonIncreasingOld:
+ case SubmissionMode::NonIncreasing: {
+ // Use the same method value for all arguments.
+ for (unsigned i = 0; i < header.arg_count; ++i) {
+ WriteReg(header.method, header.subchannel, Memory::Read32(current_addr),
+ header.arg_count - i - 1);
+ current_addr += sizeof(u32);
+ }
+ break;
+ }
+ case SubmissionMode::IncreaseOnce: {
+ ASSERT(header.arg_count.Value() >= 1);
+
+ // Use the original method for the first argument and then the next method for all other
+ // arguments.
+ WriteReg(header.method, header.subchannel, Memory::Read32(current_addr),
+ header.arg_count - 1);
+ current_addr += sizeof(u32);
+
+ for (unsigned i = 1; i < header.arg_count; ++i) {
+ WriteReg(header.method + 1, header.subchannel, Memory::Read32(current_addr),
+ header.arg_count - i - 1);
+ current_addr += sizeof(u32);
+ }
+ break;
+ }
+ case SubmissionMode::Inline: {
+ // The register value is stored in the bits 16-28 as an immediate
+ WriteReg(header.method, header.subchannel, header.inline_data, 0);
+ break;
+ }
+ default:
+ UNIMPLEMENTED();
+ }
+ }
+}
+
+} // namespace Tegra
diff --git a/src/video_core/command_processor.h b/src/video_core/command_processor.h
new file mode 100644
index 000000000..f7214ffec
--- /dev/null
+++ b/src/video_core/command_processor.h
@@ -0,0 +1,37 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <type_traits>
+#include "common/bit_field.h"
+#include "common/common_types.h"
+
+namespace Tegra {
+
+enum class SubmissionMode : u32 {
+ IncreasingOld = 0,
+ Increasing = 1,
+ NonIncreasingOld = 2,
+ NonIncreasing = 3,
+ Inline = 4,
+ IncreaseOnce = 5
+};
+
+union CommandHeader {
+ u32 hex;
+
+ BitField<0, 13, u32> method;
+ BitField<13, 3, u32> subchannel;
+
+ BitField<16, 13, u32> arg_count;
+ BitField<16, 13, u32> inline_data;
+
+ BitField<29, 3, SubmissionMode> mode;
+};
+static_assert(std::is_standard_layout<CommandHeader>::value == true,
+ "CommandHeader does not use standard layout");
+static_assert(sizeof(CommandHeader) == sizeof(u32), "CommandHeader has incorrect size!");
+
+} // namespace Tegra
diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp
new file mode 100644
index 000000000..22d44aab2
--- /dev/null
+++ b/src/video_core/debug_utils/debug_utils.cpp
@@ -0,0 +1,64 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <condition_variable>
+#include <cstdint>
+#include <cstring>
+#include <fstream>
+#include <map>
+#include <mutex>
+#include <string>
+
+#include "common/assert.h"
+#include "common/bit_field.h"
+#include "common/color.h"
+#include "common/common_types.h"
+#include "common/file_util.h"
+#include "common/logging/log.h"
+#include "common/math_util.h"
+#include "common/vector_math.h"
+#include "video_core/debug_utils/debug_utils.h"
+
+namespace Tegra {
+
+void DebugContext::DoOnEvent(Event event, void* data) {
+ {
+ std::unique_lock<std::mutex> lock(breakpoint_mutex);
+
+ // TODO(Subv): Commit the rasterizer's caches so framebuffers, render targets, etc. will
+ // show on debug widgets
+
+ // TODO: Should stop the CPU thread here once we multithread emulation.
+
+ active_breakpoint = event;
+ at_breakpoint = true;
+
+ // Tell all observers that we hit a breakpoint
+ for (auto& breakpoint_observer : breakpoint_observers) {
+ breakpoint_observer->OnMaxwellBreakPointHit(event, data);
+ }
+
+ // Wait until another thread tells us to Resume()
+ resume_from_breakpoint.wait(lock, [&] { return !at_breakpoint; });
+ }
+}
+
+void DebugContext::Resume() {
+ {
+ std::lock_guard<std::mutex> lock(breakpoint_mutex);
+
+ // Tell all observers that we are about to resume
+ for (auto& breakpoint_observer : breakpoint_observers) {
+ breakpoint_observer->OnMaxwellResume();
+ }
+
+ // Resume the waiting thread (i.e. OnEvent())
+ at_breakpoint = false;
+ }
+
+ resume_from_breakpoint.notify_one();
+}
+
+} // namespace Tegra
diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h
new file mode 100644
index 000000000..bbba8e380
--- /dev/null
+++ b/src/video_core/debug_utils/debug_utils.h
@@ -0,0 +1,163 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <algorithm>
+#include <array>
+#include <condition_variable>
+#include <iterator>
+#include <list>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <utility>
+#include <vector>
+#include "common/common_types.h"
+#include "common/vector_math.h"
+
+namespace Tegra {
+
+class DebugContext {
+public:
+ enum class Event {
+ FirstEvent = 0,
+
+ MaxwellCommandLoaded = FirstEvent,
+ MaxwellCommandProcessed,
+ IncomingPrimitiveBatch,
+ FinishedPrimitiveBatch,
+
+ NumEvents
+ };
+
+ /**
+ * Inherit from this class to be notified of events registered to some debug context.
+ * Most importantly this is used for our debugger GUI.
+ *
+ * To implement event handling, override the OnMaxwellBreakPointHit and OnMaxwellResume methods.
+ * @warning All BreakPointObservers need to be on the same thread to guarantee thread-safe state
+ * access
+ * @todo Evaluate an alternative interface, in which there is only one managing observer and
+ * multiple child observers running (by design) on the same thread.
+ */
+ class BreakPointObserver {
+ public:
+ /// Constructs the object such that it observes events of the given DebugContext.
+ BreakPointObserver(std::shared_ptr<DebugContext> debug_context)
+ : context_weak(debug_context) {
+ std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex);
+ debug_context->breakpoint_observers.push_back(this);
+ }
+
+ virtual ~BreakPointObserver() {
+ auto context = context_weak.lock();
+ if (context) {
+ std::unique_lock<std::mutex> lock(context->breakpoint_mutex);
+ context->breakpoint_observers.remove(this);
+
+ // If we are the last observer to be destroyed, tell the debugger context that
+ // it is free to continue. In particular, this is required for a proper yuzu
+ // shutdown, when the emulation thread is waiting at a breakpoint.
+ if (context->breakpoint_observers.empty())
+ context->Resume();
+ }
+ }
+
+ /**
+ * Action to perform when a breakpoint was reached.
+ * @param event Type of event which triggered the breakpoint
+ * @param data Optional data pointer (if unused, this is a nullptr)
+ * @note This function will perform nothing unless it is overridden in the child class.
+ */
+ virtual void OnMaxwellBreakPointHit(Event event, void* data) {}
+
+ /**
+ * Action to perform when emulation is resumed from a breakpoint.
+ * @note This function will perform nothing unless it is overridden in the child class.
+ */
+ virtual void OnMaxwellResume() {}
+
+ protected:
+ /**
+ * Weak context pointer. This need not be valid, so when requesting a shared_ptr via
+ * context_weak.lock(), always compare the result against nullptr.
+ */
+ std::weak_ptr<DebugContext> context_weak;
+ };
+
+ /**
+ * Simple structure defining a breakpoint state
+ */
+ struct BreakPoint {
+ bool enabled = false;
+ };
+
+ /**
+ * Static constructor used to create a shared_ptr of a DebugContext.
+ */
+ static std::shared_ptr<DebugContext> Construct() {
+ return std::shared_ptr<DebugContext>(new DebugContext);
+ }
+
+ /**
+ * Used by the emulation core when a given event has happened. If a breakpoint has been set
+ * for this event, OnEvent calls the event handlers of the registered breakpoint observers.
+ * The current thread then is halted until Resume() is called from another thread (or until
+ * emulation is stopped).
+ * @param event Event which has happened
+ * @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until
+ * Resume() is called.
+ */
+ void OnEvent(Event event, void* data) {
+ // This check is left in the header to allow the compiler to inline it.
+ if (!breakpoints[(int)event].enabled)
+ return;
+ // For the rest of event handling, call a separate function.
+ DoOnEvent(event, data);
+ }
+
+ void DoOnEvent(Event event, void* data);
+
+ /**
+ * Resume from the current breakpoint.
+ * @warning Calling this from the same thread that OnEvent was called in will cause a deadlock.
+ * Calling from any other thread is safe.
+ */
+ void Resume();
+
+ /**
+ * Delete all set breakpoints and resume emulation.
+ */
+ void ClearBreakpoints() {
+ for (auto& bp : breakpoints) {
+ bp.enabled = false;
+ }
+ Resume();
+ }
+
+ // TODO: Evaluate if access to these members should be hidden behind a public interface.
+ std::array<BreakPoint, (int)Event::NumEvents> breakpoints;
+ Event active_breakpoint;
+ bool at_breakpoint = false;
+
+private:
+ /**
+ * Private default constructor to make sure people always construct this through Construct()
+ * instead.
+ */
+ DebugContext() = default;
+
+ /// Mutex protecting current breakpoint state and the observer list.
+ std::mutex breakpoint_mutex;
+
+ /// Used by OnEvent to wait for resumption.
+ std::condition_variable resume_from_breakpoint;
+
+ /// List of registered observers
+ std::list<BreakPointObserver*> breakpoint_observers;
+};
+
+} // namespace Tegra
diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp
new file mode 100644
index 000000000..7aab163dc
--- /dev/null
+++ b/src/video_core/engines/fermi_2d.cpp
@@ -0,0 +1,13 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "video_core/engines/fermi_2d.h"
+
+namespace Tegra {
+namespace Engines {
+
+void Fermi2D::WriteReg(u32 method, u32 value) {}
+
+} // namespace Engines
+} // namespace Tegra
diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h
new file mode 100644
index 000000000..8967ddede
--- /dev/null
+++ b/src/video_core/engines/fermi_2d.h
@@ -0,0 +1,22 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Tegra {
+namespace Engines {
+
+class Fermi2D final {
+public:
+ Fermi2D() = default;
+ ~Fermi2D() = default;
+
+ /// Write the value to the register identified by method.
+ void WriteReg(u32 method, u32 value);
+};
+
+} // namespace Engines
+} // namespace Tegra
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
new file mode 100644
index 000000000..124753032
--- /dev/null
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -0,0 +1,303 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cinttypes>
+#include "common/assert.h"
+#include "core/core.h"
+#include "video_core/debug_utils/debug_utils.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/rasterizer_interface.h"
+#include "video_core/renderer_base.h"
+#include "video_core/textures/decoders.h"
+#include "video_core/textures/texture.h"
+#include "video_core/video_core.h"
+
+namespace Tegra {
+namespace Engines {
+
+/// First register id that is actually a Macro call.
+constexpr u32 MacroRegistersStart = 0xE00;
+
+Maxwell3D::Maxwell3D(MemoryManager& memory_manager)
+ : memory_manager(memory_manager), macro_interpreter(*this) {}
+
+void Maxwell3D::SubmitMacroCode(u32 entry, std::vector<u32> code) {
+ uploaded_macros[entry * 2 + MacroRegistersStart] = std::move(code);
+}
+
+void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) {
+ auto macro_code = uploaded_macros.find(method);
+ // The requested macro must have been uploaded already.
+ ASSERT_MSG(macro_code != uploaded_macros.end(), "Macro %08X was not uploaded", method);
+
+ // Reset the current macro and execute it.
+ executing_macro = 0;
+ macro_interpreter.Execute(macro_code->second, std::move(parameters));
+}
+
+void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) {
+ ASSERT_MSG(method < Regs::NUM_REGS,
+ "Invalid Maxwell3D register, increase the size of the Regs structure");
+
+ auto debug_context = Core::System::GetInstance().GetGPUDebugContext();
+
+ // It is an error to write to a register other than the current macro's ARG register before it
+ // has finished execution.
+ if (executing_macro != 0) {
+ ASSERT(method == executing_macro + 1);
+ }
+
+ // Methods after 0xE00 are special, they're actually triggers for some microcode that was
+ // uploaded to the GPU during initialization.
+ if (method >= MacroRegistersStart) {
+ // We're trying to execute a macro
+ if (executing_macro == 0) {
+ // A macro call must begin by writing the macro method's register, not its argument.
+ ASSERT_MSG((method % 2) == 0,
+ "Can't start macro execution by writing to the ARGS register");
+ executing_macro = method;
+ }
+
+ macro_params.push_back(value);
+
+ // Call the macro when there are no more parameters in the command buffer
+ if (remaining_params == 0) {
+ CallMacroMethod(executing_macro, std::move(macro_params));
+ }
+ return;
+ }
+
+ if (debug_context) {
+ debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandLoaded, nullptr);
+ }
+
+ regs.reg_array[method] = value;
+
+#define MAXWELL3D_REG_INDEX(field_name) (offsetof(Regs, field_name) / sizeof(u32))
+
+ switch (method) {
+ case MAXWELL3D_REG_INDEX(code_address.code_address_high):
+ case MAXWELL3D_REG_INDEX(code_address.code_address_low): {
+ // Note: For some reason games (like Puyo Puyo Tetris) seem to write 0 to the CODE_ADDRESS
+ // register, we do not currently know if that's intended or a bug, so we assert it lest
+ // stuff breaks in other places (like the shader address calculation).
+ ASSERT_MSG(regs.code_address.CodeAddress() == 0, "Unexpected CODE_ADDRESS register value.");
+ break;
+ }
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[1]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[2]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[3]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[4]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[5]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[6]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[7]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[8]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[9]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[10]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[11]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[12]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[13]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[14]):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data[15]): {
+ ProcessCBData(value);
+ break;
+ }
+ case MAXWELL3D_REG_INDEX(cb_bind[0].raw_config): {
+ ProcessCBBind(Regs::ShaderStage::Vertex);
+ break;
+ }
+ case MAXWELL3D_REG_INDEX(cb_bind[1].raw_config): {
+ ProcessCBBind(Regs::ShaderStage::TesselationControl);
+ break;
+ }
+ case MAXWELL3D_REG_INDEX(cb_bind[2].raw_config): {
+ ProcessCBBind(Regs::ShaderStage::TesselationEval);
+ break;
+ }
+ case MAXWELL3D_REG_INDEX(cb_bind[3].raw_config): {
+ ProcessCBBind(Regs::ShaderStage::Geometry);
+ break;
+ }
+ case MAXWELL3D_REG_INDEX(cb_bind[4].raw_config): {
+ ProcessCBBind(Regs::ShaderStage::Fragment);
+ break;
+ }
+ case MAXWELL3D_REG_INDEX(draw.vertex_end_gl): {
+ DrawArrays();
+ break;
+ }
+ case MAXWELL3D_REG_INDEX(query.query_get): {
+ ProcessQueryGet();
+ break;
+ }
+ default:
+ break;
+ }
+
+#undef MAXWELL3D_REG_INDEX
+
+ if (debug_context) {
+ debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandProcessed, nullptr);
+ }
+}
+
+void Maxwell3D::ProcessQueryGet() {
+ GPUVAddr sequence_address = regs.query.QueryAddress();
+ // Since the sequence address is given as a GPU VAddr, we have to convert it to an application
+ // VAddr before writing.
+ VAddr address = memory_manager.PhysicalToVirtualAddress(sequence_address);
+
+ switch (regs.query.query_get.mode) {
+ case Regs::QueryMode::Write: {
+ // Write the current query sequence to the sequence address.
+ u32 sequence = regs.query.query_sequence;
+ Memory::Write32(address, sequence);
+ break;
+ }
+ default:
+ UNIMPLEMENTED_MSG("Query mode %u not implemented",
+ static_cast<u32>(regs.query.query_get.mode.Value()));
+ }
+}
+
+void Maxwell3D::DrawArrays() {
+ LOG_DEBUG(HW_GPU, "called, topology=%d, count=%d", regs.draw.topology.Value(),
+ regs.vertex_buffer.count);
+
+ auto debug_context = Core::System::GetInstance().GetGPUDebugContext();
+
+ if (debug_context) {
+ debug_context->OnEvent(Tegra::DebugContext::Event::IncomingPrimitiveBatch, nullptr);
+ }
+
+ if (debug_context) {
+ debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr);
+ }
+
+ VideoCore::g_renderer->Rasterizer()->AccelerateDrawBatch(false /*is_indexed*/);
+}
+
+void Maxwell3D::ProcessCBBind(Regs::ShaderStage stage) {
+ // Bind the buffer currently in CB_ADDRESS to the specified index in the desired shader stage.
+ auto& shader = state.shader_stages[static_cast<size_t>(stage)];
+ auto& bind_data = regs.cb_bind[static_cast<size_t>(stage)];
+
+ auto& buffer = shader.const_buffers[bind_data.index];
+
+ buffer.enabled = bind_data.valid.Value() != 0;
+ buffer.index = bind_data.index;
+ buffer.address = regs.const_buffer.BufferAddress();
+ buffer.size = regs.const_buffer.cb_size;
+}
+
+void Maxwell3D::ProcessCBData(u32 value) {
+ // Write the input value to the current const buffer at the current position.
+ GPUVAddr buffer_address = regs.const_buffer.BufferAddress();
+ ASSERT(buffer_address != 0);
+
+ // Don't allow writing past the end of the buffer.
+ ASSERT(regs.const_buffer.cb_pos + sizeof(u32) <= regs.const_buffer.cb_size);
+
+ VAddr address =
+ memory_manager.PhysicalToVirtualAddress(buffer_address + regs.const_buffer.cb_pos);
+
+ Memory::Write32(address, value);
+
+ // Increment the current buffer position.
+ regs.const_buffer.cb_pos = regs.const_buffer.cb_pos + 4;
+}
+
+Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
+ GPUVAddr tic_base_address = regs.tic.TICAddress();
+
+ GPUVAddr tic_address_gpu = tic_base_address + tic_index * sizeof(Texture::TICEntry);
+ VAddr tic_address_cpu = memory_manager.PhysicalToVirtualAddress(tic_address_gpu);
+
+ Texture::TICEntry tic_entry;
+ Memory::ReadBlock(tic_address_cpu, &tic_entry, sizeof(Texture::TICEntry));
+
+ ASSERT_MSG(tic_entry.header_version == Texture::TICHeaderVersion::BlockLinear,
+ "TIC versions other than BlockLinear are unimplemented");
+
+ ASSERT_MSG(tic_entry.texture_type == Texture::TextureType::Texture2D,
+ "Texture types other than Texture2D are unimplemented");
+
+ auto r_type = tic_entry.r_type.Value();
+ auto g_type = tic_entry.g_type.Value();
+ auto b_type = tic_entry.b_type.Value();
+ auto a_type = tic_entry.a_type.Value();
+
+ // TODO(Subv): Different data types for separate components are not supported
+ ASSERT(r_type == g_type && r_type == b_type && r_type == a_type);
+
+ return tic_entry;
+}
+
+Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const {
+ GPUVAddr tsc_base_address = regs.tsc.TSCAddress();
+
+ GPUVAddr tsc_address_gpu = tsc_base_address + tsc_index * sizeof(Texture::TSCEntry);
+ VAddr tsc_address_cpu = memory_manager.PhysicalToVirtualAddress(tsc_address_gpu);
+
+ Texture::TSCEntry tsc_entry;
+ Memory::ReadBlock(tsc_address_cpu, &tsc_entry, sizeof(Texture::TSCEntry));
+ return tsc_entry;
+}
+
+std::vector<Texture::FullTextureInfo> Maxwell3D::GetStageTextures(Regs::ShaderStage stage) const {
+ std::vector<Texture::FullTextureInfo> textures;
+
+ auto& fragment_shader = state.shader_stages[static_cast<size_t>(stage)];
+ auto& tex_info_buffer = fragment_shader.const_buffers[regs.tex_cb_index];
+ ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0);
+
+ GPUVAddr tic_base_address = regs.tic.TICAddress();
+
+ GPUVAddr tex_info_buffer_end = tex_info_buffer.address + tex_info_buffer.size;
+
+ // Offset into the texture constbuffer where the texture info begins.
+ static constexpr size_t TextureInfoOffset = 0x20;
+
+ for (GPUVAddr current_texture = tex_info_buffer.address + TextureInfoOffset;
+ current_texture < tex_info_buffer_end; current_texture += sizeof(Texture::TextureHandle)) {
+
+ Texture::TextureHandle tex_handle{
+ Memory::Read32(memory_manager.PhysicalToVirtualAddress(current_texture))};
+
+ Texture::FullTextureInfo tex_info{};
+ // TODO(Subv): Use the shader to determine which textures are actually accessed.
+ tex_info.index = (current_texture - tex_info_buffer.address - TextureInfoOffset) /
+ sizeof(Texture::TextureHandle);
+
+ // Load the TIC data.
+ if (tex_handle.tic_id != 0) {
+ tex_info.enabled = true;
+
+ auto tic_entry = GetTICEntry(tex_handle.tic_id);
+ // TODO(Subv): Workaround for BitField's move constructor being deleted.
+ std::memcpy(&tex_info.tic, &tic_entry, sizeof(tic_entry));
+ }
+
+ // Load the TSC data
+ if (tex_handle.tsc_id != 0) {
+ auto tsc_entry = GetTSCEntry(tex_handle.tsc_id);
+ // TODO(Subv): Workaround for BitField's move constructor being deleted.
+ std::memcpy(&tex_info.tsc, &tsc_entry, sizeof(tsc_entry));
+ }
+
+ if (tex_info.enabled)
+ textures.push_back(tex_info);
+ }
+
+ return textures;
+}
+
+u32 Maxwell3D::GetRegisterValue(u32 method) const {
+ ASSERT_MSG(method < Regs::NUM_REGS, "Invalid Maxwell3D register");
+ return regs.reg_array[method];
+}
+
+} // namespace Engines
+} // namespace Tegra
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
new file mode 100644
index 000000000..98b39b2ff
--- /dev/null
+++ b/src/video_core/engines/maxwell_3d.h
@@ -0,0 +1,590 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <unordered_map>
+#include <vector>
+#include "common/assert.h"
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/math_util.h"
+#include "video_core/gpu.h"
+#include "video_core/macro_interpreter.h"
+#include "video_core/memory_manager.h"
+#include "video_core/textures/texture.h"
+
+namespace Tegra {
+namespace Engines {
+
+class Maxwell3D final {
+public:
+ explicit Maxwell3D(MemoryManager& memory_manager);
+ ~Maxwell3D() = default;
+
+ /// Register structure of the Maxwell3D engine.
+ /// TODO(Subv): This structure will need to be made bigger as more registers are discovered.
+ struct Regs {
+ static constexpr size_t NUM_REGS = 0xE36;
+
+ static constexpr size_t NumRenderTargets = 8;
+ static constexpr size_t NumViewports = 16;
+ static constexpr size_t NumCBData = 16;
+ static constexpr size_t NumVertexArrays = 32;
+ static constexpr size_t NumVertexAttributes = 32;
+ static constexpr size_t MaxShaderProgram = 6;
+ static constexpr size_t MaxShaderStage = 5;
+ // Maximum number of const buffers per shader stage.
+ static constexpr size_t MaxConstBuffers = 16;
+
+ enum class QueryMode : u32 {
+ Write = 0,
+ Sync = 1,
+ };
+
+ enum class ShaderProgram : u32 {
+ VertexA = 0,
+ VertexB = 1,
+ TesselationControl = 2,
+ TesselationEval = 3,
+ Geometry = 4,
+ Fragment = 5,
+ };
+
+ enum class ShaderStage : u32 {
+ Vertex = 0,
+ TesselationControl = 1,
+ TesselationEval = 2,
+ Geometry = 3,
+ Fragment = 4,
+ };
+
+ struct VertexAttribute {
+ enum class Size : u32 {
+ Size_32_32_32_32 = 0x01,
+ Size_32_32_32 = 0x02,
+ Size_16_16_16_16 = 0x03,
+ Size_32_32 = 0x04,
+ Size_16_16_16 = 0x05,
+ Size_8_8_8_8 = 0x0a,
+ Size_16_16 = 0x0f,
+ Size_32 = 0x12,
+ Size_8_8_8 = 0x13,
+ Size_8_8 = 0x18,
+ Size_16 = 0x1b,
+ Size_8 = 0x1d,
+ Size_10_10_10_2 = 0x30,
+ Size_11_11_10 = 0x31,
+ };
+
+ enum class Type : u32 {
+ SignedNorm = 1,
+ UnsignedNorm = 2,
+ SignedInt = 3,
+ UnsignedInt = 4,
+ UnsignedScaled = 5,
+ SignedScaled = 6,
+ Float = 7,
+ };
+
+ union {
+ BitField<0, 5, u32> buffer;
+ BitField<6, 1, u32> constant;
+ BitField<7, 14, u32> offset;
+ BitField<21, 6, Size> size;
+ BitField<27, 3, Type> type;
+ BitField<31, 1, u32> bgra;
+ };
+
+ u32 ComponentCount() const {
+ switch (size) {
+ case Size::Size_32_32_32_32:
+ return 4;
+ case Size::Size_32_32_32:
+ return 3;
+ case Size::Size_16_16_16_16:
+ return 4;
+ case Size::Size_32_32:
+ return 2;
+ case Size::Size_16_16_16:
+ return 3;
+ case Size::Size_8_8_8_8:
+ return 4;
+ case Size::Size_16_16:
+ return 2;
+ case Size::Size_32:
+ return 1;
+ case Size::Size_8_8_8:
+ return 3;
+ case Size::Size_8_8:
+ return 2;
+ case Size::Size_16:
+ return 1;
+ case Size::Size_8:
+ return 1;
+ case Size::Size_10_10_10_2:
+ return 4;
+ case Size::Size_11_11_10:
+ return 3;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ u32 SizeInBytes() const {
+ switch (size) {
+ case Size::Size_32_32_32_32:
+ return 16;
+ case Size::Size_32_32_32:
+ return 12;
+ case Size::Size_16_16_16_16:
+ return 8;
+ case Size::Size_32_32:
+ return 8;
+ case Size::Size_16_16_16:
+ return 6;
+ case Size::Size_8_8_8_8:
+ return 4;
+ case Size::Size_16_16:
+ return 4;
+ case Size::Size_32:
+ return 4;
+ case Size::Size_8_8_8:
+ return 3;
+ case Size::Size_8_8:
+ return 2;
+ case Size::Size_16:
+ return 2;
+ case Size::Size_8:
+ return 1;
+ case Size::Size_10_10_10_2:
+ return 4;
+ case Size::Size_11_11_10:
+ return 4;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ std::string SizeString() const {
+ switch (size) {
+ case Size::Size_32_32_32_32:
+ return "32_32_32_32";
+ case Size::Size_32_32_32:
+ return "32_32_32";
+ case Size::Size_16_16_16_16:
+ return "16_16_16_16";
+ case Size::Size_32_32:
+ return "32_32";
+ case Size::Size_16_16_16:
+ return "16_16_16";
+ case Size::Size_8_8_8_8:
+ return "8_8_8_8";
+ case Size::Size_16_16:
+ return "16_16";
+ case Size::Size_32:
+ return "32";
+ case Size::Size_8_8_8:
+ return "8_8_8";
+ case Size::Size_8_8:
+ return "8_8";
+ case Size::Size_16:
+ return "16";
+ case Size::Size_8:
+ return "8";
+ case Size::Size_10_10_10_2:
+ return "10_10_10_2";
+ case Size::Size_11_11_10:
+ return "11_11_10";
+ }
+ UNREACHABLE();
+ return {};
+ }
+
+ std::string TypeString() const {
+ switch (type) {
+ case Type::SignedNorm:
+ return "SNORM";
+ case Type::UnsignedNorm:
+ return "UNORM";
+ case Type::SignedInt:
+ return "SINT";
+ case Type::UnsignedInt:
+ return "UINT";
+ case Type::UnsignedScaled:
+ return "USCALED";
+ case Type::SignedScaled:
+ return "SSCALED";
+ case Type::Float:
+ return "FLOAT";
+ }
+ UNREACHABLE();
+ return {};
+ }
+
+ bool IsNormalized() const {
+ return (type == Type::SignedNorm) || (type == Type::UnsignedNorm);
+ }
+ };
+
+ enum class PrimitiveTopology : u32 {
+ Points = 0x0,
+ Lines = 0x1,
+ LineLoop = 0x2,
+ LineStrip = 0x3,
+ Triangles = 0x4,
+ TriangleStrip = 0x5,
+ TriangleFan = 0x6,
+ Quads = 0x7,
+ QuadStrip = 0x8,
+ Polygon = 0x9,
+ LinesAdjacency = 0xa,
+ LineStripAdjacency = 0xb,
+ TrianglesAdjacency = 0xc,
+ TriangleStripAdjacency = 0xd,
+ Patches = 0xe,
+ };
+
+ union {
+ struct {
+ INSERT_PADDING_WORDS(0x200);
+
+ struct {
+ u32 address_high;
+ u32 address_low;
+ u32 width;
+ u32 height;
+ Tegra::RenderTargetFormat format;
+ u32 block_dimensions;
+ u32 array_mode;
+ u32 layer_stride;
+ u32 base_layer;
+ INSERT_PADDING_WORDS(7);
+
+ GPUVAddr Address() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
+ address_low);
+ }
+ } rt[NumRenderTargets];
+
+ INSERT_PADDING_WORDS(0x80);
+
+ struct {
+ union {
+ BitField<0, 16, u32> x;
+ BitField<16, 16, u32> width;
+ };
+ union {
+ BitField<0, 16, u32> y;
+ BitField<16, 16, u32> height;
+ };
+ float depth_range_near;
+ float depth_range_far;
+
+ MathUtil::Rectangle<s32> GetRect() const {
+ return {
+ static_cast<s32>(x), // left
+ static_cast<s32>(y + height), // top
+ static_cast<s32>(x + width), // right
+ static_cast<s32>(y) // bottom
+ };
+ };
+ } viewport[NumViewports];
+
+ INSERT_PADDING_WORDS(0x1D);
+
+ struct {
+ u32 first;
+ u32 count;
+ } vertex_buffer;
+
+ INSERT_PADDING_WORDS(0x99);
+
+ struct {
+ u32 address_high;
+ u32 address_low;
+ u32 format;
+ u32 block_dimensions;
+ u32 layer_stride;
+
+ GPUVAddr Address() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
+ address_low);
+ }
+ } zeta;
+
+ INSERT_PADDING_WORDS(0x5B);
+
+ VertexAttribute vertex_attrib_format[NumVertexAttributes];
+
+ INSERT_PADDING_WORDS(0xF);
+
+ struct {
+ union {
+ BitField<0, 4, u32> count;
+ };
+ } rt_control;
+
+ INSERT_PADDING_WORDS(0xCF);
+
+ struct {
+ u32 tsc_address_high;
+ u32 tsc_address_low;
+ u32 tsc_limit;
+
+ GPUVAddr TSCAddress() const {
+ return static_cast<GPUVAddr>(
+ (static_cast<GPUVAddr>(tsc_address_high) << 32) | tsc_address_low);
+ }
+ } tsc;
+
+ INSERT_PADDING_WORDS(0x3);
+
+ struct {
+ u32 tic_address_high;
+ u32 tic_address_low;
+ u32 tic_limit;
+
+ GPUVAddr TICAddress() const {
+ return static_cast<GPUVAddr>(
+ (static_cast<GPUVAddr>(tic_address_high) << 32) | tic_address_low);
+ }
+ } tic;
+
+ INSERT_PADDING_WORDS(0x22);
+
+ struct {
+ u32 code_address_high;
+ u32 code_address_low;
+
+ GPUVAddr CodeAddress() const {
+ return static_cast<GPUVAddr>(
+ (static_cast<GPUVAddr>(code_address_high) << 32) | code_address_low);
+ }
+ } code_address;
+ INSERT_PADDING_WORDS(1);
+
+ struct {
+ u32 vertex_end_gl;
+ union {
+ u32 vertex_begin_gl;
+ BitField<0, 16, PrimitiveTopology> topology;
+ };
+ } draw;
+
+ INSERT_PADDING_WORDS(0x139);
+ struct {
+ u32 query_address_high;
+ u32 query_address_low;
+ u32 query_sequence;
+ union {
+ u32 raw;
+ BitField<0, 2, QueryMode> mode;
+ BitField<4, 1, u32> fence;
+ BitField<12, 4, u32> unit;
+ } query_get;
+
+ GPUVAddr QueryAddress() const {
+ return static_cast<GPUVAddr>(
+ (static_cast<GPUVAddr>(query_address_high) << 32) | query_address_low);
+ }
+ } query;
+
+ INSERT_PADDING_WORDS(0x3C);
+
+ struct {
+ union {
+ BitField<0, 12, u32> stride;
+ BitField<12, 1, u32> enable;
+ };
+ u32 start_high;
+ u32 start_low;
+ u32 divisor;
+
+ GPUVAddr StartAddress() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(start_high) << 32) |
+ start_low);
+ }
+ } vertex_array[NumVertexArrays];
+
+ INSERT_PADDING_WORDS(0x40);
+
+ struct {
+ u32 limit_high;
+ u32 limit_low;
+
+ GPUVAddr LimitAddress() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(limit_high) << 32) |
+ limit_low);
+ }
+ } vertex_array_limit[NumVertexArrays];
+
+ struct {
+ union {
+ BitField<0, 1, u32> enable;
+ BitField<4, 4, ShaderProgram> program;
+ };
+ u32 start_id;
+ INSERT_PADDING_WORDS(1);
+ u32 gpr_alloc;
+ ShaderStage type;
+ INSERT_PADDING_WORDS(9);
+ } shader_config[MaxShaderProgram];
+
+ INSERT_PADDING_WORDS(0x8C);
+
+ struct {
+ u32 cb_size;
+ u32 cb_address_high;
+ u32 cb_address_low;
+ u32 cb_pos;
+ u32 cb_data[NumCBData];
+
+ GPUVAddr BufferAddress() const {
+ return static_cast<GPUVAddr>(
+ (static_cast<GPUVAddr>(cb_address_high) << 32) | cb_address_low);
+ }
+ } const_buffer;
+
+ INSERT_PADDING_WORDS(0x10);
+
+ struct {
+ union {
+ u32 raw_config;
+ BitField<0, 1, u32> valid;
+ BitField<4, 5, u32> index;
+ };
+ INSERT_PADDING_WORDS(7);
+ } cb_bind[MaxShaderStage];
+
+ INSERT_PADDING_WORDS(0x56);
+
+ u32 tex_cb_index;
+
+ INSERT_PADDING_WORDS(0x395);
+
+ struct {
+ /// Compressed address of a buffer that holds information about bound SSBOs.
+ /// This address is usually bound to c0 in the shaders.
+ u32 buffer_address;
+
+ GPUVAddr BufferAddress() const {
+ return static_cast<GPUVAddr>(buffer_address) << 8;
+ }
+ } ssbo_info;
+
+ INSERT_PADDING_WORDS(0x11);
+
+ struct {
+ u32 address[MaxShaderStage];
+ u32 size[MaxShaderStage];
+ } tex_info_buffers;
+
+ INSERT_PADDING_WORDS(0x102);
+ };
+ std::array<u32, NUM_REGS> reg_array;
+ };
+ } regs{};
+
+ static_assert(sizeof(Regs) == Regs::NUM_REGS * sizeof(u32), "Maxwell3D Regs has wrong size");
+
+ struct State {
+ struct ConstBufferInfo {
+ GPUVAddr address;
+ u32 index;
+ u32 size;
+ bool enabled;
+ };
+
+ struct ShaderStageInfo {
+ std::array<ConstBufferInfo, Regs::MaxConstBuffers> const_buffers;
+ };
+
+ std::array<ShaderStageInfo, Regs::MaxShaderStage> shader_stages;
+ };
+
+ State state{};
+
+ /// Reads a register value located at the input method address
+ u32 GetRegisterValue(u32 method) const;
+
+ /// Write the value to the register identified by method.
+ void WriteReg(u32 method, u32 value, u32 remaining_params);
+
+ /// Uploads the code for a GPU macro program associated with the specified entry.
+ void SubmitMacroCode(u32 entry, std::vector<u32> code);
+
+ /// Returns a list of enabled textures for the specified shader stage.
+ std::vector<Texture::FullTextureInfo> GetStageTextures(Regs::ShaderStage stage) const;
+
+private:
+ MemoryManager& memory_manager;
+
+ std::unordered_map<u32, std::vector<u32>> uploaded_macros;
+
+ /// Macro method that is currently being executed / being fed parameters.
+ u32 executing_macro = 0;
+ /// Parameters that have been submitted to the macro call so far.
+ std::vector<u32> macro_params;
+
+ /// Interpreter for the macro codes uploaded to the GPU.
+ MacroInterpreter macro_interpreter;
+
+ /// Retrieves information about a specific TIC entry from the TIC buffer.
+ Texture::TICEntry GetTICEntry(u32 tic_index) const;
+
+ /// Retrieves information about a specific TSC entry from the TSC buffer.
+ Texture::TSCEntry GetTSCEntry(u32 tsc_index) const;
+
+ /**
+ * Call a macro on this engine.
+ * @param method Method to call
+ * @param parameters Arguments to the method call
+ */
+ void CallMacroMethod(u32 method, std::vector<u32> parameters);
+
+ /// Handles a write to the QUERY_GET register.
+ void ProcessQueryGet();
+
+ /// Handles a write to the CB_DATA[i] register.
+ void ProcessCBData(u32 value);
+
+ /// Handles a write to the CB_BIND register.
+ void ProcessCBBind(Regs::ShaderStage stage);
+
+ /// Handles a write to the VERTEX_END_GL register, triggering a draw.
+ void DrawArrays();
+};
+
+#define ASSERT_REG_POSITION(field_name, position) \
+ static_assert(offsetof(Maxwell3D::Regs, field_name) == position * 4, \
+ "Field " #field_name " has invalid position")
+
+ASSERT_REG_POSITION(rt, 0x200);
+ASSERT_REG_POSITION(viewport, 0x300);
+ASSERT_REG_POSITION(vertex_buffer, 0x35D);
+ASSERT_REG_POSITION(zeta, 0x3F8);
+ASSERT_REG_POSITION(vertex_attrib_format[0], 0x458);
+ASSERT_REG_POSITION(rt_control, 0x487);
+ASSERT_REG_POSITION(tsc, 0x557);
+ASSERT_REG_POSITION(tic, 0x55D);
+ASSERT_REG_POSITION(code_address, 0x582);
+ASSERT_REG_POSITION(draw, 0x585);
+ASSERT_REG_POSITION(query, 0x6C0);
+ASSERT_REG_POSITION(vertex_array[0], 0x700);
+ASSERT_REG_POSITION(vertex_array_limit[0], 0x7C0);
+ASSERT_REG_POSITION(shader_config[0], 0x800);
+ASSERT_REG_POSITION(const_buffer, 0x8E0);
+ASSERT_REG_POSITION(cb_bind[0], 0x904);
+ASSERT_REG_POSITION(tex_cb_index, 0x982);
+ASSERT_REG_POSITION(ssbo_info, 0xD18);
+ASSERT_REG_POSITION(tex_info_buffers.address[0], 0xD2A);
+ASSERT_REG_POSITION(tex_info_buffers.size[0], 0xD2F);
+
+#undef ASSERT_REG_POSITION
+
+} // namespace Engines
+} // namespace Tegra
diff --git a/src/video_core/engines/maxwell_compute.cpp b/src/video_core/engines/maxwell_compute.cpp
new file mode 100644
index 000000000..e4e5f9e5e
--- /dev/null
+++ b/src/video_core/engines/maxwell_compute.cpp
@@ -0,0 +1,13 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "video_core/engines/maxwell_compute.h"
+
+namespace Tegra {
+namespace Engines {
+
+void MaxwellCompute::WriteReg(u32 method, u32 value) {}
+
+} // namespace Engines
+} // namespace Tegra
diff --git a/src/video_core/engines/maxwell_compute.h b/src/video_core/engines/maxwell_compute.h
new file mode 100644
index 000000000..7262e1bcb
--- /dev/null
+++ b/src/video_core/engines/maxwell_compute.h
@@ -0,0 +1,22 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Tegra {
+namespace Engines {
+
+class MaxwellCompute final {
+public:
+ MaxwellCompute() = default;
+ ~MaxwellCompute() = default;
+
+ /// Write the value to the register identified by method.
+ void WriteReg(u32 method, u32 value);
+};
+
+} // namespace Engines
+} // namespace Tegra
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
new file mode 100644
index 000000000..9463cd5d6
--- /dev/null
+++ b/src/video_core/gpu.cpp
@@ -0,0 +1,25 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "video_core/engines/fermi_2d.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/engines/maxwell_compute.h"
+#include "video_core/gpu.h"
+
+namespace Tegra {
+
+GPU::GPU() {
+ memory_manager = std::make_unique<MemoryManager>();
+ maxwell_3d = std::make_unique<Engines::Maxwell3D>(*memory_manager);
+ fermi_2d = std::make_unique<Engines::Fermi2D>();
+ maxwell_compute = std::make_unique<Engines::MaxwellCompute>();
+}
+
+GPU::~GPU() = default;
+
+const Tegra::Engines::Maxwell3D& GPU::Get3DEngine() const {
+ return *maxwell_3d;
+}
+
+} // namespace Tegra
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h
new file mode 100644
index 000000000..71a8661b4
--- /dev/null
+++ b/src/video_core/gpu.h
@@ -0,0 +1,107 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <unordered_map>
+#include <vector>
+#include "common/common_types.h"
+#include "core/hle/service/nvflinger/buffer_queue.h"
+#include "video_core/memory_manager.h"
+
+namespace Tegra {
+
+enum class RenderTargetFormat : u32 {
+ NONE = 0x0,
+ RGBA8_UNORM = 0xD5,
+};
+
+class DebugContext;
+
+/**
+ * Struct describing framebuffer configuration
+ */
+struct FramebufferConfig {
+ enum class PixelFormat : u32 {
+ ABGR8 = 1,
+ };
+
+ /**
+ * Returns the number of bytes per pixel.
+ */
+ static u32 BytesPerPixel(PixelFormat format) {
+ switch (format) {
+ case PixelFormat::ABGR8:
+ return 4;
+ }
+
+ UNREACHABLE();
+ }
+
+ VAddr address;
+ u32 offset;
+ u32 width;
+ u32 height;
+ u32 stride;
+ PixelFormat pixel_format;
+
+ using TransformFlags = Service::NVFlinger::BufferQueue::BufferTransformFlags;
+ TransformFlags transform_flags;
+};
+
+namespace Engines {
+class Fermi2D;
+class Maxwell3D;
+class MaxwellCompute;
+} // namespace Engines
+
+enum class EngineID {
+ FERMI_TWOD_A = 0x902D, // 2D Engine
+ MAXWELL_B = 0xB197, // 3D Engine
+ MAXWELL_COMPUTE_B = 0xB1C0,
+ KEPLER_INLINE_TO_MEMORY_B = 0xA140,
+ MAXWELL_DMA_COPY_A = 0xB0B5,
+};
+
+class GPU final {
+public:
+ GPU();
+ ~GPU();
+
+ /// Processes a command list stored at the specified address in GPU memory.
+ void ProcessCommandList(GPUVAddr address, u32 size);
+
+ /// Returns a reference to the Maxwell3D GPU engine.
+ const Engines::Maxwell3D& Get3DEngine() const;
+
+ std::unique_ptr<MemoryManager> memory_manager;
+
+ Engines::Maxwell3D& Maxwell3D() {
+ return *maxwell_3d;
+ }
+
+private:
+ static constexpr u32 InvalidGraphMacroEntry = 0xFFFFFFFF;
+
+ /// Writes a single register in the engine bound to the specified subchannel
+ void WriteReg(u32 method, u32 subchannel, u32 value, u32 remaining_params);
+
+ /// Mapping of command subchannels to their bound engine ids.
+ std::unordered_map<u32, EngineID> bound_engines;
+
+ /// 3D engine
+ std::unique_ptr<Engines::Maxwell3D> maxwell_3d;
+ /// 2D engine
+ std::unique_ptr<Engines::Fermi2D> fermi_2d;
+ /// Compute engine
+ std::unique_ptr<Engines::MaxwellCompute> maxwell_compute;
+
+ /// Entry of the macro that is currently being uploaded
+ u32 current_macro_entry = InvalidGraphMacroEntry;
+ /// Code being uploaded for the current macro
+ std::vector<u32> current_macro_code;
+};
+
+} // namespace Tegra
diff --git a/src/video_core/macro_interpreter.cpp b/src/video_core/macro_interpreter.cpp
new file mode 100644
index 000000000..993a67746
--- /dev/null
+++ b/src/video_core/macro_interpreter.cpp
@@ -0,0 +1,257 @@
+// Copyright 2018 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 "video_core/engines/maxwell_3d.h"
+#include "video_core/macro_interpreter.h"
+
+namespace Tegra {
+
+MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d) : maxwell3d(maxwell3d) {}
+
+void MacroInterpreter::Execute(const std::vector<u32>& code, std::vector<u32> parameters) {
+ Reset();
+ registers[1] = parameters[0];
+ this->parameters = std::move(parameters);
+
+ // Execute the code until we hit an exit condition.
+ bool keep_executing = true;
+ while (keep_executing) {
+ keep_executing = Step(code, false);
+ }
+
+ // Assert the the macro used all the input parameters
+ ASSERT(next_parameter_index == this->parameters.size());
+}
+
+void MacroInterpreter::Reset() {
+ registers = {};
+ pc = 0;
+ delayed_pc = boost::none;
+ method_address.raw = 0;
+ parameters.clear();
+ // The next parameter index starts at 1, because $r1 already has the value of the first
+ // parameter.
+ next_parameter_index = 1;
+}
+
+bool MacroInterpreter::Step(const std::vector<u32>& code, bool is_delay_slot) {
+ u32 base_address = pc;
+
+ Opcode opcode = GetOpcode(code);
+ pc += 4;
+
+ // Update the program counter if we were delayed
+ if (delayed_pc != boost::none) {
+ ASSERT(is_delay_slot);
+ pc = *delayed_pc;
+ delayed_pc = boost::none;
+ }
+
+ switch (opcode.operation) {
+ case Operation::ALU: {
+ u32 result = GetALUResult(opcode.alu_operation, GetRegister(opcode.src_a),
+ GetRegister(opcode.src_b));
+ ProcessResult(opcode.result_operation, opcode.dst, result);
+ break;
+ }
+ case Operation::AddImmediate: {
+ ProcessResult(opcode.result_operation, opcode.dst,
+ GetRegister(opcode.src_a) + opcode.immediate);
+ break;
+ }
+ case Operation::ExtractInsert: {
+ u32 dst = GetRegister(opcode.src_a);
+ u32 src = GetRegister(opcode.src_b);
+
+ src = (src >> opcode.bf_src_bit) & opcode.GetBitfieldMask();
+ dst &= ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit);
+ dst |= src << opcode.bf_dst_bit;
+ ProcessResult(opcode.result_operation, opcode.dst, dst);
+ break;
+ }
+ case Operation::ExtractShiftLeftImmediate: {
+ u32 dst = GetRegister(opcode.src_a);
+ u32 src = GetRegister(opcode.src_b);
+
+ u32 result = ((src >> dst) & opcode.GetBitfieldMask()) << opcode.bf_dst_bit;
+
+ ProcessResult(opcode.result_operation, opcode.dst, result);
+ break;
+ }
+ case Operation::ExtractShiftLeftRegister: {
+ u32 dst = GetRegister(opcode.src_a);
+ u32 src = GetRegister(opcode.src_b);
+
+ u32 result = ((src >> opcode.bf_src_bit) & opcode.GetBitfieldMask()) << dst;
+
+ ProcessResult(opcode.result_operation, opcode.dst, result);
+ break;
+ }
+ case Operation::Read: {
+ u32 result = Read(GetRegister(opcode.src_a) + opcode.immediate);
+ ProcessResult(opcode.result_operation, opcode.dst, result);
+ break;
+ }
+ case Operation::Branch: {
+ ASSERT_MSG(!is_delay_slot, "Executing a branch in a delay slot is not valid");
+ u32 value = GetRegister(opcode.src_a);
+ bool taken = EvaluateBranchCondition(opcode.branch_condition, value);
+ if (taken) {
+ // Ignore the delay slot if the branch has the annul bit.
+ if (opcode.branch_annul) {
+ pc = base_address + (opcode.immediate << 2);
+ return true;
+ }
+
+ delayed_pc = base_address + (opcode.immediate << 2);
+ // Execute one more instruction due to the delay slot.
+ return Step(code, true);
+ }
+ break;
+ }
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented macro operation %u",
+ static_cast<u32>(opcode.operation.Value()));
+ }
+
+ if (opcode.is_exit) {
+ // Exit has a delay slot, execute the next instruction
+ // Note: Executing an exit during a branch delay slot will cause the instruction at the
+ // branch target to be executed before exiting.
+ Step(code, true);
+ return false;
+ }
+
+ return true;
+}
+
+MacroInterpreter::Opcode MacroInterpreter::GetOpcode(const std::vector<u32>& code) const {
+ ASSERT((pc % sizeof(u32)) == 0);
+ ASSERT(pc < code.size() * sizeof(u32));
+ return {code[pc / sizeof(u32)]};
+}
+
+u32 MacroInterpreter::GetALUResult(ALUOperation operation, u32 src_a, u32 src_b) const {
+ switch (operation) {
+ case ALUOperation::Add:
+ return src_a + src_b;
+ // TODO(Subv): Implement AddWithCarry
+ case ALUOperation::Subtract:
+ return src_a - src_b;
+ // TODO(Subv): Implement SubtractWithBorrow
+ case ALUOperation::Xor:
+ return src_a ^ src_b;
+ case ALUOperation::Or:
+ return src_a | src_b;
+ case ALUOperation::And:
+ return src_a & src_b;
+ case ALUOperation::AndNot:
+ return src_a & ~src_b;
+ case ALUOperation::Nand:
+ return ~(src_a & src_b);
+
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented ALU operation %u", static_cast<u32>(operation));
+ }
+}
+
+void MacroInterpreter::ProcessResult(ResultOperation operation, u32 reg, u32 result) {
+ switch (operation) {
+ case ResultOperation::IgnoreAndFetch:
+ // Fetch parameter and ignore result.
+ SetRegister(reg, FetchParameter());
+ break;
+ case ResultOperation::Move:
+ // Move result.
+ SetRegister(reg, result);
+ break;
+ case ResultOperation::MoveAndSetMethod:
+ // Move result and use as Method Address.
+ SetRegister(reg, result);
+ SetMethodAddress(result);
+ break;
+ case ResultOperation::FetchAndSend:
+ // Fetch parameter and send result.
+ SetRegister(reg, FetchParameter());
+ Send(result);
+ break;
+ case ResultOperation::MoveAndSend:
+ // Move and send result.
+ SetRegister(reg, result);
+ Send(result);
+ break;
+ case ResultOperation::FetchAndSetMethod:
+ // Fetch parameter and use result as Method Address.
+ SetRegister(reg, FetchParameter());
+ SetMethodAddress(result);
+ break;
+ case ResultOperation::MoveAndSetMethodFetchAndSend:
+ // Move result and use as Method Address, then fetch and send parameter.
+ SetRegister(reg, result);
+ SetMethodAddress(result);
+ Send(FetchParameter());
+ break;
+ case ResultOperation::MoveAndSetMethodSend:
+ // Move result and use as Method Address, then send bits 12:17 of result.
+ SetRegister(reg, result);
+ SetMethodAddress(result);
+ Send((result >> 12) & 0b111111);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented result operation %u", static_cast<u32>(operation));
+ }
+}
+
+u32 MacroInterpreter::FetchParameter() {
+ ASSERT(next_parameter_index < parameters.size());
+ return parameters[next_parameter_index++];
+}
+
+u32 MacroInterpreter::GetRegister(u32 register_id) const {
+ // Register 0 is supposed to always return 0.
+ if (register_id == 0)
+ return 0;
+
+ ASSERT(register_id < registers.size());
+ return registers[register_id];
+}
+
+void MacroInterpreter::SetRegister(u32 register_id, u32 value) {
+ // Register 0 is supposed to always return 0. NOP is implemented as a store to the zero
+ // register.
+ if (register_id == 0)
+ return;
+
+ ASSERT(register_id < registers.size());
+ registers[register_id] = value;
+}
+
+void MacroInterpreter::SetMethodAddress(u32 address) {
+ method_address.raw = address;
+}
+
+void MacroInterpreter::Send(u32 value) {
+ maxwell3d.WriteReg(method_address.address, value, 0);
+ // Increment the method address by the method increment.
+ method_address.address.Assign(method_address.address.Value() +
+ method_address.increment.Value());
+}
+
+u32 MacroInterpreter::Read(u32 method) const {
+ return maxwell3d.GetRegisterValue(method);
+}
+
+bool MacroInterpreter::EvaluateBranchCondition(BranchCondition cond, u32 value) const {
+ switch (cond) {
+ case BranchCondition::Zero:
+ return value == 0;
+ case BranchCondition::NotZero:
+ return value != 0;
+ }
+ UNREACHABLE();
+}
+
+} // namespace Tegra
diff --git a/src/video_core/macro_interpreter.h b/src/video_core/macro_interpreter.h
new file mode 100644
index 000000000..a71e359d8
--- /dev/null
+++ b/src/video_core/macro_interpreter.h
@@ -0,0 +1,164 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <vector>
+#include <boost/optional.hpp>
+#include "common/bit_field.h"
+#include "common/common_types.h"
+
+namespace Tegra {
+namespace Engines {
+class Maxwell3D;
+}
+
+class MacroInterpreter final {
+public:
+ explicit MacroInterpreter(Engines::Maxwell3D& maxwell3d);
+
+ /**
+ * Executes the macro code with the specified input parameters.
+ * @param code The macro byte code to execute
+ * @param parameters The parameters of the macro
+ */
+ void Execute(const std::vector<u32>& code, std::vector<u32> parameters);
+
+private:
+ enum class Operation : u32 {
+ ALU = 0,
+ AddImmediate = 1,
+ ExtractInsert = 2,
+ ExtractShiftLeftImmediate = 3,
+ ExtractShiftLeftRegister = 4,
+ Read = 5,
+ Unused = 6, // This operation doesn't seem to be a valid encoding.
+ Branch = 7,
+ };
+
+ enum class ALUOperation : u32 {
+ Add = 0,
+ AddWithCarry = 1,
+ Subtract = 2,
+ SubtractWithBorrow = 3,
+ // Operations 4-7 don't seem to be valid encodings.
+ Xor = 8,
+ Or = 9,
+ And = 10,
+ AndNot = 11,
+ Nand = 12
+ };
+
+ enum class ResultOperation : u32 {
+ IgnoreAndFetch = 0,
+ Move = 1,
+ MoveAndSetMethod = 2,
+ FetchAndSend = 3,
+ MoveAndSend = 4,
+ FetchAndSetMethod = 5,
+ MoveAndSetMethodFetchAndSend = 6,
+ MoveAndSetMethodSend = 7
+ };
+
+ enum class BranchCondition : u32 {
+ Zero = 0,
+ NotZero = 1,
+ };
+
+ union Opcode {
+ u32 raw;
+ BitField<0, 3, Operation> operation;
+ BitField<4, 3, ResultOperation> result_operation;
+ BitField<4, 1, BranchCondition> branch_condition;
+ BitField<5, 1, u32>
+ branch_annul; // If set on a branch, then the branch doesn't have a delay slot.
+ BitField<7, 1, u32> is_exit;
+ BitField<8, 3, u32> dst;
+ BitField<11, 3, u32> src_a;
+ BitField<14, 3, u32> src_b;
+ // The signed immediate overlaps the second source operand and the alu operation.
+ BitField<14, 18, s32> immediate;
+
+ BitField<17, 5, ALUOperation> alu_operation;
+
+ // Bitfield instructions data
+ BitField<17, 5, u32> bf_src_bit;
+ BitField<22, 5, u32> bf_size;
+ BitField<27, 5, u32> bf_dst_bit;
+
+ u32 GetBitfieldMask() const {
+ return (1 << bf_size) - 1;
+ }
+ };
+
+ union MethodAddress {
+ u32 raw;
+ BitField<0, 12, u32> address;
+ BitField<12, 6, u32> increment;
+ };
+
+ /// Resets the execution engine state, zeroing registers, etc.
+ void Reset();
+
+ /**
+ * Executes a single macro instruction located at the current program counter. Returns whether
+ * the interpreter should keep running.
+ * @param code The macro code to execute.
+ * @param is_delay_slot Whether the current step is being executed due to a delay slot in a
+ * previous instruction.
+ */
+ bool Step(const std::vector<u32>& code, bool is_delay_slot);
+
+ /// Calculates the result of an ALU operation. src_a OP src_b;
+ u32 GetALUResult(ALUOperation operation, u32 src_a, u32 src_b) const;
+
+ /// Performs the result operation on the input result and stores it in the specified register
+ /// (if necessary).
+ void ProcessResult(ResultOperation operation, u32 reg, u32 result);
+
+ /// Evaluates the branch condition and returns whether the branch should be taken or not.
+ bool EvaluateBranchCondition(BranchCondition cond, u32 value) const;
+
+ /// Reads an opcode at the current program counter location.
+ Opcode GetOpcode(const std::vector<u32>& code) const;
+
+ /// Returns the specified register's value. Register 0 is hardcoded to always return 0.
+ u32 GetRegister(u32 register_id) const;
+
+ /// Sets the register to the input value.
+ void SetRegister(u32 register_id, u32 value);
+
+ /// Sets the method address to use for the next Send instruction.
+ void SetMethodAddress(u32 address);
+
+ /// Calls a GPU Engine method with the input parameter.
+ void Send(u32 value);
+
+ /// Reads a GPU register located at the method address.
+ u32 Read(u32 method) const;
+
+ /// Returns the next parameter in the parameter queue.
+ u32 FetchParameter();
+
+ Engines::Maxwell3D& maxwell3d;
+
+ u32 pc; ///< Current program counter
+ boost::optional<u32>
+ delayed_pc; ///< Program counter to execute at after the delay slot is executed.
+
+ static constexpr size_t NumMacroRegisters = 8;
+
+ /// General purpose macro registers.
+ std::array<u32, NumMacroRegisters> registers = {};
+
+ /// Method address to use for the next Send instruction.
+ MethodAddress method_address = {};
+
+ /// Input parameters of the current macro.
+ std::vector<u32> parameters;
+ /// Index of the next parameter that will be fetched by the 'parm' instruction.
+ u32 next_parameter_index = 0;
+};
+} // namespace Tegra
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
new file mode 100644
index 000000000..2789a4ca1
--- /dev/null
+++ b/src/video_core/memory_manager.cpp
@@ -0,0 +1,110 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "video_core/memory_manager.h"
+
+namespace Tegra {
+
+PAddr MemoryManager::AllocateSpace(u64 size, u64 align) {
+ boost::optional<PAddr> paddr = FindFreeBlock(size, align);
+ ASSERT(paddr);
+
+ for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) {
+ PageSlot(*paddr + offset) = static_cast<u64>(PageStatus::Allocated);
+ }
+
+ return *paddr;
+}
+
+PAddr MemoryManager::AllocateSpace(PAddr paddr, u64 size, u64 align) {
+ for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) {
+ if (IsPageMapped(paddr + offset)) {
+ return AllocateSpace(size, align);
+ }
+ }
+
+ for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) {
+ PageSlot(paddr + offset) = static_cast<u64>(PageStatus::Allocated);
+ }
+
+ return paddr;
+}
+
+PAddr MemoryManager::MapBufferEx(VAddr vaddr, u64 size) {
+ vaddr &= ~Memory::PAGE_MASK;
+
+ boost::optional<PAddr> paddr = FindFreeBlock(size);
+ ASSERT(paddr);
+
+ for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) {
+ PageSlot(*paddr + offset) = vaddr + offset;
+ }
+
+ return *paddr;
+}
+
+PAddr MemoryManager::MapBufferEx(VAddr vaddr, PAddr paddr, u64 size) {
+ vaddr &= ~Memory::PAGE_MASK;
+ paddr &= ~Memory::PAGE_MASK;
+
+ for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) {
+ if (PageSlot(paddr + offset) != static_cast<u64>(PageStatus::Allocated)) {
+ return MapBufferEx(vaddr, size);
+ }
+ }
+
+ for (u64 offset = 0; offset < size; offset += Memory::PAGE_SIZE) {
+ PageSlot(paddr + offset) = vaddr + offset;
+ }
+
+ return paddr;
+}
+
+boost::optional<PAddr> MemoryManager::FindFreeBlock(u64 size, u64 align) {
+ PAddr paddr{};
+ u64 free_space{};
+ align = (align + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
+
+ while (paddr + free_space < MAX_ADDRESS) {
+ if (!IsPageMapped(paddr + free_space)) {
+ free_space += Memory::PAGE_SIZE;
+ if (free_space >= size) {
+ return paddr;
+ }
+ } else {
+ paddr += free_space + Memory::PAGE_SIZE;
+ free_space = 0;
+ const u64 remainder{paddr % align};
+ if (!remainder) {
+ paddr = (paddr - remainder) + align;
+ }
+ }
+ }
+
+ return {};
+}
+
+VAddr MemoryManager::PhysicalToVirtualAddress(PAddr paddr) {
+ VAddr base_addr = PageSlot(paddr);
+ ASSERT(base_addr != static_cast<u64>(PageStatus::Unmapped));
+ return base_addr + (paddr & Memory::PAGE_MASK);
+}
+
+bool MemoryManager::IsPageMapped(PAddr paddr) {
+ return PageSlot(paddr) != static_cast<u64>(PageStatus::Unmapped);
+}
+
+VAddr& MemoryManager::PageSlot(PAddr paddr) {
+ auto& block = page_table[(paddr >> (Memory::PAGE_BITS + PAGE_TABLE_BITS)) & PAGE_TABLE_MASK];
+ if (!block) {
+ block = std::make_unique<PageBlock>();
+ for (unsigned index = 0; index < PAGE_BLOCK_SIZE; index++) {
+ (*block)[index] = static_cast<u64>(PageStatus::Unmapped);
+ }
+ }
+ return (*block)[(paddr >> Memory::PAGE_BITS) & PAGE_BLOCK_MASK];
+}
+
+} // namespace Tegra
diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h
new file mode 100644
index 000000000..47da7acd6
--- /dev/null
+++ b/src/video_core/memory_manager.h
@@ -0,0 +1,49 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include "common/common_types.h"
+#include "core/memory.h"
+
+namespace Tegra {
+
+/// Virtual addresses in the GPU's memory map are 64 bit.
+using GPUVAddr = u64;
+
+class MemoryManager final {
+public:
+ MemoryManager() = default;
+
+ PAddr AllocateSpace(u64 size, u64 align);
+ PAddr AllocateSpace(PAddr paddr, u64 size, u64 align);
+ PAddr MapBufferEx(VAddr vaddr, u64 size);
+ PAddr MapBufferEx(VAddr vaddr, PAddr paddr, u64 size);
+ VAddr PhysicalToVirtualAddress(PAddr paddr);
+
+private:
+ boost::optional<PAddr> FindFreeBlock(u64 size, u64 align = 1);
+ bool IsPageMapped(PAddr paddr);
+ VAddr& PageSlot(PAddr paddr);
+
+ enum class PageStatus : u64 {
+ Unmapped = 0xFFFFFFFFFFFFFFFFULL,
+ Allocated = 0xFFFFFFFFFFFFFFFEULL,
+ };
+
+ static constexpr u64 MAX_ADDRESS{0x10000000000ULL};
+ static constexpr u64 PAGE_TABLE_BITS{14};
+ static constexpr u64 PAGE_TABLE_SIZE{1 << PAGE_TABLE_BITS};
+ static constexpr u64 PAGE_TABLE_MASK{PAGE_TABLE_SIZE - 1};
+ static constexpr u64 PAGE_BLOCK_BITS{14};
+ static constexpr u64 PAGE_BLOCK_SIZE{1 << PAGE_BLOCK_BITS};
+ static constexpr u64 PAGE_BLOCK_MASK{PAGE_BLOCK_SIZE - 1};
+
+ using PageBlock = std::array<VAddr, PAGE_BLOCK_SIZE>;
+ std::array<std::unique_ptr<PageBlock>, PAGE_TABLE_SIZE> page_table{};
+};
+
+} // namespace Tegra
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
new file mode 100644
index 000000000..8239f9aad
--- /dev/null
+++ b/src/video_core/rasterizer_interface.h
@@ -0,0 +1,63 @@
+// Copyright 2018 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 "video_core/gpu.h"
+
+struct ScreenInfo;
+
+namespace VideoCore {
+
+class RasterizerInterface {
+public:
+ virtual ~RasterizerInterface() {}
+
+ /// Draw the current batch of vertex arrays
+ virtual void DrawArrays() = 0;
+
+ /// Notify rasterizer that the specified Maxwell register has been changed
+ virtual void NotifyMaxwellRegisterChanged(u32 id) = 0;
+
+ /// Notify rasterizer that all caches should be flushed to 3DS memory
+ virtual void FlushAll() = 0;
+
+ /// Notify rasterizer that any caches of the specified region should be flushed to 3DS memory
+ virtual void FlushRegion(VAddr addr, u64 size) = 0;
+
+ /// Notify rasterizer that any caches of the specified region should be invalidated
+ virtual void InvalidateRegion(VAddr addr, u64 size) = 0;
+
+ /// Notify rasterizer that any caches of the specified region should be flushed to 3DS memory
+ /// and invalidated
+ virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0;
+
+ /// Attempt to use a faster method to perform a display transfer with is_texture_copy = 0
+ virtual bool AccelerateDisplayTransfer(const void* config) {
+ return false;
+ }
+
+ /// Attempt to use a faster method to perform a display transfer with is_texture_copy = 1
+ virtual bool AccelerateTextureCopy(const void* config) {
+ return false;
+ }
+
+ /// Attempt to use a faster method to fill a region
+ virtual bool AccelerateFill(const void* config) {
+ return false;
+ }
+
+ /// Attempt to use a faster method to display the framebuffer to screen
+ virtual bool AccelerateDisplay(const Tegra::FramebufferConfig& framebuffer,
+ VAddr framebuffer_addr, u32 pixel_stride,
+ ScreenInfo& screen_info) {
+ return false;
+ }
+
+ virtual bool AccelerateDrawBatch(bool is_indexed) {
+ return false;
+ }
+};
+} // namespace VideoCore
diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp
index 51e1d45f9..30075b23c 100644
--- a/src/video_core/renderer_base.cpp
+++ b/src/video_core/renderer_base.cpp
@@ -5,6 +5,11 @@
#include <atomic>
#include <memory>
#include "video_core/renderer_base.h"
+#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/video_core.h"
-void RendererBase::RefreshRasterizerSetting() {}
+void RendererBase::RefreshRasterizerSetting() {
+ if (rasterizer == nullptr) {
+ rasterizer = std::make_unique<RasterizerOpenGL>();
+ }
+}
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 28893b181..89a960eaf 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -8,6 +8,8 @@
#include <boost/optional.hpp>
#include "common/assert.h"
#include "common/common_types.h"
+#include "video_core/gpu.h"
+#include "video_core/rasterizer_interface.h"
class EmuWindow;
@@ -16,39 +18,10 @@ public:
/// Used to reference a framebuffer
enum kFramebuffer { kFramebuffer_VirtualXFB = 0, kFramebuffer_EFB, kFramebuffer_Texture };
- /**
- * Struct describing framebuffer metadata
- * TODO(bunnei): This struct belongs in the GPU code, but we don't have a good place for it yet.
- */
- struct FramebufferInfo {
- enum class PixelFormat : u32 {
- ABGR8 = 1,
- };
-
- /**
- * Returns the number of bytes per pixel.
- */
- static u32 BytesPerPixel(PixelFormat format) {
- switch (format) {
- case PixelFormat::ABGR8:
- return 4;
- }
-
- UNREACHABLE();
- }
-
- VAddr address;
- u32 offset;
- u32 width;
- u32 height;
- u32 stride;
- PixelFormat pixel_format;
- };
-
virtual ~RendererBase() {}
/// Swap buffers (render frame)
- virtual void SwapBuffers(boost::optional<const FramebufferInfo&> framebuffer_info) = 0;
+ virtual void SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) = 0;
/**
* Set the emulator window to use for renderer
@@ -73,12 +46,16 @@ public:
return m_current_frame;
}
+ VideoCore::RasterizerInterface* Rasterizer() const {
+ return rasterizer.get();
+ }
+
void RefreshRasterizerSetting();
protected:
+ std::unique_ptr<VideoCore::RasterizerInterface> rasterizer;
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
private:
- bool opengl_rasterizer_active = false;
};
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
new file mode 100644
index 000000000..911890f16
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -0,0 +1,578 @@
+// Copyright 2015 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <glad/glad.h>
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/math_util.h"
+#include "common/microprofile.h"
+#include "common/scope_exit.h"
+#include "common/vector_math.h"
+#include "core/core.h"
+#include "core/hle/kernel/process.h"
+#include "core/settings.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/renderer_opengl/gl_rasterizer.h"
+#include "video_core/renderer_opengl/gl_shader_gen.h"
+#include "video_core/renderer_opengl/maxwell_to_gl.h"
+#include "video_core/renderer_opengl/renderer_opengl.h"
+
+using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+using PixelFormat = SurfaceParams::PixelFormat;
+using SurfaceType = SurfaceParams::SurfaceType;
+
+MICROPROFILE_DEFINE(OpenGL_VAO, "OpenGL", "Vertex Array Setup", MP_RGB(128, 128, 192));
+MICROPROFILE_DEFINE(OpenGL_VS, "OpenGL", "Vertex Shader Setup", MP_RGB(128, 128, 192));
+MICROPROFILE_DEFINE(OpenGL_FS, "OpenGL", "Fragment Shader Setup", MP_RGB(128, 128, 192));
+MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192));
+MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(100, 100, 255));
+MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100));
+
+enum class UniformBindings : GLuint { Common, VS, FS };
+
+static void SetShaderUniformBlockBinding(GLuint shader, const char* name, UniformBindings binding,
+ size_t expected_size) {
+ GLuint ub_index = glGetUniformBlockIndex(shader, name);
+ if (ub_index != GL_INVALID_INDEX) {
+ GLint ub_size = 0;
+ glGetActiveUniformBlockiv(shader, ub_index, GL_UNIFORM_BLOCK_DATA_SIZE, &ub_size);
+ ASSERT_MSG(ub_size == expected_size,
+ "Uniform block size did not match! Got %d, expected %zu",
+ static_cast<int>(ub_size), expected_size);
+ glUniformBlockBinding(shader, ub_index, static_cast<GLuint>(binding));
+ }
+}
+
+static void SetShaderUniformBlockBindings(GLuint shader) {
+ SetShaderUniformBlockBinding(shader, "shader_data", UniformBindings::Common,
+ sizeof(RasterizerOpenGL::UniformData));
+ SetShaderUniformBlockBinding(shader, "vs_config", UniformBindings::VS,
+ sizeof(RasterizerOpenGL::VSUniformData));
+ SetShaderUniformBlockBinding(shader, "fs_config", UniformBindings::FS,
+ sizeof(RasterizerOpenGL::FSUniformData));
+}
+
+RasterizerOpenGL::RasterizerOpenGL() {
+ shader_dirty = true;
+
+ has_ARB_buffer_storage = false;
+ has_ARB_direct_state_access = false;
+ has_ARB_separate_shader_objects = false;
+ has_ARB_vertex_attrib_binding = false;
+
+ GLint ext_num;
+ glGetIntegerv(GL_NUM_EXTENSIONS, &ext_num);
+ for (GLint i = 0; i < ext_num; i++) {
+ std::string extension{reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, i))};
+
+ if (extension == "GL_ARB_buffer_storage") {
+ has_ARB_buffer_storage = true;
+ } else if (extension == "GL_ARB_direct_state_access") {
+ has_ARB_direct_state_access = true;
+ } else if (extension == "GL_ARB_separate_shader_objects") {
+ has_ARB_separate_shader_objects = true;
+ } else if (extension == "GL_ARB_vertex_attrib_binding") {
+ has_ARB_vertex_attrib_binding = true;
+ }
+ }
+
+ // Clipping plane 0 is always enabled for PICA fixed clip plane z <= 0
+ state.clip_distance[0] = true;
+
+ // Generate VBO, VAO and UBO
+ vertex_buffer = OGLStreamBuffer::MakeBuffer(GLAD_GL_ARB_buffer_storage, GL_ARRAY_BUFFER);
+ vertex_buffer->Create(VERTEX_BUFFER_SIZE, VERTEX_BUFFER_SIZE / 2);
+ sw_vao.Create();
+ uniform_buffer.Create();
+
+ state.draw.vertex_array = sw_vao.handle;
+ state.draw.vertex_buffer = vertex_buffer->GetHandle();
+ state.draw.uniform_buffer = uniform_buffer.handle;
+ state.Apply();
+
+ glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformData), nullptr, GL_STATIC_DRAW);
+ glBindBufferBase(GL_UNIFORM_BUFFER, 0, uniform_buffer.handle);
+
+ uniform_block_data.dirty = true;
+
+ // Create render framebuffer
+ framebuffer.Create();
+
+ if (has_ARB_separate_shader_objects) {
+ hw_vao.Create();
+ hw_vao_enabled_attributes.fill(false);
+
+ stream_buffer = OGLStreamBuffer::MakeBuffer(has_ARB_buffer_storage, GL_ARRAY_BUFFER);
+ stream_buffer->Create(STREAM_BUFFER_SIZE, STREAM_BUFFER_SIZE / 2);
+ state.draw.vertex_buffer = stream_buffer->GetHandle();
+
+ pipeline.Create();
+ state.draw.program_pipeline = pipeline.handle;
+ state.draw.shader_program = 0;
+ state.draw.vertex_array = hw_vao.handle;
+ state.Apply();
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, stream_buffer->GetHandle());
+
+ vs_uniform_buffer.Create();
+ glBindBuffer(GL_UNIFORM_BUFFER, vs_uniform_buffer.handle);
+ glBufferData(GL_UNIFORM_BUFFER, sizeof(VSUniformData), nullptr, GL_STREAM_COPY);
+ glBindBufferBase(GL_UNIFORM_BUFFER, 1, vs_uniform_buffer.handle);
+ } else {
+ UNREACHABLE();
+ }
+
+ accelerate_draw = AccelDraw::Disabled;
+
+ glEnable(GL_BLEND);
+
+ LOG_CRITICAL(Render_OpenGL, "Sync fixed function OpenGL state here!");
+}
+
+RasterizerOpenGL::~RasterizerOpenGL() {
+ if (stream_buffer != nullptr) {
+ state.draw.vertex_buffer = stream_buffer->GetHandle();
+ state.Apply();
+ stream_buffer->Release();
+ }
+}
+
+void RasterizerOpenGL::AnalyzeVertexArray(bool is_indexed) {
+ const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
+
+ if (is_indexed) {
+ UNREACHABLE();
+ }
+
+ // TODO(bunnei): Add support for 1+ vertex arrays
+ vs_input_size = regs.vertex_buffer.count * regs.vertex_array[0].stride;
+}
+
+void RasterizerOpenGL::SetupVertexArray(u8* array_ptr, GLintptr buffer_offset) {
+ MICROPROFILE_SCOPE(OpenGL_VAO);
+ const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
+ const auto& memory_manager = Core::System().GetInstance().GPU().memory_manager;
+
+ state.draw.vertex_array = hw_vao.handle;
+ state.draw.vertex_buffer = stream_buffer->GetHandle();
+ state.Apply();
+
+ // TODO(bunnei): Add support for 1+ vertex arrays
+ const auto& vertex_array{regs.vertex_array[0]};
+ ASSERT_MSG(vertex_array.enable, "vertex array 0 is disabled?");
+ ASSERT_MSG(!vertex_array.divisor, "vertex array 0 divisor is unimplemented!");
+ for (unsigned index = 1; index < Maxwell::NumVertexArrays; ++index) {
+ ASSERT_MSG(!regs.vertex_array[index].enable, "vertex array %d is unimplemented!", index);
+ }
+
+ // Use the vertex array as-is, assumes that the data is formatted correctly for OpenGL.
+ // Enables the first 16 vertex attributes always, as we don't know which ones are actually used
+ // until shader time. Note, Tegra technically supports 32, but we're cappinig this to 16 for now
+ // to avoid OpenGL errors.
+ for (unsigned index = 0; index < 16; ++index) {
+ auto& attrib = regs.vertex_attrib_format[index];
+ glVertexAttribPointer(index, attrib.ComponentCount(), MaxwellToGL::VertexType(attrib),
+ attrib.IsNormalized() ? GL_TRUE : GL_FALSE, vertex_array.stride,
+ reinterpret_cast<GLvoid*>(buffer_offset + attrib.offset));
+ glEnableVertexAttribArray(index);
+ hw_vao_enabled_attributes[index] = true;
+ }
+
+ // Copy vertex array data
+ const u32 data_size{vertex_array.stride * regs.vertex_buffer.count};
+ const VAddr data_addr{memory_manager->PhysicalToVirtualAddress(vertex_array.StartAddress())};
+ res_cache.FlushRegion(data_addr, data_size, nullptr);
+ Memory::ReadBlock(data_addr, array_ptr, data_size);
+
+ array_ptr += data_size;
+ buffer_offset += data_size;
+}
+
+void RasterizerOpenGL::SetupVertexShader(VSUniformData* ub_ptr, GLintptr buffer_offset) {
+ MICROPROFILE_SCOPE(OpenGL_VS);
+ LOG_CRITICAL(Render_OpenGL, "Emulated shaders are not supported! Using a passthrough shader.");
+ glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, current_shader->shader.handle);
+}
+
+void RasterizerOpenGL::SetupFragmentShader(FSUniformData* ub_ptr, GLintptr buffer_offset) {
+ MICROPROFILE_SCOPE(OpenGL_FS);
+ UNREACHABLE();
+}
+
+bool RasterizerOpenGL::AccelerateDrawBatch(bool is_indexed) {
+ if (!has_ARB_separate_shader_objects) {
+ UNREACHABLE();
+ return false;
+ }
+
+ accelerate_draw = is_indexed ? AccelDraw::Indexed : AccelDraw::Arrays;
+ DrawArrays();
+
+ return true;
+}
+
+void RasterizerOpenGL::DrawArrays() {
+ if (accelerate_draw == AccelDraw::Disabled)
+ return;
+
+ MICROPROFILE_SCOPE(OpenGL_Drawing);
+ const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
+
+ // TODO(bunnei): Implement these
+ const bool has_stencil = false;
+ const bool using_color_fb = true;
+ const bool using_depth_fb = false;
+ const MathUtil::Rectangle<s32> viewport_rect{regs.viewport[0].GetRect()};
+
+ const bool write_color_fb =
+ state.color_mask.red_enabled == GL_TRUE || state.color_mask.green_enabled == GL_TRUE ||
+ state.color_mask.blue_enabled == GL_TRUE || state.color_mask.alpha_enabled == GL_TRUE;
+
+ const bool write_depth_fb =
+ (state.depth.test_enabled && state.depth.write_mask == GL_TRUE) ||
+ (has_stencil && state.stencil.test_enabled && state.stencil.write_mask != 0);
+
+ Surface color_surface;
+ Surface depth_surface;
+ MathUtil::Rectangle<u32> surfaces_rect;
+ std::tie(color_surface, depth_surface, surfaces_rect) =
+ res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb, viewport_rect);
+
+ const u16 res_scale = color_surface != nullptr
+ ? color_surface->res_scale
+ : (depth_surface == nullptr ? 1u : depth_surface->res_scale);
+
+ MathUtil::Rectangle<u32> draw_rect{
+ static_cast<u32>(MathUtil::Clamp<s32>(static_cast<s32>(surfaces_rect.left) +
+ viewport_rect.left * res_scale,
+ surfaces_rect.left, surfaces_rect.right)), // Left
+ static_cast<u32>(MathUtil::Clamp<s32>(static_cast<s32>(surfaces_rect.bottom) +
+ viewport_rect.top * res_scale,
+ surfaces_rect.bottom, surfaces_rect.top)), // Top
+ static_cast<u32>(MathUtil::Clamp<s32>(static_cast<s32>(surfaces_rect.left) +
+ viewport_rect.right * res_scale,
+ surfaces_rect.left, surfaces_rect.right)), // Right
+ static_cast<u32>(MathUtil::Clamp<s32>(static_cast<s32>(surfaces_rect.bottom) +
+ viewport_rect.bottom * res_scale,
+ surfaces_rect.bottom, surfaces_rect.top))}; // Bottom
+
+ // Bind the framebuffer surfaces
+ BindFramebufferSurfaces(color_surface, depth_surface, has_stencil);
+
+ // Sync the viewport
+ SyncViewport(surfaces_rect, res_scale);
+
+ // TODO(bunnei): Sync framebuffer_scale uniform here
+ // TODO(bunnei): Sync scissorbox uniform(s) here
+ // TODO(bunnei): Sync and bind the texture surfaces
+
+ // Sync and bind the shader
+ if (shader_dirty) {
+ SetShader();
+ shader_dirty = false;
+ }
+
+ // Sync the uniform data
+ if (uniform_block_data.dirty) {
+ glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(UniformData), &uniform_block_data.data);
+ uniform_block_data.dirty = false;
+ }
+
+ // Viewport can have negative offsets or larger dimensions than our framebuffer sub-rect. Enable
+ // scissor test to prevent drawing outside of the framebuffer region
+ state.scissor.enabled = true;
+ state.scissor.x = draw_rect.left;
+ state.scissor.y = draw_rect.bottom;
+ state.scissor.width = draw_rect.GetWidth();
+ state.scissor.height = draw_rect.GetHeight();
+ state.Apply();
+
+ // Draw the vertex batch
+ const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
+ AnalyzeVertexArray(is_indexed);
+ state.draw.vertex_buffer = stream_buffer->GetHandle();
+ state.Apply();
+
+ size_t buffer_size = static_cast<size_t>(vs_input_size);
+ if (is_indexed) {
+ UNREACHABLE();
+ }
+ buffer_size += sizeof(VSUniformData);
+
+ size_t ptr_pos = 0;
+ u8* buffer_ptr;
+ GLintptr buffer_offset;
+ std::tie(buffer_ptr, buffer_offset) =
+ stream_buffer->Map(static_cast<GLsizeiptr>(buffer_size), 4);
+
+ SetupVertexArray(buffer_ptr, buffer_offset);
+ ptr_pos += vs_input_size;
+
+ GLintptr index_buffer_offset = 0;
+ if (is_indexed) {
+ UNREACHABLE();
+ }
+
+ SetupVertexShader(reinterpret_cast<VSUniformData*>(&buffer_ptr[ptr_pos]),
+ buffer_offset + static_cast<GLintptr>(ptr_pos));
+ const GLintptr vs_ubo_offset = buffer_offset + static_cast<GLintptr>(ptr_pos);
+ ptr_pos += sizeof(VSUniformData);
+
+ stream_buffer->Unmap();
+
+ const auto copy_buffer = [&](GLuint handle, GLintptr offset, GLsizeiptr size) {
+ if (has_ARB_direct_state_access) {
+ glCopyNamedBufferSubData(stream_buffer->GetHandle(), handle, offset, 0, size);
+ } else {
+ glBindBuffer(GL_COPY_WRITE_BUFFER, handle);
+ glCopyBufferSubData(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, offset, 0, size);
+ }
+ };
+
+ copy_buffer(vs_uniform_buffer.handle, vs_ubo_offset, sizeof(VSUniformData));
+
+ glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, current_shader->shader.handle);
+
+ if (is_indexed) {
+ UNREACHABLE();
+ } else {
+ glDrawArrays(MaxwellToGL::PrimitiveTopology(regs.draw.topology), 0,
+ regs.vertex_buffer.count);
+ }
+
+ // Disable scissor test
+ state.scissor.enabled = false;
+
+ accelerate_draw = AccelDraw::Disabled;
+
+ // Unbind textures for potential future use as framebuffer attachments
+ for (auto& texture_unit : state.texture_units) {
+ texture_unit.texture_2d = 0;
+ }
+ state.Apply();
+
+ // Mark framebuffer surfaces as dirty
+ MathUtil::Rectangle<u32> draw_rect_unscaled{
+ draw_rect.left / res_scale, draw_rect.top / res_scale, draw_rect.right / res_scale,
+ draw_rect.bottom / res_scale};
+
+ if (color_surface != nullptr && write_color_fb) {
+ auto interval = color_surface->GetSubRectInterval(draw_rect_unscaled);
+ res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval),
+ color_surface);
+ }
+ if (depth_surface != nullptr && write_depth_fb) {
+ auto interval = depth_surface->GetSubRectInterval(draw_rect_unscaled);
+ res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval),
+ depth_surface);
+ }
+}
+
+void RasterizerOpenGL::NotifyMaxwellRegisterChanged(u32 id) {}
+
+void RasterizerOpenGL::FlushAll() {
+ MICROPROFILE_SCOPE(OpenGL_CacheManagement);
+ res_cache.FlushAll();
+}
+
+void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {
+ MICROPROFILE_SCOPE(OpenGL_CacheManagement);
+ res_cache.FlushRegion(addr, size);
+}
+
+void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
+ MICROPROFILE_SCOPE(OpenGL_CacheManagement);
+ res_cache.InvalidateRegion(addr, size, nullptr);
+}
+
+void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
+ MICROPROFILE_SCOPE(OpenGL_CacheManagement);
+ res_cache.FlushRegion(addr, size);
+ res_cache.InvalidateRegion(addr, size, nullptr);
+}
+
+bool RasterizerOpenGL::AccelerateDisplayTransfer(const void* config) {
+ MICROPROFILE_SCOPE(OpenGL_Blits);
+ UNREACHABLE();
+ return true;
+}
+
+bool RasterizerOpenGL::AccelerateTextureCopy(const void* config) {
+ UNREACHABLE();
+ return true;
+}
+
+bool RasterizerOpenGL::AccelerateFill(const void* config) {
+ UNREACHABLE();
+ return true;
+}
+
+bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& framebuffer,
+ VAddr framebuffer_addr, u32 pixel_stride,
+ ScreenInfo& screen_info) {
+ if (framebuffer_addr == 0) {
+ return false;
+ }
+ MICROPROFILE_SCOPE(OpenGL_CacheManagement);
+
+ SurfaceParams src_params;
+ src_params.addr = framebuffer_addr;
+ src_params.width = std::min(framebuffer.width, pixel_stride);
+ src_params.height = framebuffer.height;
+ src_params.stride = pixel_stride;
+ src_params.is_tiled = false;
+ src_params.pixel_format =
+ SurfaceParams::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format);
+ src_params.UpdateParams();
+
+ MathUtil::Rectangle<u32> src_rect;
+ Surface src_surface;
+ std::tie(src_surface, src_rect) =
+ res_cache.GetSurfaceSubRect(src_params, ScaleMatch::Ignore, true);
+
+ if (src_surface == nullptr) {
+ return false;
+ }
+
+ u32 scaled_width = src_surface->GetScaledWidth();
+ u32 scaled_height = src_surface->GetScaledHeight();
+
+ screen_info.display_texcoords = MathUtil::Rectangle<float>(
+ (float)src_rect.bottom / (float)scaled_height, (float)src_rect.left / (float)scaled_width,
+ (float)src_rect.top / (float)scaled_height, (float)src_rect.right / (float)scaled_width);
+
+ screen_info.display_texture = src_surface->texture.handle;
+
+ return true;
+}
+
+void RasterizerOpenGL::SetShader() {
+ // TODO(bunnei): The below sets up a static test shader for passing untransformed vertices to
+ // OpenGL for rendering. This should be removed/replaced when we start emulating Maxwell
+ // shaders.
+
+ static constexpr char vertex_shader[] = R"(
+#version 150 core
+
+in vec2 vert_position;
+in vec2 vert_tex_coord;
+out vec2 frag_tex_coord;
+
+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(mat3x2(0.0015625f, 0.0, 0.0, -0.0027778, -1.0, 1.0)) * vert_position + mat3x2(0.0015625f, 0.0, 0.0, -0.0027778, -1.0, 1.0)[2], 0.0, 1.0);
+ frag_tex_coord = vert_tex_coord;
+}
+)";
+
+ static constexpr char fragment_shader[] = R"(
+#version 150 core
+
+in vec2 frag_tex_coord;
+out vec4 color;
+
+uniform sampler2D color_texture;
+
+void main() {
+ color = vec4(1.0, 0.0, 1.0, 0.0);
+}
+)";
+
+ if (current_shader) {
+ return;
+ }
+
+ LOG_CRITICAL(Render_OpenGL, "Emulated shaders are not supported! Using a passthrough shader.");
+
+ current_shader = &test_shader;
+ if (has_ARB_separate_shader_objects) {
+ test_shader.shader.Create(vertex_shader, nullptr, fragment_shader, {}, true);
+ glActiveShaderProgram(pipeline.handle, test_shader.shader.handle);
+ } else {
+ UNREACHABLE();
+ }
+
+ state.draw.shader_program = test_shader.shader.handle;
+ state.Apply();
+
+ if (has_ARB_separate_shader_objects) {
+ state.draw.shader_program = 0;
+ state.Apply();
+ }
+}
+
+void RasterizerOpenGL::BindFramebufferSurfaces(const Surface& color_surface,
+ const Surface& depth_surface, bool has_stencil) {
+ state.draw.draw_framebuffer = framebuffer.handle;
+ state.Apply();
+
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ color_surface != nullptr ? color_surface->texture.handle : 0, 0);
+ if (depth_surface != nullptr) {
+ if (has_stencil) {
+ // attach both depth and stencil
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
+ depth_surface->texture.handle, 0);
+ } else {
+ // attach depth
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
+ depth_surface->texture.handle, 0);
+ // clear stencil attachment
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
+ }
+ } else {
+ // clear both depth and stencil attachment
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
+ 0);
+ }
+}
+
+void RasterizerOpenGL::SyncViewport(const MathUtil::Rectangle<u32>& surfaces_rect, u16 res_scale) {
+ const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
+ const MathUtil::Rectangle<s32> viewport_rect{regs.viewport[0].GetRect()};
+
+ state.viewport.x = static_cast<GLint>(surfaces_rect.left) + viewport_rect.left * res_scale;
+ state.viewport.y = static_cast<GLint>(surfaces_rect.bottom) + viewport_rect.bottom * res_scale;
+ state.viewport.width = static_cast<GLsizei>(viewport_rect.GetWidth() * res_scale);
+ state.viewport.height = static_cast<GLsizei>(viewport_rect.GetHeight() * res_scale);
+}
+
+void RasterizerOpenGL::SyncClipEnabled() {
+ UNREACHABLE();
+}
+
+void RasterizerOpenGL::SyncClipCoef() {
+ UNREACHABLE();
+}
+
+void RasterizerOpenGL::SyncCullMode() {
+ UNREACHABLE();
+}
+
+void RasterizerOpenGL::SyncDepthScale() {
+ UNREACHABLE();
+}
+
+void RasterizerOpenGL::SyncDepthOffset() {
+ UNREACHABLE();
+}
+
+void RasterizerOpenGL::SyncBlendEnabled() {
+ UNREACHABLE();
+}
+
+void RasterizerOpenGL::SyncBlendFuncs() {
+ UNREACHABLE();
+}
+
+void RasterizerOpenGL::SyncBlendColor() {
+ UNREACHABLE();
+}
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
new file mode 100644
index 000000000..fd53e94cd
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -0,0 +1,179 @@
+// Copyright 2015 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include <cstring>
+#include <memory>
+#include <unordered_map>
+#include <vector>
+#include <glad/glad.h>
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "common/hash.h"
+#include "common/vector_math.h"
+#include "video_core/rasterizer_interface.h"
+#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
+#include "video_core/renderer_opengl/gl_resource_manager.h"
+#include "video_core/renderer_opengl/gl_shader_gen.h"
+#include "video_core/renderer_opengl/gl_state.h"
+#include "video_core/renderer_opengl/gl_stream_buffer.h"
+
+struct ScreenInfo;
+
+class RasterizerOpenGL : public VideoCore::RasterizerInterface {
+public:
+ RasterizerOpenGL();
+ ~RasterizerOpenGL() override;
+
+ void DrawArrays() override;
+ void NotifyMaxwellRegisterChanged(u32 id) override;
+ void FlushAll() override;
+ void FlushRegion(VAddr addr, u64 size) override;
+ void InvalidateRegion(VAddr addr, u64 size) override;
+ void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
+ bool AccelerateDisplayTransfer(const void* config) override;
+ bool AccelerateTextureCopy(const void* config) override;
+ bool AccelerateFill(const void* config) override;
+ bool AccelerateDisplay(const Tegra::FramebufferConfig& framebuffer, VAddr framebuffer_addr,
+ u32 pixel_stride, ScreenInfo& screen_info) override;
+ bool AccelerateDrawBatch(bool is_indexed) override;
+
+ /// OpenGL shader generated for a given Maxwell register state
+ struct MaxwellShader {
+ /// OpenGL shader resource
+ OGLShader shader;
+ };
+
+ struct VertexShader {
+ OGLShader shader;
+ };
+
+ struct FragmentShader {
+ OGLShader shader;
+ };
+
+ /// Uniform structure for the Uniform Buffer Object, all vectors must be 16-byte aligned
+ // NOTE: Always keep a vec4 at the end. The GL spec is not clear wether the alignment at
+ // the end of a uniform block is included in UNIFORM_BLOCK_DATA_SIZE or not.
+ // Not following that rule will cause problems on some AMD drivers.
+ struct UniformData {};
+
+ // static_assert(
+ // sizeof(UniformData) == 0x460,
+ // "The size of the UniformData structure has changed, update the structure in the shader");
+ static_assert(sizeof(UniformData) < 16384,
+ "UniformData structure must be less than 16kb as per the OpenGL spec");
+
+ struct VSUniformData {};
+ // static_assert(
+ // sizeof(VSUniformData) == 1856,
+ // "The size of the VSUniformData structure has changed, update the structure in the
+ // shader");
+ static_assert(sizeof(VSUniformData) < 16384,
+ "VSUniformData structure must be less than 16kb as per the OpenGL spec");
+
+ struct FSUniformData {};
+ // static_assert(
+ // sizeof(FSUniformData) == 1856,
+ // "The size of the FSUniformData structure has changed, update the structure in the
+ // shader");
+ static_assert(sizeof(FSUniformData) < 16384,
+ "FSUniformData structure must be less than 16kb as per the OpenGL spec");
+
+private:
+ struct SamplerInfo {};
+
+ /// Binds the framebuffer color and depth surface
+ void BindFramebufferSurfaces(const Surface& color_surface, const Surface& depth_surface,
+ bool has_stencil);
+
+ /// Syncs the viewport to match the guest state
+ void SyncViewport(const MathUtil::Rectangle<u32>& surfaces_rect, u16 res_scale);
+
+ /// Syncs the clip enabled status to match the guest state
+ void SyncClipEnabled();
+
+ /// Syncs the clip coefficients to match the guest state
+ void SyncClipCoef();
+
+ /// Sets the OpenGL shader in accordance with the current guest state
+ void SetShader();
+
+ /// Syncs the cull mode to match the guest state
+ void SyncCullMode();
+
+ /// Syncs the depth scale to match the guest state
+ void SyncDepthScale();
+
+ /// Syncs the depth offset to match the guest state
+ void SyncDepthOffset();
+
+ /// Syncs the blend enabled status to match the guest state
+ void SyncBlendEnabled();
+
+ /// Syncs the blend functions to match the guest state
+ void SyncBlendFuncs();
+
+ /// Syncs the blend color to match the guest state
+ void SyncBlendColor();
+
+ bool has_ARB_buffer_storage;
+ bool has_ARB_direct_state_access;
+ bool has_ARB_separate_shader_objects;
+ bool has_ARB_vertex_attrib_binding;
+
+ OpenGLState state;
+
+ RasterizerCacheOpenGL res_cache;
+
+ /// Shader used for test renderering - to be removed once we have emulated shaders
+ MaxwellShader test_shader{};
+
+ const MaxwellShader* current_shader{};
+ bool shader_dirty{};
+
+ struct {
+ UniformData data;
+ bool dirty;
+ } uniform_block_data = {};
+
+ OGLPipeline pipeline;
+ OGLVertexArray sw_vao;
+ OGLVertexArray hw_vao;
+ std::array<bool, 16> hw_vao_enabled_attributes;
+
+ std::array<SamplerInfo, 32> texture_samplers;
+ static constexpr size_t VERTEX_BUFFER_SIZE = 128 * 1024 * 1024;
+ std::unique_ptr<OGLStreamBuffer> vertex_buffer;
+ OGLBuffer uniform_buffer;
+ OGLFramebuffer framebuffer;
+
+ static constexpr size_t STREAM_BUFFER_SIZE = 4 * 1024 * 1024;
+ std::unique_ptr<OGLStreamBuffer> stream_buffer;
+
+ GLsizeiptr vs_input_size;
+
+ void AnalyzeVertexArray(bool is_indexed);
+ void SetupVertexArray(u8* array_ptr, GLintptr buffer_offset);
+
+ OGLBuffer vs_uniform_buffer;
+ std::unordered_map<GLShader::MaxwellVSConfig, VertexShader*> vs_shader_map;
+ std::unordered_map<std::string, VertexShader> vs_shader_cache;
+ OGLShader vs_default_shader;
+
+ void SetupVertexShader(VSUniformData* ub_ptr, GLintptr buffer_offset);
+
+ OGLBuffer fs_uniform_buffer;
+ std::unordered_map<GLShader::MaxwellFSConfig, FragmentShader*> fs_shader_map;
+ std::unordered_map<std::string, FragmentShader> fs_shader_cache;
+ OGLShader fs_default_shader;
+
+ void SetupFragmentShader(FSUniformData* ub_ptr, GLintptr buffer_offset);
+
+ enum class AccelDraw { Disabled, Arrays, Indexed };
+ AccelDraw accelerate_draw;
+};
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
new file mode 100644
index 000000000..2ffbd3bab
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -0,0 +1,1432 @@
+// Copyright 2015 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <atomic>
+#include <cstring>
+#include <iterator>
+#include <memory>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+#include <boost/optional.hpp>
+#include <boost/range/iterator_range.hpp>
+#include <glad/glad.h>
+#include "common/alignment.h"
+#include "common/bit_field.h"
+#include "common/color.h"
+#include "common/logging/log.h"
+#include "common/math_util.h"
+#include "common/microprofile.h"
+#include "common/scope_exit.h"
+#include "common/vector_math.h"
+#include "core/core.h"
+#include "core/frontend/emu_window.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/vm_manager.h"
+#include "core/memory.h"
+#include "core/settings.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
+#include "video_core/renderer_opengl/gl_state.h"
+#include "video_core/utils.h"
+#include "video_core/video_core.h"
+
+using SurfaceType = SurfaceParams::SurfaceType;
+using PixelFormat = SurfaceParams::PixelFormat;
+
+struct FormatTuple {
+ GLint internal_format;
+ GLenum format;
+ GLenum type;
+};
+
+static constexpr std::array<FormatTuple, 5> fb_format_tuples = {{
+ {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8}, // RGBA8
+ {GL_RGB8, GL_BGR, GL_UNSIGNED_BYTE}, // RGB8
+ {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, // RGB5A1
+ {GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565
+ {GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}, // RGBA4
+}};
+
+static constexpr std::array<FormatTuple, 4> depth_format_tuples = {{
+ {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16
+ {},
+ {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT}, // D24
+ {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24S8
+}};
+
+static constexpr FormatTuple tex_tuple = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE};
+
+static const FormatTuple& GetFormatTuple(PixelFormat pixel_format) {
+ const SurfaceType type = SurfaceParams::GetFormatType(pixel_format);
+ if (type == SurfaceType::Color) {
+ ASSERT(static_cast<size_t>(pixel_format) < fb_format_tuples.size());
+ return fb_format_tuples[static_cast<unsigned int>(pixel_format)];
+ } else if (type == SurfaceType::Depth || type == SurfaceType::DepthStencil) {
+ size_t tuple_idx = static_cast<size_t>(pixel_format) - 14;
+ ASSERT(tuple_idx < depth_format_tuples.size());
+ return depth_format_tuples[tuple_idx];
+ }
+ return tex_tuple;
+}
+
+template <typename Map, typename Interval>
+constexpr auto RangeFromInterval(Map& map, const Interval& interval) {
+ return boost::make_iterator_range(map.equal_range(interval));
+}
+
+static u16 GetResolutionScaleFactor() {
+ return static_cast<u16>(!Settings::values.resolution_factor
+ ? VideoCore::g_emu_window->GetFramebufferLayout().GetScalingRatio()
+ : Settings::values.resolution_factor);
+}
+
+template <bool morton_to_gl, PixelFormat format>
+static void MortonCopyTile(u32 stride, u8* tile_buffer, u8* gl_buffer) {
+ constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / 8;
+ constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format);
+ for (u32 y = 0; y < 8; ++y) {
+ for (u32 x = 0; x < 8; ++x) {
+ u8* tile_ptr = tile_buffer + VideoCore::MortonInterleave(x, y) * bytes_per_pixel;
+ u8* gl_ptr = gl_buffer + ((7 - y) * stride + x) * gl_bytes_per_pixel;
+ if (morton_to_gl) {
+ if (format == PixelFormat::D24S8) {
+ gl_ptr[0] = tile_ptr[3];
+ std::memcpy(gl_ptr + 1, tile_ptr, 3);
+ } else {
+ std::memcpy(gl_ptr, tile_ptr, bytes_per_pixel);
+ }
+ } else {
+ if (format == PixelFormat::D24S8) {
+ std::memcpy(tile_ptr, gl_ptr + 1, 3);
+ tile_ptr[3] = gl_ptr[0];
+ } else {
+ std::memcpy(tile_ptr, gl_ptr, bytes_per_pixel);
+ }
+ }
+ }
+ }
+}
+
+template <bool morton_to_gl, PixelFormat format>
+static void MortonCopy(u32 stride, u32 height, u8* gl_buffer, VAddr base, VAddr start, VAddr end) {
+ constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / 8;
+ constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format);
+
+ // TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should check the
+ // configuration for this and perform more generic un/swizzle
+ LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!");
+ VideoCore::MortonCopyPixels128(stride, height, bytes_per_pixel, gl_bytes_per_pixel,
+ Memory::GetPointer(base), gl_buffer, morton_to_gl);
+}
+
+static constexpr std::array<void (*)(u32, u32, u8*, VAddr, VAddr, VAddr), 18> morton_to_gl_fns = {
+ MortonCopy<true, PixelFormat::RGBA8>,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+};
+
+static constexpr std::array<void (*)(u32, u32, u8*, VAddr, VAddr, VAddr), 18> gl_to_morton_fns = {
+ MortonCopy<false, PixelFormat::RGBA8>,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+};
+
+// Allocate an uninitialized texture of appropriate size and format for the surface
+static void AllocateSurfaceTexture(GLuint texture, const FormatTuple& format_tuple, u32 width,
+ u32 height) {
+ OpenGLState cur_state = OpenGLState::GetCurState();
+
+ // Keep track of previous texture bindings
+ GLuint old_tex = cur_state.texture_units[0].texture_2d;
+ cur_state.texture_units[0].texture_2d = texture;
+ cur_state.Apply();
+ glActiveTexture(GL_TEXTURE0);
+
+ glTexImage2D(GL_TEXTURE_2D, 0, format_tuple.internal_format, width, height, 0,
+ format_tuple.format, format_tuple.type, nullptr);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ // Restore previous texture bindings
+ cur_state.texture_units[0].texture_2d = old_tex;
+ cur_state.Apply();
+}
+
+static bool BlitTextures(GLuint src_tex, const MathUtil::Rectangle<u32>& src_rect, GLuint dst_tex,
+ const MathUtil::Rectangle<u32>& dst_rect, SurfaceType type,
+ GLuint read_fb_handle, GLuint draw_fb_handle) {
+ OpenGLState state = OpenGLState::GetCurState();
+
+ OpenGLState prev_state = state;
+ SCOPE_EXIT({ prev_state.Apply(); });
+
+ // Make sure textures aren't bound to texture units, since going to bind them to framebuffer
+ // components
+ state.ResetTexture(src_tex);
+ state.ResetTexture(dst_tex);
+
+ state.draw.read_framebuffer = read_fb_handle;
+ state.draw.draw_framebuffer = draw_fb_handle;
+ state.Apply();
+
+ u32 buffers = 0;
+
+ if (type == SurfaceType::Color || type == SurfaceType::Texture) {
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, src_tex,
+ 0);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
+ 0);
+
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex,
+ 0);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
+ 0);
+
+ buffers = GL_COLOR_BUFFER_BIT;
+ } else if (type == SurfaceType::Depth) {
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, src_tex, 0);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
+
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, dst_tex, 0);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
+
+ buffers = GL_DEPTH_BUFFER_BIT;
+ } else if (type == SurfaceType::DepthStencil) {
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
+ src_tex, 0);
+
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
+ dst_tex, 0);
+
+ buffers = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
+ }
+
+ glBlitFramebuffer(src_rect.left, src_rect.bottom, src_rect.right, src_rect.top, dst_rect.left,
+ dst_rect.bottom, dst_rect.right, dst_rect.top, buffers,
+ buffers == GL_COLOR_BUFFER_BIT ? GL_LINEAR : GL_NEAREST);
+
+ return true;
+}
+
+static bool FillSurface(const Surface& surface, const u8* fill_data,
+ const MathUtil::Rectangle<u32>& fill_rect, GLuint draw_fb_handle) {
+ UNREACHABLE();
+ return {};
+}
+
+SurfaceParams SurfaceParams::FromInterval(SurfaceInterval interval) const {
+ SurfaceParams params = *this;
+ const u32 tiled_size = is_tiled ? 8 : 1;
+ const u64 stride_tiled_bytes = BytesInPixels(stride * tiled_size);
+ VAddr aligned_start =
+ addr + Common::AlignDown(boost::icl::first(interval) - addr, stride_tiled_bytes);
+ VAddr aligned_end =
+ addr + Common::AlignUp(boost::icl::last_next(interval) - addr, stride_tiled_bytes);
+
+ if (aligned_end - aligned_start > stride_tiled_bytes) {
+ params.addr = aligned_start;
+ params.height = static_cast<u32>((aligned_end - aligned_start) / BytesInPixels(stride));
+ } else {
+ // 1 row
+ ASSERT(aligned_end - aligned_start == stride_tiled_bytes);
+ const u64 tiled_alignment = BytesInPixels(is_tiled ? 8 * 8 : 1);
+ aligned_start =
+ addr + Common::AlignDown(boost::icl::first(interval) - addr, tiled_alignment);
+ aligned_end =
+ addr + Common::AlignUp(boost::icl::last_next(interval) - addr, tiled_alignment);
+ params.addr = aligned_start;
+ params.width = static_cast<u32>(PixelsInBytes(aligned_end - aligned_start) / tiled_size);
+ params.stride = params.width;
+ params.height = tiled_size;
+ }
+ params.UpdateParams();
+
+ return params;
+}
+
+SurfaceInterval SurfaceParams::GetSubRectInterval(MathUtil::Rectangle<u32> unscaled_rect) const {
+ if (unscaled_rect.GetHeight() == 0 || unscaled_rect.GetWidth() == 0) {
+ return {};
+ }
+
+ if (is_tiled) {
+ unscaled_rect.left = Common::AlignDown(unscaled_rect.left, 8) * 8;
+ unscaled_rect.bottom = Common::AlignDown(unscaled_rect.bottom, 8) / 8;
+ unscaled_rect.right = Common::AlignUp(unscaled_rect.right, 8) * 8;
+ unscaled_rect.top = Common::AlignUp(unscaled_rect.top, 8) / 8;
+ }
+
+ const u32 stride_tiled = !is_tiled ? stride : stride * 8;
+
+ const u32 pixel_offset =
+ stride_tiled * (!is_tiled ? unscaled_rect.bottom : (height / 8) - unscaled_rect.top) +
+ unscaled_rect.left;
+
+ const u32 pixels = (unscaled_rect.GetHeight() - 1) * stride_tiled + unscaled_rect.GetWidth();
+
+ return {addr + BytesInPixels(pixel_offset), addr + BytesInPixels(pixel_offset + pixels)};
+}
+
+MathUtil::Rectangle<u32> SurfaceParams::GetSubRect(const SurfaceParams& sub_surface) const {
+ const u32 begin_pixel_index = static_cast<u32>(PixelsInBytes(sub_surface.addr - addr));
+
+ if (is_tiled) {
+ const int x0 = (begin_pixel_index % (stride * 8)) / 8;
+ const int y0 = (begin_pixel_index / (stride * 8)) * 8;
+ // Top to bottom
+ return MathUtil::Rectangle<u32>(x0, height - y0, x0 + sub_surface.width,
+ height - (y0 + sub_surface.height));
+ }
+
+ const int x0 = begin_pixel_index % stride;
+ const int y0 = begin_pixel_index / stride;
+ // Bottom to top
+ return MathUtil::Rectangle<u32>(x0, y0 + sub_surface.height, x0 + sub_surface.width, y0);
+}
+
+MathUtil::Rectangle<u32> SurfaceParams::GetScaledSubRect(const SurfaceParams& sub_surface) const {
+ auto rect = GetSubRect(sub_surface);
+ rect.left = rect.left * res_scale;
+ rect.right = rect.right * res_scale;
+ rect.top = rect.top * res_scale;
+ rect.bottom = rect.bottom * res_scale;
+ return rect;
+}
+
+bool SurfaceParams::ExactMatch(const SurfaceParams& other_surface) const {
+ return std::tie(other_surface.addr, other_surface.width, other_surface.height,
+ other_surface.stride, other_surface.pixel_format, other_surface.is_tiled) ==
+ std::tie(addr, width, height, stride, pixel_format, is_tiled) &&
+ pixel_format != PixelFormat::Invalid;
+}
+
+bool SurfaceParams::CanSubRect(const SurfaceParams& sub_surface) const {
+ return sub_surface.addr >= addr && sub_surface.end <= end &&
+ sub_surface.pixel_format == pixel_format && pixel_format != PixelFormat::Invalid &&
+ sub_surface.is_tiled == is_tiled &&
+ (sub_surface.addr - addr) % BytesInPixels(is_tiled ? 64 : 1) == 0 &&
+ (sub_surface.stride == stride || sub_surface.height <= (is_tiled ? 8u : 1u)) &&
+ GetSubRect(sub_surface).left + sub_surface.width <= stride;
+}
+
+bool SurfaceParams::CanExpand(const SurfaceParams& expanded_surface) const {
+ return pixel_format != PixelFormat::Invalid && pixel_format == expanded_surface.pixel_format &&
+ addr <= expanded_surface.end && expanded_surface.addr <= end &&
+ is_tiled == expanded_surface.is_tiled && stride == expanded_surface.stride &&
+ (std::max(expanded_surface.addr, addr) - std::min(expanded_surface.addr, addr)) %
+ BytesInPixels(stride * (is_tiled ? 8 : 1)) ==
+ 0;
+}
+
+bool SurfaceParams::CanTexCopy(const SurfaceParams& texcopy_params) const {
+ if (pixel_format == PixelFormat::Invalid || addr > texcopy_params.addr ||
+ end < texcopy_params.end) {
+ return false;
+ }
+ if (texcopy_params.width != texcopy_params.stride) {
+ const u32 tile_stride = static_cast<u32>(BytesInPixels(stride * (is_tiled ? 8 : 1)));
+ return (texcopy_params.addr - addr) % BytesInPixels(is_tiled ? 64 : 1) == 0 &&
+ texcopy_params.width % BytesInPixels(is_tiled ? 64 : 1) == 0 &&
+ (texcopy_params.height == 1 || texcopy_params.stride == tile_stride) &&
+ ((texcopy_params.addr - addr) % tile_stride) + texcopy_params.width <= tile_stride;
+ }
+ return FromInterval(texcopy_params.GetInterval()).GetInterval() == texcopy_params.GetInterval();
+}
+
+bool CachedSurface::CanFill(const SurfaceParams& dest_surface,
+ SurfaceInterval fill_interval) const {
+ if (type == SurfaceType::Fill && IsRegionValid(fill_interval) &&
+ boost::icl::first(fill_interval) >= addr &&
+ boost::icl::last_next(fill_interval) <= end && // dest_surface is within our fill range
+ dest_surface.FromInterval(fill_interval).GetInterval() ==
+ fill_interval) { // make sure interval is a rectangle in dest surface
+ if (fill_size * 8 != dest_surface.GetFormatBpp()) {
+ // Check if bits repeat for our fill_size
+ const u32 dest_bytes_per_pixel = std::max(dest_surface.GetFormatBpp() / 8, 1u);
+ std::vector<u8> fill_test(fill_size * dest_bytes_per_pixel);
+
+ for (u32 i = 0; i < dest_bytes_per_pixel; ++i)
+ std::memcpy(&fill_test[i * fill_size], &fill_data[0], fill_size);
+
+ for (u32 i = 0; i < fill_size; ++i)
+ if (std::memcmp(&fill_test[dest_bytes_per_pixel * i], &fill_test[0],
+ dest_bytes_per_pixel) != 0)
+ return false;
+
+ if (dest_surface.GetFormatBpp() == 4 && (fill_test[0] & 0xF) != (fill_test[0] >> 4))
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool CachedSurface::CanCopy(const SurfaceParams& dest_surface,
+ SurfaceInterval copy_interval) const {
+ SurfaceParams subrect_params = dest_surface.FromInterval(copy_interval);
+ ASSERT(subrect_params.GetInterval() == copy_interval);
+ if (CanSubRect(subrect_params))
+ return true;
+
+ if (CanFill(dest_surface, copy_interval))
+ return true;
+
+ return false;
+}
+
+SurfaceInterval SurfaceParams::GetCopyableInterval(const Surface& src_surface) const {
+ SurfaceInterval result{};
+ const auto valid_regions =
+ SurfaceRegions(GetInterval() & src_surface->GetInterval()) - src_surface->invalid_regions;
+ for (auto& valid_interval : valid_regions) {
+ const SurfaceInterval aligned_interval{
+ addr + Common::AlignUp(boost::icl::first(valid_interval) - addr,
+ BytesInPixels(is_tiled ? 8 * 8 : 1)),
+ addr + Common::AlignDown(boost::icl::last_next(valid_interval) - addr,
+ BytesInPixels(is_tiled ? 8 * 8 : 1))};
+
+ if (BytesInPixels(is_tiled ? 8 * 8 : 1) > boost::icl::length(valid_interval) ||
+ boost::icl::length(aligned_interval) == 0) {
+ continue;
+ }
+
+ // Get the rectangle within aligned_interval
+ const u32 stride_bytes = static_cast<u32>(BytesInPixels(stride)) * (is_tiled ? 8 : 1);
+ SurfaceInterval rect_interval{
+ addr + Common::AlignUp(boost::icl::first(aligned_interval) - addr, stride_bytes),
+ addr + Common::AlignDown(boost::icl::last_next(aligned_interval) - addr, stride_bytes),
+ };
+ if (boost::icl::first(rect_interval) > boost::icl::last_next(rect_interval)) {
+ // 1 row
+ rect_interval = aligned_interval;
+ } else if (boost::icl::length(rect_interval) == 0) {
+ // 2 rows that do not make a rectangle, return the larger one
+ const SurfaceInterval row1{boost::icl::first(aligned_interval),
+ boost::icl::first(rect_interval)};
+ const SurfaceInterval row2{boost::icl::first(rect_interval),
+ boost::icl::last_next(aligned_interval)};
+ rect_interval = (boost::icl::length(row1) > boost::icl::length(row2)) ? row1 : row2;
+ }
+
+ if (boost::icl::length(rect_interval) > boost::icl::length(result)) {
+ result = rect_interval;
+ }
+ }
+ return result;
+}
+
+void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surface& dst_surface,
+ SurfaceInterval copy_interval) {
+ SurfaceParams subrect_params = dst_surface->FromInterval(copy_interval);
+ ASSERT(subrect_params.GetInterval() == copy_interval);
+
+ ASSERT(src_surface != dst_surface);
+
+ // This is only called when CanCopy is true, no need to run checks here
+ if (src_surface->type == SurfaceType::Fill) {
+ // FillSurface needs a 4 bytes buffer
+ const u64 fill_offset =
+ (boost::icl::first(copy_interval) - src_surface->addr) % src_surface->fill_size;
+ std::array<u8, 4> fill_buffer;
+
+ u64 fill_buff_pos = fill_offset;
+ for (int i : {0, 1, 2, 3})
+ fill_buffer[i] = src_surface->fill_data[fill_buff_pos++ % src_surface->fill_size];
+
+ FillSurface(dst_surface, &fill_buffer[0], dst_surface->GetScaledSubRect(subrect_params),
+ draw_framebuffer.handle);
+ return;
+ }
+ if (src_surface->CanSubRect(subrect_params)) {
+ BlitTextures(src_surface->texture.handle, src_surface->GetScaledSubRect(subrect_params),
+ dst_surface->texture.handle, dst_surface->GetScaledSubRect(subrect_params),
+ src_surface->type, read_framebuffer.handle, draw_framebuffer.handle);
+ return;
+ }
+ UNREACHABLE();
+}
+
+MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64, 192));
+void CachedSurface::LoadGLBuffer(VAddr load_start, VAddr load_end) {
+ ASSERT(type != SurfaceType::Fill);
+
+ u8* const texture_src_data = Memory::GetPointer(addr);
+ if (texture_src_data == nullptr)
+ return;
+
+ if (gl_buffer == nullptr) {
+ gl_buffer_size = width * height * GetGLBytesPerPixel(pixel_format);
+ gl_buffer.reset(new u8[gl_buffer_size]);
+ }
+
+ MICROPROFILE_SCOPE(OpenGL_SurfaceLoad);
+
+ ASSERT(load_start >= addr && load_end <= end);
+ const u64 start_offset = load_start - addr;
+
+ if (!is_tiled) {
+ ASSERT(type == SurfaceType::Color);
+ const u32 bytes_per_pixel{GetFormatBpp() >> 3};
+
+ // TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should check
+ // the configuration for this and perform more generic un/swizzle
+ LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!");
+ VideoCore::MortonCopyPixels128(width, height, bytes_per_pixel, 4,
+ texture_src_data + start_offset, &gl_buffer[start_offset],
+ true);
+ } else {
+ morton_to_gl_fns[static_cast<size_t>(pixel_format)](stride, height, &gl_buffer[0], addr,
+ load_start, load_end);
+ }
+}
+
+MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64));
+void CachedSurface::FlushGLBuffer(VAddr flush_start, VAddr flush_end) {
+ u8* const dst_buffer = Memory::GetPointer(addr);
+ if (dst_buffer == nullptr)
+ return;
+
+ ASSERT(gl_buffer_size == width * height * GetGLBytesPerPixel(pixel_format));
+
+ // TODO: Should probably be done in ::Memory:: and check for other regions too
+ // same as loadglbuffer()
+ if (flush_start < Memory::VRAM_VADDR_END && flush_end > Memory::VRAM_VADDR_END)
+ flush_end = Memory::VRAM_VADDR_END;
+
+ if (flush_start < Memory::VRAM_VADDR && flush_end > Memory::VRAM_VADDR)
+ flush_start = Memory::VRAM_VADDR;
+
+ MICROPROFILE_SCOPE(OpenGL_SurfaceFlush);
+
+ ASSERT(flush_start >= addr && flush_end <= end);
+ const u64 start_offset = flush_start - addr;
+ const u64 end_offset = flush_end - addr;
+
+ if (type == SurfaceType::Fill) {
+ const u64 coarse_start_offset = start_offset - (start_offset % fill_size);
+ const u64 backup_bytes = start_offset % fill_size;
+ std::array<u8, 4> backup_data;
+ if (backup_bytes)
+ std::memcpy(&backup_data[0], &dst_buffer[coarse_start_offset], backup_bytes);
+
+ for (u64 offset = coarse_start_offset; offset < end_offset; offset += fill_size) {
+ std::memcpy(&dst_buffer[offset], &fill_data[0],
+ std::min(fill_size, end_offset - offset));
+ }
+
+ if (backup_bytes)
+ std::memcpy(&dst_buffer[coarse_start_offset], &backup_data[0], backup_bytes);
+ } else if (!is_tiled) {
+ ASSERT(type == SurfaceType::Color);
+ std::memcpy(dst_buffer + start_offset, &gl_buffer[start_offset], flush_end - flush_start);
+ } else {
+ gl_to_morton_fns[static_cast<size_t>(pixel_format)](stride, height, &gl_buffer[0], addr,
+ flush_start, flush_end);
+ }
+}
+
+MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192));
+void CachedSurface::UploadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle,
+ GLuint draw_fb_handle) {
+ if (type == SurfaceType::Fill)
+ return;
+
+ MICROPROFILE_SCOPE(OpenGL_TextureUL);
+
+ ASSERT(gl_buffer_size == width * height * GetGLBytesPerPixel(pixel_format));
+
+ // Load data from memory to the surface
+ GLint x0 = static_cast<GLint>(rect.left);
+ GLint y0 = static_cast<GLint>(rect.bottom);
+ size_t buffer_offset = (y0 * stride + x0) * GetGLBytesPerPixel(pixel_format);
+
+ const FormatTuple& tuple = GetFormatTuple(pixel_format);
+ GLuint target_tex = texture.handle;
+
+ // If not 1x scale, create 1x texture that we will blit from to replace texture subrect in
+ // surface
+ OGLTexture unscaled_tex;
+ if (res_scale != 1) {
+ x0 = 0;
+ y0 = 0;
+
+ unscaled_tex.Create();
+ AllocateSurfaceTexture(unscaled_tex.handle, tuple, rect.GetWidth(), rect.GetHeight());
+ target_tex = unscaled_tex.handle;
+ }
+
+ OpenGLState cur_state = OpenGLState::GetCurState();
+
+ GLuint old_tex = cur_state.texture_units[0].texture_2d;
+ cur_state.texture_units[0].texture_2d = target_tex;
+ cur_state.Apply();
+
+ // Ensure no bad interactions with GL_UNPACK_ALIGNMENT
+ ASSERT(stride * GetGLBytesPerPixel(pixel_format) % 4 == 0);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(stride));
+
+ glActiveTexture(GL_TEXTURE0);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, static_cast<GLsizei>(rect.GetWidth()),
+ static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type,
+ &gl_buffer[buffer_offset]);
+
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+
+ cur_state.texture_units[0].texture_2d = old_tex;
+ cur_state.Apply();
+
+ if (res_scale != 1) {
+ auto scaled_rect = rect;
+ scaled_rect.left *= res_scale;
+ scaled_rect.top *= res_scale;
+ scaled_rect.right *= res_scale;
+ scaled_rect.bottom *= res_scale;
+
+ BlitTextures(unscaled_tex.handle, {0, rect.GetHeight(), rect.GetWidth(), 0}, texture.handle,
+ scaled_rect, type, read_fb_handle, draw_fb_handle);
+ }
+}
+
+MICROPROFILE_DEFINE(OpenGL_TextureDL, "OpenGL", "Texture Download", MP_RGB(128, 192, 64));
+void CachedSurface::DownloadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle,
+ GLuint draw_fb_handle) {
+ if (type == SurfaceType::Fill)
+ return;
+
+ MICROPROFILE_SCOPE(OpenGL_TextureDL);
+
+ if (gl_buffer == nullptr) {
+ gl_buffer_size = width * height * GetGLBytesPerPixel(pixel_format);
+ gl_buffer.reset(new u8[gl_buffer_size]);
+ }
+
+ OpenGLState state = OpenGLState::GetCurState();
+ OpenGLState prev_state = state;
+ SCOPE_EXIT({ prev_state.Apply(); });
+
+ const FormatTuple& tuple = GetFormatTuple(pixel_format);
+
+ // Ensure no bad interactions with GL_PACK_ALIGNMENT
+ ASSERT(stride * GetGLBytesPerPixel(pixel_format) % 4 == 0);
+ glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(stride));
+ size_t buffer_offset = (rect.bottom * stride + rect.left) * GetGLBytesPerPixel(pixel_format);
+
+ // If not 1x scale, blit scaled texture to a new 1x texture and use that to flush
+ if (res_scale != 1) {
+ auto scaled_rect = rect;
+ scaled_rect.left *= res_scale;
+ scaled_rect.top *= res_scale;
+ scaled_rect.right *= res_scale;
+ scaled_rect.bottom *= res_scale;
+
+ OGLTexture unscaled_tex;
+ unscaled_tex.Create();
+
+ MathUtil::Rectangle<u32> unscaled_tex_rect{0, rect.GetHeight(), rect.GetWidth(), 0};
+ AllocateSurfaceTexture(unscaled_tex.handle, tuple, rect.GetWidth(), rect.GetHeight());
+ BlitTextures(texture.handle, scaled_rect, unscaled_tex.handle, unscaled_tex_rect, type,
+ read_fb_handle, draw_fb_handle);
+
+ state.texture_units[0].texture_2d = unscaled_tex.handle;
+ state.Apply();
+
+ glActiveTexture(GL_TEXTURE0);
+ glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, &gl_buffer[buffer_offset]);
+ } else {
+ state.ResetTexture(texture.handle);
+ state.draw.read_framebuffer = read_fb_handle;
+ state.Apply();
+
+ if (type == SurfaceType::Color || type == SurfaceType::Texture) {
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture.handle, 0);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
+ 0, 0);
+ } else if (type == SurfaceType::Depth) {
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
+ texture.handle, 0);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
+ } else {
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
+ texture.handle, 0);
+ }
+ glReadPixels(static_cast<GLint>(rect.left), static_cast<GLint>(rect.bottom),
+ static_cast<GLsizei>(rect.GetWidth()), static_cast<GLsizei>(rect.GetHeight()),
+ tuple.format, tuple.type, &gl_buffer[buffer_offset]);
+ }
+
+ glPixelStorei(GL_PACK_ROW_LENGTH, 0);
+}
+
+enum MatchFlags {
+ Invalid = 1, // Flag that can be applied to other match types, invalid matches require
+ // validation before they can be used
+ Exact = 1 << 1, // Surfaces perfectly match
+ SubRect = 1 << 2, // Surface encompasses params
+ Copy = 1 << 3, // Surface we can copy from
+ Expand = 1 << 4, // Surface that can expand params
+ TexCopy = 1 << 5 // Surface that will match a display transfer "texture copy" parameters
+};
+
+constexpr MatchFlags operator|(MatchFlags lhs, MatchFlags rhs) {
+ return static_cast<MatchFlags>(static_cast<int>(lhs) | static_cast<int>(rhs));
+}
+
+/// Get the best surface match (and its match type) for the given flags
+template <MatchFlags find_flags>
+Surface FindMatch(const SurfaceCache& surface_cache, const SurfaceParams& params,
+ ScaleMatch match_scale_type,
+ boost::optional<SurfaceInterval> validate_interval = boost::none) {
+ Surface match_surface = nullptr;
+ bool match_valid = false;
+ u32 match_scale = 0;
+ SurfaceInterval match_interval{};
+
+ for (auto& pair : RangeFromInterval(surface_cache, params.GetInterval())) {
+ for (auto& surface : pair.second) {
+ bool res_scale_matched = match_scale_type == ScaleMatch::Exact
+ ? (params.res_scale == surface->res_scale)
+ : (params.res_scale <= surface->res_scale);
+ // validity will be checked in GetCopyableInterval
+ bool is_valid =
+ find_flags & MatchFlags::Copy
+ ? true
+ : surface->IsRegionValid(validate_interval.value_or(params.GetInterval()));
+
+ if (!(find_flags & MatchFlags::Invalid) && !is_valid)
+ continue;
+
+ auto IsMatch_Helper = [&](auto check_type, auto match_fn) {
+ if (!(find_flags & check_type))
+ return;
+
+ bool matched;
+ SurfaceInterval surface_interval;
+ std::tie(matched, surface_interval) = match_fn();
+ if (!matched)
+ return;
+
+ if (!res_scale_matched && match_scale_type != ScaleMatch::Ignore &&
+ surface->type != SurfaceType::Fill)
+ return;
+
+ // Found a match, update only if this is better than the previous one
+ auto UpdateMatch = [&] {
+ match_surface = surface;
+ match_valid = is_valid;
+ match_scale = surface->res_scale;
+ match_interval = surface_interval;
+ };
+
+ if (surface->res_scale > match_scale) {
+ UpdateMatch();
+ return;
+ } else if (surface->res_scale < match_scale) {
+ return;
+ }
+
+ if (is_valid && !match_valid) {
+ UpdateMatch();
+ return;
+ } else if (is_valid != match_valid) {
+ return;
+ }
+
+ if (boost::icl::length(surface_interval) > boost::icl::length(match_interval)) {
+ UpdateMatch();
+ }
+ };
+ IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::Exact>{}, [&] {
+ return std::make_pair(surface->ExactMatch(params), surface->GetInterval());
+ });
+ IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::SubRect>{}, [&] {
+ return std::make_pair(surface->CanSubRect(params), surface->GetInterval());
+ });
+ IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::Copy>{}, [&] {
+ auto copy_interval =
+ params.FromInterval(*validate_interval).GetCopyableInterval(surface);
+ bool matched = boost::icl::length(copy_interval & *validate_interval) != 0 &&
+ surface->CanCopy(params, copy_interval);
+ return std::make_pair(matched, copy_interval);
+ });
+ IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::Expand>{}, [&] {
+ return std::make_pair(surface->CanExpand(params), surface->GetInterval());
+ });
+ IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::TexCopy>{}, [&] {
+ return std::make_pair(surface->CanTexCopy(params), surface->GetInterval());
+ });
+ }
+ }
+ return match_surface;
+}
+
+RasterizerCacheOpenGL::RasterizerCacheOpenGL() {
+ read_framebuffer.Create();
+ draw_framebuffer.Create();
+
+ attributeless_vao.Create();
+
+ d24s8_abgr_buffer.Create();
+ d24s8_abgr_buffer_size = 0;
+
+ const char* vs_source = R"(
+#version 330 core
+const vec2 vertices[4] = vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
+void main() {
+ gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);
+}
+)";
+ const char* fs_source = R"(
+#version 330 core
+
+uniform samplerBuffer tbo;
+uniform vec2 tbo_size;
+uniform vec4 viewport;
+
+out vec4 color;
+
+void main() {
+ vec2 tbo_coord = (gl_FragCoord.xy - viewport.xy) * tbo_size / viewport.zw;
+ int tbo_offset = int(tbo_coord.y) * int(tbo_size.x) + int(tbo_coord.x);
+ color = texelFetch(tbo, tbo_offset).rabg;
+}
+)";
+ d24s8_abgr_shader.Create(vs_source, nullptr, fs_source);
+
+ OpenGLState state = OpenGLState::GetCurState();
+ GLuint old_program = state.draw.shader_program;
+ state.draw.shader_program = d24s8_abgr_shader.handle;
+ state.Apply();
+
+ GLint tbo_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "tbo");
+ ASSERT(tbo_u_id != -1);
+ glUniform1i(tbo_u_id, 0);
+
+ state.draw.shader_program = old_program;
+ state.Apply();
+
+ d24s8_abgr_tbo_size_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "tbo_size");
+ ASSERT(d24s8_abgr_tbo_size_u_id != -1);
+ d24s8_abgr_viewport_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "viewport");
+ ASSERT(d24s8_abgr_viewport_u_id != -1);
+}
+
+RasterizerCacheOpenGL::~RasterizerCacheOpenGL() {
+ FlushAll();
+ while (!surface_cache.empty())
+ UnregisterSurface(*surface_cache.begin()->second.begin());
+}
+
+bool RasterizerCacheOpenGL::BlitSurfaces(const Surface& src_surface,
+ const MathUtil::Rectangle<u32>& src_rect,
+ const Surface& dst_surface,
+ const MathUtil::Rectangle<u32>& dst_rect) {
+ if (!SurfaceParams::CheckFormatsBlittable(src_surface->pixel_format, dst_surface->pixel_format))
+ return false;
+
+ return BlitTextures(src_surface->texture.handle, src_rect, dst_surface->texture.handle,
+ dst_rect, src_surface->type, read_framebuffer.handle,
+ draw_framebuffer.handle);
+}
+
+void RasterizerCacheOpenGL::ConvertD24S8toABGR(GLuint src_tex,
+ const MathUtil::Rectangle<u32>& src_rect,
+ GLuint dst_tex,
+ const MathUtil::Rectangle<u32>& dst_rect) {
+ OpenGLState prev_state = OpenGLState::GetCurState();
+ SCOPE_EXIT({ prev_state.Apply(); });
+
+ OpenGLState state;
+ state.draw.read_framebuffer = read_framebuffer.handle;
+ state.draw.draw_framebuffer = draw_framebuffer.handle;
+ state.Apply();
+
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, d24s8_abgr_buffer.handle);
+
+ GLsizeiptr target_pbo_size = src_rect.GetWidth() * src_rect.GetHeight() * 4;
+ if (target_pbo_size > d24s8_abgr_buffer_size) {
+ d24s8_abgr_buffer_size = target_pbo_size * 2;
+ glBufferData(GL_PIXEL_PACK_BUFFER, d24s8_abgr_buffer_size, nullptr, GL_STREAM_COPY);
+ }
+
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, src_tex,
+ 0);
+ glReadPixels(static_cast<GLint>(src_rect.left), static_cast<GLint>(src_rect.bottom),
+ static_cast<GLsizei>(src_rect.GetWidth()),
+ static_cast<GLsizei>(src_rect.GetHeight()), GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8,
+ 0);
+
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+
+ // PBO now contains src_tex in RABG format
+ state.draw.shader_program = d24s8_abgr_shader.handle;
+ state.draw.vertex_array = attributeless_vao.handle;
+ state.viewport.x = static_cast<GLint>(dst_rect.left);
+ state.viewport.y = static_cast<GLint>(dst_rect.bottom);
+ state.viewport.width = static_cast<GLsizei>(dst_rect.GetWidth());
+ state.viewport.height = static_cast<GLsizei>(dst_rect.GetHeight());
+ state.Apply();
+
+ OGLTexture tbo;
+ tbo.Create();
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_BUFFER, tbo.handle);
+ glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA8, d24s8_abgr_buffer.handle);
+
+ glUniform2f(d24s8_abgr_tbo_size_u_id, static_cast<GLfloat>(src_rect.GetWidth()),
+ static_cast<GLfloat>(src_rect.GetHeight()));
+ glUniform4f(d24s8_abgr_viewport_u_id, static_cast<GLfloat>(state.viewport.x),
+ static_cast<GLfloat>(state.viewport.y), static_cast<GLfloat>(state.viewport.width),
+ static_cast<GLfloat>(state.viewport.height));
+
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, 0);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+ glBindTexture(GL_TEXTURE_BUFFER, 0);
+}
+
+Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale,
+ bool load_if_create) {
+ if (params.addr == 0 || params.height * params.width == 0) {
+ return nullptr;
+ }
+ // Use GetSurfaceSubRect instead
+ ASSERT(params.width == params.stride);
+
+ ASSERT(!params.is_tiled || (params.width % 8 == 0 && params.height % 8 == 0));
+
+ // Check for an exact match in existing surfaces
+ Surface surface =
+ FindMatch<MatchFlags::Exact | MatchFlags::Invalid>(surface_cache, params, match_res_scale);
+
+ if (surface == nullptr) {
+ u16 target_res_scale = params.res_scale;
+ if (match_res_scale != ScaleMatch::Exact) {
+ // This surface may have a subrect of another surface with a higher res_scale, find it
+ // to adjust our params
+ SurfaceParams find_params = params;
+ Surface expandable = FindMatch<MatchFlags::Expand | MatchFlags::Invalid>(
+ surface_cache, find_params, match_res_scale);
+ if (expandable != nullptr && expandable->res_scale > target_res_scale) {
+ target_res_scale = expandable->res_scale;
+ }
+ // Keep res_scale when reinterpreting d24s8 -> rgba8
+ if (params.pixel_format == PixelFormat::RGBA8) {
+ find_params.pixel_format = PixelFormat::D24S8;
+ expandable = FindMatch<MatchFlags::Expand | MatchFlags::Invalid>(
+ surface_cache, find_params, match_res_scale);
+ if (expandable != nullptr && expandable->res_scale > target_res_scale) {
+ target_res_scale = expandable->res_scale;
+ }
+ }
+ }
+ SurfaceParams new_params = params;
+ new_params.res_scale = target_res_scale;
+ surface = CreateSurface(new_params);
+ RegisterSurface(surface);
+ }
+
+ if (load_if_create) {
+ ValidateSurface(surface, params.addr, params.size);
+ }
+
+ return surface;
+}
+
+SurfaceRect_Tuple RasterizerCacheOpenGL::GetSurfaceSubRect(const SurfaceParams& params,
+ ScaleMatch match_res_scale,
+ bool load_if_create) {
+ if (params.addr == 0 || params.height * params.width == 0) {
+ return std::make_tuple(nullptr, MathUtil::Rectangle<u32>{});
+ }
+
+ // Attempt to find encompassing surface
+ Surface surface = FindMatch<MatchFlags::SubRect | MatchFlags::Invalid>(surface_cache, params,
+ match_res_scale);
+
+ // Check if FindMatch failed because of res scaling
+ // If that's the case create a new surface with
+ // the dimensions of the lower res_scale surface
+ // to suggest it should not be used again
+ if (surface == nullptr && match_res_scale != ScaleMatch::Ignore) {
+ surface = FindMatch<MatchFlags::SubRect | MatchFlags::Invalid>(surface_cache, params,
+ ScaleMatch::Ignore);
+ if (surface != nullptr) {
+ ASSERT(surface->res_scale < params.res_scale);
+ SurfaceParams new_params = *surface;
+ new_params.res_scale = params.res_scale;
+
+ surface = CreateSurface(new_params);
+ RegisterSurface(surface);
+ }
+ }
+
+ SurfaceParams aligned_params = params;
+ if (params.is_tiled) {
+ aligned_params.height = Common::AlignUp(params.height, 8);
+ aligned_params.width = Common::AlignUp(params.width, 8);
+ aligned_params.stride = Common::AlignUp(params.stride, 8);
+ aligned_params.UpdateParams();
+ }
+
+ // Check for a surface we can expand before creating a new one
+ if (surface == nullptr) {
+ surface = FindMatch<MatchFlags::Expand | MatchFlags::Invalid>(surface_cache, aligned_params,
+ match_res_scale);
+ if (surface != nullptr) {
+ aligned_params.width = aligned_params.stride;
+ aligned_params.UpdateParams();
+
+ SurfaceParams new_params = *surface;
+ new_params.addr = std::min(aligned_params.addr, surface->addr);
+ new_params.end = std::max(aligned_params.end, surface->end);
+ new_params.size = new_params.end - new_params.addr;
+ new_params.height = static_cast<u32>(
+ new_params.size / aligned_params.BytesInPixels(aligned_params.stride));
+ ASSERT(new_params.size % aligned_params.BytesInPixels(aligned_params.stride) == 0);
+
+ Surface new_surface = CreateSurface(new_params);
+ DuplicateSurface(surface, new_surface);
+
+ // Delete the expanded surface, this can't be done safely yet
+ // because it may still be in use
+ remove_surfaces.emplace(surface);
+
+ surface = new_surface;
+ RegisterSurface(new_surface);
+ }
+ }
+
+ // No subrect found - create and return a new surface
+ if (surface == nullptr) {
+ SurfaceParams new_params = aligned_params;
+ // Can't have gaps in a surface
+ new_params.width = aligned_params.stride;
+ new_params.UpdateParams();
+ // GetSurface will create the new surface and possibly adjust res_scale if necessary
+ surface = GetSurface(new_params, match_res_scale, load_if_create);
+ } else if (load_if_create) {
+ ValidateSurface(surface, aligned_params.addr, aligned_params.size);
+ }
+
+ return std::make_tuple(surface, surface->GetScaledSubRect(params));
+}
+
+Surface RasterizerCacheOpenGL::GetTextureSurface(const void* config) {
+ UNREACHABLE();
+ return {};
+}
+
+SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces(
+ bool using_color_fb, bool using_depth_fb, const MathUtil::Rectangle<s32>& viewport) {
+ const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
+ const auto& memory_manager = Core::System().GetInstance().GPU().memory_manager;
+ const auto& config = regs.rt[0];
+
+ // TODO(bunnei): This is hard corded to use just the first render buffer
+ LOG_WARNING(Render_OpenGL, "hard-coded for render target 0!");
+
+ // update resolution_scale_factor and reset cache if changed
+ // TODO (bunnei): This code was ported as-is from Citra, and is technically not thread-safe. We
+ // need to fix this before making the renderer multi-threaded.
+ static u16 resolution_scale_factor = GetResolutionScaleFactor();
+ if (resolution_scale_factor != GetResolutionScaleFactor()) {
+ resolution_scale_factor = GetResolutionScaleFactor();
+ FlushAll();
+ while (!surface_cache.empty())
+ UnregisterSurface(*surface_cache.begin()->second.begin());
+ }
+
+ MathUtil::Rectangle<u32> viewport_clamped{
+ static_cast<u32>(MathUtil::Clamp(viewport.left, 0, static_cast<s32>(config.width))),
+ static_cast<u32>(MathUtil::Clamp(viewport.top, 0, static_cast<s32>(config.height))),
+ static_cast<u32>(MathUtil::Clamp(viewport.right, 0, static_cast<s32>(config.width))),
+ static_cast<u32>(MathUtil::Clamp(viewport.bottom, 0, static_cast<s32>(config.height)))};
+
+ // get color and depth surfaces
+ SurfaceParams color_params;
+ color_params.is_tiled = true;
+ color_params.res_scale = resolution_scale_factor;
+ color_params.width = config.width;
+ color_params.height = config.height;
+ SurfaceParams depth_params = color_params;
+
+ color_params.addr = memory_manager->PhysicalToVirtualAddress(config.Address());
+ color_params.pixel_format = SurfaceParams::PixelFormatFromRenderTargetFormat(config.format);
+ color_params.UpdateParams();
+
+ ASSERT_MSG(!using_depth_fb, "depth buffer is unimplemented");
+ // depth_params.addr = config.GetDepthBufferPhysicalAddress();
+ // depth_params.pixel_format = SurfaceParams::PixelFormatFromDepthFormat(config.depth_format);
+ // depth_params.UpdateParams();
+
+ auto color_vp_interval = color_params.GetSubRectInterval(viewport_clamped);
+ auto depth_vp_interval = depth_params.GetSubRectInterval(viewport_clamped);
+
+ // Make sure that framebuffers don't overlap if both color and depth are being used
+ if (using_color_fb && using_depth_fb &&
+ boost::icl::length(color_vp_interval & depth_vp_interval)) {
+ LOG_CRITICAL(Render_OpenGL, "Color and depth framebuffer memory regions overlap; "
+ "overlapping framebuffers not supported!");
+ using_depth_fb = false;
+ }
+
+ MathUtil::Rectangle<u32> color_rect{};
+ Surface color_surface = nullptr;
+ if (using_color_fb)
+ std::tie(color_surface, color_rect) =
+ GetSurfaceSubRect(color_params, ScaleMatch::Exact, false);
+
+ MathUtil::Rectangle<u32> depth_rect{};
+ Surface depth_surface = nullptr;
+ if (using_depth_fb)
+ std::tie(depth_surface, depth_rect) =
+ GetSurfaceSubRect(depth_params, ScaleMatch::Exact, false);
+
+ MathUtil::Rectangle<u32> fb_rect{};
+ if (color_surface != nullptr && depth_surface != nullptr) {
+ fb_rect = color_rect;
+ // Color and Depth surfaces must have the same dimensions and offsets
+ if (color_rect.bottom != depth_rect.bottom || color_rect.top != depth_rect.top ||
+ color_rect.left != depth_rect.left || color_rect.right != depth_rect.right) {
+ color_surface = GetSurface(color_params, ScaleMatch::Exact, false);
+ depth_surface = GetSurface(depth_params, ScaleMatch::Exact, false);
+ fb_rect = color_surface->GetScaledRect();
+ }
+ } else if (color_surface != nullptr) {
+ fb_rect = color_rect;
+ } else if (depth_surface != nullptr) {
+ fb_rect = depth_rect;
+ }
+
+ if (color_surface != nullptr) {
+ ValidateSurface(color_surface, boost::icl::first(color_vp_interval),
+ boost::icl::length(color_vp_interval));
+ }
+ if (depth_surface != nullptr) {
+ ValidateSurface(depth_surface, boost::icl::first(depth_vp_interval),
+ boost::icl::length(depth_vp_interval));
+ }
+
+ return std::make_tuple(color_surface, depth_surface, fb_rect);
+}
+
+Surface RasterizerCacheOpenGL::GetFillSurface(const void* config) {
+ UNREACHABLE();
+ return {};
+}
+
+SurfaceRect_Tuple RasterizerCacheOpenGL::GetTexCopySurface(const SurfaceParams& params) {
+ MathUtil::Rectangle<u32> rect{};
+
+ Surface match_surface = FindMatch<MatchFlags::TexCopy | MatchFlags::Invalid>(
+ surface_cache, params, ScaleMatch::Ignore);
+
+ if (match_surface != nullptr) {
+ ValidateSurface(match_surface, params.addr, params.size);
+
+ SurfaceParams match_subrect;
+ if (params.width != params.stride) {
+ const u32 tiled_size = match_surface->is_tiled ? 8 : 1;
+ match_subrect = params;
+ match_subrect.width =
+ static_cast<u32>(match_surface->PixelsInBytes(params.width) / tiled_size);
+ match_subrect.stride =
+ static_cast<u32>(match_surface->PixelsInBytes(params.stride) / tiled_size);
+ match_subrect.height *= tiled_size;
+ } else {
+ match_subrect = match_surface->FromInterval(params.GetInterval());
+ ASSERT(match_subrect.GetInterval() == params.GetInterval());
+ }
+
+ rect = match_surface->GetScaledSubRect(match_subrect);
+ }
+
+ return std::make_tuple(match_surface, rect);
+}
+
+void RasterizerCacheOpenGL::DuplicateSurface(const Surface& src_surface,
+ const Surface& dest_surface) {
+ ASSERT(dest_surface->addr <= src_surface->addr && dest_surface->end >= src_surface->end);
+
+ BlitSurfaces(src_surface, src_surface->GetScaledRect(), dest_surface,
+ dest_surface->GetScaledSubRect(*src_surface));
+
+ dest_surface->invalid_regions -= src_surface->GetInterval();
+ dest_surface->invalid_regions += src_surface->invalid_regions;
+
+ SurfaceRegions regions;
+ for (auto& pair : RangeFromInterval(dirty_regions, src_surface->GetInterval())) {
+ if (pair.second == src_surface) {
+ regions += pair.first;
+ }
+ }
+ for (auto& interval : regions) {
+ dirty_regions.set({interval, dest_surface});
+ }
+}
+
+void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, VAddr addr, u64 size) {
+ if (size == 0)
+ return;
+
+ const SurfaceInterval validate_interval(addr, addr + size);
+
+ if (surface->type == SurfaceType::Fill) {
+ // Sanity check, fill surfaces will always be valid when used
+ ASSERT(surface->IsRegionValid(validate_interval));
+ return;
+ }
+
+ while (true) {
+ const auto it = surface->invalid_regions.find(validate_interval);
+ if (it == surface->invalid_regions.end())
+ break;
+
+ const auto interval = *it & validate_interval;
+ // Look for a valid surface to copy from
+ SurfaceParams params = surface->FromInterval(interval);
+
+ Surface copy_surface =
+ FindMatch<MatchFlags::Copy>(surface_cache, params, ScaleMatch::Ignore, interval);
+ if (copy_surface != nullptr) {
+ SurfaceInterval copy_interval = params.GetCopyableInterval(copy_surface);
+ CopySurface(copy_surface, surface, copy_interval);
+ surface->invalid_regions.erase(copy_interval);
+ continue;
+ }
+
+ // D24S8 to RGBA8
+ if (surface->pixel_format == PixelFormat::RGBA8) {
+ params.pixel_format = PixelFormat::D24S8;
+ Surface reinterpret_surface =
+ FindMatch<MatchFlags::Copy>(surface_cache, params, ScaleMatch::Ignore, interval);
+ if (reinterpret_surface != nullptr) {
+ ASSERT(reinterpret_surface->pixel_format == PixelFormat::D24S8);
+
+ SurfaceInterval convert_interval = params.GetCopyableInterval(reinterpret_surface);
+ SurfaceParams convert_params = surface->FromInterval(convert_interval);
+ auto src_rect = reinterpret_surface->GetScaledSubRect(convert_params);
+ auto dest_rect = surface->GetScaledSubRect(convert_params);
+
+ ConvertD24S8toABGR(reinterpret_surface->texture.handle, src_rect,
+ surface->texture.handle, dest_rect);
+
+ surface->invalid_regions.erase(convert_interval);
+ continue;
+ }
+ }
+
+ // Load data from 3DS memory
+ FlushRegion(params.addr, params.size);
+ surface->LoadGLBuffer(params.addr, params.end);
+ surface->UploadGLTexture(surface->GetSubRect(params), read_framebuffer.handle,
+ draw_framebuffer.handle);
+ surface->invalid_regions.erase(params.GetInterval());
+ }
+}
+
+void RasterizerCacheOpenGL::FlushRegion(VAddr addr, u64 size, Surface flush_surface) {
+ if (size == 0)
+ return;
+
+ const SurfaceInterval flush_interval(addr, addr + size);
+ SurfaceRegions flushed_intervals;
+
+ for (auto& pair : RangeFromInterval(dirty_regions, flush_interval)) {
+ // small sizes imply that this most likely comes from the cpu, flush the entire region
+ // the point is to avoid thousands of small writes every frame if the cpu decides to access
+ // that region, anything higher than 8 you're guaranteed it comes from a service
+ const auto interval = size <= 8 ? pair.first : pair.first & flush_interval;
+ auto& surface = pair.second;
+
+ if (flush_surface != nullptr && surface != flush_surface)
+ continue;
+
+ // Sanity check, this surface is the last one that marked this region dirty
+ ASSERT(surface->IsRegionValid(interval));
+
+ if (surface->type != SurfaceType::Fill) {
+ SurfaceParams params = surface->FromInterval(interval);
+ surface->DownloadGLTexture(surface->GetSubRect(params), read_framebuffer.handle,
+ draw_framebuffer.handle);
+ }
+ surface->FlushGLBuffer(boost::icl::first(interval), boost::icl::last_next(interval));
+ flushed_intervals += interval;
+ }
+ // Reset dirty regions
+ dirty_regions -= flushed_intervals;
+}
+
+void RasterizerCacheOpenGL::FlushAll() {
+ FlushRegion(0, Kernel::VMManager::MAX_ADDRESS);
+}
+
+void RasterizerCacheOpenGL::InvalidateRegion(VAddr addr, u64 size, const Surface& region_owner) {
+ if (size == 0)
+ return;
+
+ const SurfaceInterval invalid_interval(addr, addr + size);
+
+ if (region_owner != nullptr) {
+ ASSERT(region_owner->type != SurfaceType::Texture);
+ ASSERT(addr >= region_owner->addr && addr + size <= region_owner->end);
+ // Surfaces can't have a gap
+ ASSERT(region_owner->width == region_owner->stride);
+ region_owner->invalid_regions.erase(invalid_interval);
+ }
+
+ for (auto& pair : RangeFromInterval(surface_cache, invalid_interval)) {
+ for (auto& cached_surface : pair.second) {
+ if (cached_surface == region_owner)
+ continue;
+
+ // If cpu is invalidating this region we want to remove it
+ // to (likely) mark the memory pages as uncached
+ if (region_owner == nullptr && size <= 8) {
+ FlushRegion(cached_surface->addr, cached_surface->size, cached_surface);
+ remove_surfaces.emplace(cached_surface);
+ continue;
+ }
+
+ const auto interval = cached_surface->GetInterval() & invalid_interval;
+ cached_surface->invalid_regions.insert(interval);
+
+ // Remove only "empty" fill surfaces to avoid destroying and recreating OGL textures
+ if (cached_surface->type == SurfaceType::Fill &&
+ cached_surface->IsSurfaceFullyInvalid()) {
+ remove_surfaces.emplace(cached_surface);
+ }
+ }
+ }
+
+ if (region_owner != nullptr)
+ dirty_regions.set({invalid_interval, region_owner});
+ else
+ dirty_regions.erase(invalid_interval);
+
+ for (auto& remove_surface : remove_surfaces) {
+ if (remove_surface == region_owner) {
+ Surface expanded_surface = FindMatch<MatchFlags::SubRect | MatchFlags::Invalid>(
+ surface_cache, *region_owner, ScaleMatch::Ignore);
+ ASSERT(expanded_surface);
+
+ if ((region_owner->invalid_regions - expanded_surface->invalid_regions).empty()) {
+ DuplicateSurface(region_owner, expanded_surface);
+ } else {
+ continue;
+ }
+ }
+ UnregisterSurface(remove_surface);
+ }
+
+ remove_surfaces.clear();
+}
+
+Surface RasterizerCacheOpenGL::CreateSurface(const SurfaceParams& params) {
+ Surface surface = std::make_shared<CachedSurface>();
+ static_cast<SurfaceParams&>(*surface) = params;
+
+ surface->texture.Create();
+
+ surface->gl_buffer_size = 0;
+ surface->invalid_regions.insert(surface->GetInterval());
+ AllocateSurfaceTexture(surface->texture.handle, GetFormatTuple(surface->pixel_format),
+ surface->GetScaledWidth(), surface->GetScaledHeight());
+
+ return surface;
+}
+
+void RasterizerCacheOpenGL::RegisterSurface(const Surface& surface) {
+ if (surface->registered) {
+ return;
+ }
+ surface->registered = true;
+ surface_cache.add({surface->GetInterval(), SurfaceSet{surface}});
+ UpdatePagesCachedCount(surface->addr, surface->size, 1);
+}
+
+void RasterizerCacheOpenGL::UnregisterSurface(const Surface& surface) {
+ if (!surface->registered) {
+ return;
+ }
+ surface->registered = false;
+ UpdatePagesCachedCount(surface->addr, surface->size, -1);
+ surface_cache.subtract({surface->GetInterval(), SurfaceSet{surface}});
+}
+
+void RasterizerCacheOpenGL::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
+ const u64 num_pages =
+ ((addr + size - 1) >> Memory::PAGE_BITS) - (addr >> Memory::PAGE_BITS) + 1;
+ const u64 page_start = addr >> Memory::PAGE_BITS;
+ const u64 page_end = page_start + num_pages;
+
+ // Interval maps will erase segments if count reaches 0, so if delta is negative we have to
+ // subtract after iterating
+ const auto pages_interval = PageMap::interval_type::right_open(page_start, page_end);
+ if (delta > 0)
+ cached_pages.add({pages_interval, delta});
+
+ for (const auto& pair : RangeFromInterval(cached_pages, pages_interval)) {
+ const auto interval = pair.first & pages_interval;
+ const int count = pair.second;
+
+ const VAddr interval_start_addr = boost::icl::first(interval) << Memory::PAGE_BITS;
+ const VAddr interval_end_addr = boost::icl::last_next(interval) << Memory::PAGE_BITS;
+ const u64 interval_size = interval_end_addr - interval_start_addr;
+
+ if (delta > 0 && count == delta)
+ Memory::RasterizerMarkRegionCached(interval_start_addr, interval_size, true);
+ else if (delta < 0 && count == -delta)
+ Memory::RasterizerMarkRegionCached(interval_start_addr, interval_size, false);
+ else
+ ASSERT(count >= 0);
+ }
+
+ if (delta < 0)
+ cached_pages.add({pages_interval, delta});
+}
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
new file mode 100644
index 000000000..1f660d30c
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
@@ -0,0 +1,369 @@
+// Copyright 2015 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <set>
+#include <tuple>
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
+#endif
+#include <boost/icl/interval_map.hpp>
+#include <boost/icl/interval_set.hpp>
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+#include <glad/glad.h>
+#include "common/assert.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/math_util.h"
+#include "video_core/gpu.h"
+#include "video_core/renderer_opengl/gl_resource_manager.h"
+
+struct CachedSurface;
+using Surface = std::shared_ptr<CachedSurface>;
+using SurfaceSet = std::set<Surface>;
+
+using SurfaceRegions = boost::icl::interval_set<VAddr>;
+using SurfaceMap = boost::icl::interval_map<VAddr, Surface>;
+using SurfaceCache = boost::icl::interval_map<VAddr, SurfaceSet>;
+
+using SurfaceInterval = SurfaceCache::interval_type;
+static_assert(std::is_same<SurfaceRegions::interval_type, SurfaceCache::interval_type>() &&
+ std::is_same<SurfaceMap::interval_type, SurfaceCache::interval_type>(),
+ "incorrect interval types");
+
+using SurfaceRect_Tuple = std::tuple<Surface, MathUtil::Rectangle<u32>>;
+using SurfaceSurfaceRect_Tuple = std::tuple<Surface, Surface, MathUtil::Rectangle<u32>>;
+
+using PageMap = boost::icl::interval_map<u64, int>;
+
+enum class ScaleMatch {
+ Exact, // only accept same res scale
+ Upscale, // only allow higher scale than params
+ Ignore // accept every scaled res
+};
+
+struct SurfaceParams {
+ enum class PixelFormat {
+ // First 5 formats are shared between textures and color buffers
+ RGBA8 = 0,
+ RGB8 = 1,
+ RGB5A1 = 2,
+ RGB565 = 3,
+ RGBA4 = 4,
+
+ // Texture-only formats
+ IA8 = 5,
+ RG8 = 6,
+ I8 = 7,
+ A8 = 8,
+ IA4 = 9,
+ I4 = 10,
+ A4 = 11,
+ ETC1 = 12,
+ ETC1A4 = 13,
+
+ // Depth buffer-only formats
+ D16 = 14,
+ // gap
+ D24 = 16,
+ D24S8 = 17,
+
+ Invalid = 255,
+ };
+
+ enum class SurfaceType {
+ Color = 0,
+ Texture = 1,
+ Depth = 2,
+ DepthStencil = 3,
+ Fill = 4,
+ Invalid = 5
+ };
+
+ static constexpr unsigned int GetFormatBpp(PixelFormat format) {
+ constexpr std::array<unsigned int, 18> bpp_table = {
+ 32, // RGBA8
+ 24, // RGB8
+ 16, // RGB5A1
+ 16, // RGB565
+ 16, // RGBA4
+ 16, // IA8
+ 16, // RG8
+ 8, // I8
+ 8, // A8
+ 8, // IA4
+ 4, // I4
+ 4, // A4
+ 4, // ETC1
+ 8, // ETC1A4
+ 16, // D16
+ 0,
+ 24, // D24
+ 32, // D24S8
+ };
+
+ assert(static_cast<size_t>(format) < bpp_table.size());
+ return bpp_table[static_cast<size_t>(format)];
+ }
+ unsigned int GetFormatBpp() const {
+ return GetFormatBpp(pixel_format);
+ }
+
+ static PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format) {
+ switch (format) {
+ case Tegra::RenderTargetFormat::RGBA8_UNORM:
+ return PixelFormat::RGBA8;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ static PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat format) {
+ switch (format) {
+ case Tegra::FramebufferConfig::PixelFormat::ABGR8:
+ return PixelFormat::RGBA8;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ static bool CheckFormatsBlittable(PixelFormat pixel_format_a, PixelFormat pixel_format_b) {
+ SurfaceType a_type = GetFormatType(pixel_format_a);
+ SurfaceType b_type = GetFormatType(pixel_format_b);
+
+ if ((a_type == SurfaceType::Color || a_type == SurfaceType::Texture) &&
+ (b_type == SurfaceType::Color || b_type == SurfaceType::Texture)) {
+ return true;
+ }
+
+ if (a_type == SurfaceType::Depth && b_type == SurfaceType::Depth) {
+ return true;
+ }
+
+ if (a_type == SurfaceType::DepthStencil && b_type == SurfaceType::DepthStencil) {
+ return true;
+ }
+
+ return false;
+ }
+
+ static constexpr SurfaceType GetFormatType(PixelFormat pixel_format) {
+ if ((unsigned int)pixel_format < 5) {
+ return SurfaceType::Color;
+ }
+
+ if ((unsigned int)pixel_format < 14) {
+ return SurfaceType::Texture;
+ }
+
+ if (pixel_format == PixelFormat::D16 || pixel_format == PixelFormat::D24) {
+ return SurfaceType::Depth;
+ }
+
+ if (pixel_format == PixelFormat::D24S8) {
+ return SurfaceType::DepthStencil;
+ }
+
+ return SurfaceType::Invalid;
+ }
+
+ /// Update the params "size", "end" and "type" from the already set "addr", "width", "height"
+ /// and "pixel_format"
+ void UpdateParams() {
+ if (stride == 0) {
+ stride = width;
+ }
+ type = GetFormatType(pixel_format);
+ size = !is_tiled ? BytesInPixels(stride * (height - 1) + width)
+ : BytesInPixels(stride * 8 * (height / 8 - 1) + width * 8);
+ end = addr + size;
+ }
+
+ SurfaceInterval GetInterval() const {
+ return SurfaceInterval::right_open(addr, end);
+ }
+
+ // Returns the outer rectangle containing "interval"
+ SurfaceParams FromInterval(SurfaceInterval interval) const;
+
+ SurfaceInterval GetSubRectInterval(MathUtil::Rectangle<u32> unscaled_rect) const;
+
+ // Returns the region of the biggest valid rectange within interval
+ SurfaceInterval GetCopyableInterval(const Surface& src_surface) const;
+
+ u32 GetScaledWidth() const {
+ return width * res_scale;
+ }
+
+ u32 GetScaledHeight() const {
+ return height * res_scale;
+ }
+
+ MathUtil::Rectangle<u32> GetRect() const {
+ return {0, height, width, 0};
+ }
+
+ MathUtil::Rectangle<u32> GetScaledRect() const {
+ return {0, GetScaledHeight(), GetScaledWidth(), 0};
+ }
+
+ u64 PixelsInBytes(u64 size) const {
+ return size * CHAR_BIT / GetFormatBpp(pixel_format);
+ }
+
+ u64 BytesInPixels(u64 pixels) const {
+ return pixels * GetFormatBpp(pixel_format) / CHAR_BIT;
+ }
+
+ bool ExactMatch(const SurfaceParams& other_surface) const;
+ bool CanSubRect(const SurfaceParams& sub_surface) const;
+ bool CanExpand(const SurfaceParams& expanded_surface) const;
+ bool CanTexCopy(const SurfaceParams& texcopy_params) const;
+
+ MathUtil::Rectangle<u32> GetSubRect(const SurfaceParams& sub_surface) const;
+ MathUtil::Rectangle<u32> GetScaledSubRect(const SurfaceParams& sub_surface) const;
+
+ VAddr addr = 0;
+ VAddr end = 0;
+ u64 size = 0;
+
+ u32 width = 0;
+ u32 height = 0;
+ u32 stride = 0;
+ u16 res_scale = 1;
+
+ bool is_tiled = false;
+ PixelFormat pixel_format = PixelFormat::Invalid;
+ SurfaceType type = SurfaceType::Invalid;
+};
+
+struct CachedSurface : SurfaceParams {
+ bool CanFill(const SurfaceParams& dest_surface, SurfaceInterval fill_interval) const;
+ bool CanCopy(const SurfaceParams& dest_surface, SurfaceInterval copy_interval) const;
+
+ bool IsRegionValid(SurfaceInterval interval) const {
+ return (invalid_regions.find(interval) == invalid_regions.end());
+ }
+
+ bool IsSurfaceFullyInvalid() const {
+ return (invalid_regions & GetInterval()) == SurfaceRegions(GetInterval());
+ }
+
+ bool registered = false;
+ SurfaceRegions invalid_regions;
+
+ u64 fill_size = 0; /// Number of bytes to read from fill_data
+ std::array<u8, 4> fill_data;
+
+ OGLTexture texture;
+
+ static constexpr unsigned int GetGLBytesPerPixel(PixelFormat format) {
+ // OpenGL needs 4 bpp alignment for D24 since using GL_UNSIGNED_INT as type
+ return format == PixelFormat::Invalid
+ ? 0
+ : (format == PixelFormat::D24 || GetFormatType(format) == SurfaceType::Texture)
+ ? 4
+ : SurfaceParams::GetFormatBpp(format) / 8;
+ }
+
+ std::unique_ptr<u8[]> gl_buffer;
+ size_t gl_buffer_size = 0;
+
+ // Read/Write data in Switch memory to/from gl_buffer
+ void LoadGLBuffer(VAddr load_start, VAddr load_end);
+ void FlushGLBuffer(VAddr flush_start, VAddr flush_end);
+
+ // Upload/Download data in gl_buffer in/to this surface's texture
+ void UploadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle,
+ GLuint draw_fb_handle);
+ void DownloadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle,
+ GLuint draw_fb_handle);
+};
+
+class RasterizerCacheOpenGL : NonCopyable {
+public:
+ RasterizerCacheOpenGL();
+ ~RasterizerCacheOpenGL();
+
+ /// Blit one surface's texture to another
+ bool BlitSurfaces(const Surface& src_surface, const MathUtil::Rectangle<u32>& src_rect,
+ const Surface& dst_surface, const MathUtil::Rectangle<u32>& dst_rect);
+
+ void ConvertD24S8toABGR(GLuint src_tex, const MathUtil::Rectangle<u32>& src_rect,
+ GLuint dst_tex, const MathUtil::Rectangle<u32>& dst_rect);
+
+ /// Copy one surface's region to another
+ void CopySurface(const Surface& src_surface, const Surface& dst_surface,
+ SurfaceInterval copy_interval);
+
+ /// Load a texture from 3DS memory to OpenGL and cache it (if not already cached)
+ Surface GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale,
+ bool load_if_create);
+
+ /// Attempt to find a subrect (resolution scaled) of a surface, otherwise loads a texture from
+ /// 3DS memory to OpenGL and caches it (if not already cached)
+ SurfaceRect_Tuple GetSurfaceSubRect(const SurfaceParams& params, ScaleMatch match_res_scale,
+ bool load_if_create);
+
+ /// Get a surface based on the texture configuration
+ Surface GetTextureSurface(const void* config);
+
+ /// Get the color and depth surfaces based on the framebuffer configuration
+ SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb,
+ const MathUtil::Rectangle<s32>& viewport);
+
+ /// Get a surface that matches the fill config
+ Surface GetFillSurface(const void* config);
+
+ /// Get a surface that matches a "texture copy" display transfer config
+ SurfaceRect_Tuple GetTexCopySurface(const SurfaceParams& params);
+
+ /// Write any cached resources overlapping the region back to memory (if dirty)
+ void FlushRegion(VAddr addr, u64 size, Surface flush_surface = nullptr);
+
+ /// Mark region as being invalidated by region_owner (nullptr if 3DS memory)
+ void InvalidateRegion(VAddr addr, u64 size, const Surface& region_owner);
+
+ /// Flush all cached resources tracked by this cache manager
+ void FlushAll();
+
+private:
+ void DuplicateSurface(const Surface& src_surface, const Surface& dest_surface);
+
+ /// Update surface's texture for given region when necessary
+ void ValidateSurface(const Surface& surface, VAddr addr, u64 size);
+
+ /// Create a new surface
+ Surface CreateSurface(const SurfaceParams& params);
+
+ /// Register surface into the cache
+ void RegisterSurface(const Surface& surface);
+
+ /// Remove surface from the cache
+ void UnregisterSurface(const Surface& surface);
+
+ /// Increase/decrease the number of surface in pages touching the specified region
+ void UpdatePagesCachedCount(VAddr addr, u64 size, int delta);
+
+ SurfaceCache surface_cache;
+ PageMap cached_pages;
+ SurfaceMap dirty_regions;
+ SurfaceSet remove_surfaces;
+
+ OGLFramebuffer read_framebuffer;
+ OGLFramebuffer draw_framebuffer;
+
+ OGLVertexArray attributeless_vao;
+ OGLBuffer d24s8_abgr_buffer;
+ GLsizeiptr d24s8_abgr_buffer_size;
+ OGLShader d24s8_abgr_shader;
+ GLint d24s8_abgr_tbo_size_u_id;
+ GLint d24s8_abgr_viewport_u_id;
+};
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h
index 13301ec9f..7da5e74d1 100644
--- a/src/video_core/renderer_opengl/gl_resource_manager.h
+++ b/src/video_core/renderer_opengl/gl_resource_manager.h
@@ -36,7 +36,7 @@ public:
if (handle == 0)
return;
glDeleteTextures(1, &handle);
- OpenGLState::ResetTexture(handle);
+ OpenGLState::GetCurState().ResetTexture(handle).Apply();
handle = 0;
}
@@ -69,7 +69,7 @@ public:
if (handle == 0)
return;
glDeleteSamplers(1, &handle);
- OpenGLState::ResetSampler(handle);
+ OpenGLState::GetCurState().ResetSampler(handle).Apply();
handle = 0;
}
@@ -91,10 +91,13 @@ public:
}
/// Creates a new internal OpenGL resource and stores the handle
- void Create(const char* vert_shader, const char* frag_shader) {
+ void Create(const char* vert_shader, const char* geo_shader, const char* frag_shader,
+ const std::vector<const char*>& feedback_vars = {},
+ bool separable_program = false) {
if (handle != 0)
return;
- handle = GLShader::LoadProgram(vert_shader, frag_shader);
+ handle = GLShader::LoadProgram(vert_shader, geo_shader, frag_shader, feedback_vars,
+ separable_program);
}
/// Deletes the internal OpenGL resource
@@ -102,7 +105,40 @@ public:
if (handle == 0)
return;
glDeleteProgram(handle);
- OpenGLState::ResetProgram(handle);
+ OpenGLState::GetCurState().ResetProgram(handle).Apply();
+ handle = 0;
+ }
+
+ GLuint handle = 0;
+};
+
+class OGLPipeline : private NonCopyable {
+public:
+ OGLPipeline() = default;
+ OGLPipeline(OGLPipeline&& o) {
+ handle = std::exchange<GLuint>(o.handle, 0);
+ }
+ ~OGLPipeline() {
+ Release();
+ }
+ OGLPipeline& operator=(OGLPipeline&& o) {
+ handle = std::exchange<GLuint>(o.handle, 0);
+ return *this;
+ }
+
+ /// Creates a new internal OpenGL resource and stores the handle
+ void Create() {
+ if (handle != 0)
+ return;
+ glGenProgramPipelines(1, &handle);
+ }
+
+ /// Deletes the internal OpenGL resource
+ void Release() {
+ if (handle == 0)
+ return;
+ glDeleteProgramPipelines(1, &handle);
+ OpenGLState::GetCurState().ResetPipeline(handle).Apply();
handle = 0;
}
@@ -135,13 +171,46 @@ public:
if (handle == 0)
return;
glDeleteBuffers(1, &handle);
- OpenGLState::ResetBuffer(handle);
+ OpenGLState::GetCurState().ResetBuffer(handle).Apply();
handle = 0;
}
GLuint handle = 0;
};
+class OGLSync : private NonCopyable {
+public:
+ OGLSync() = default;
+
+ OGLSync(OGLSync&& o) : handle(std::exchange(o.handle, nullptr)) {}
+
+ ~OGLSync() {
+ Release();
+ }
+ OGLSync& operator=(OGLSync&& o) {
+ Release();
+ handle = std::exchange(o.handle, nullptr);
+ return *this;
+ }
+
+ /// Creates a new internal OpenGL resource and stores the handle
+ void Create() {
+ if (handle != 0)
+ return;
+ handle = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+ }
+
+ /// Deletes the internal OpenGL resource
+ void Release() {
+ if (handle == 0)
+ return;
+ glDeleteSync(handle);
+ handle = 0;
+ }
+
+ GLsync handle = 0;
+};
+
class OGLVertexArray : private NonCopyable {
public:
OGLVertexArray() = default;
@@ -168,7 +237,7 @@ public:
if (handle == 0)
return;
glDeleteVertexArrays(1, &handle);
- OpenGLState::ResetVertexArray(handle);
+ OpenGLState::GetCurState().ResetVertexArray(handle).Apply();
handle = 0;
}
@@ -201,7 +270,7 @@ public:
if (handle == 0)
return;
glDeleteFramebuffers(1, &handle);
- OpenGLState::ResetFramebuffer(handle);
+ OpenGLState::GetCurState().ResetFramebuffer(handle).Apply();
handle = 0;
}
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
new file mode 100644
index 000000000..564ea8f9e
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -0,0 +1,58 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <string>
+#include <queue>
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "video_core/renderer_opengl/gl_shader_decompiler.h"
+
+namespace Maxwell3D {
+namespace Shader {
+namespace Decompiler {
+
+constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH;
+
+class Impl {
+public:
+ Impl(const std::array<u32, MAX_PROGRAM_CODE_LENGTH>& program_code,
+ const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>& swizzle_data, u32 main_offset,
+ const std::function<std::string(u32)>& inputreg_getter,
+ const std::function<std::string(u32)>& outputreg_getter, bool sanitize_mul,
+ const std::string& emit_cb, const std::string& setemit_cb)
+ : program_code(program_code), swizzle_data(swizzle_data), main_offset(main_offset),
+ inputreg_getter(inputreg_getter), outputreg_getter(outputreg_getter),
+ sanitize_mul(sanitize_mul), emit_cb(emit_cb), setemit_cb(setemit_cb) {}
+
+ std::string Decompile() {
+ UNREACHABLE();
+ return {};
+ }
+
+private:
+ const std::array<u32, MAX_PROGRAM_CODE_LENGTH>& program_code;
+ const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>& swizzle_data;
+ u32 main_offset;
+ const std::function<std::string(u32)>& inputreg_getter;
+ const std::function<std::string(u32)>& outputreg_getter;
+ bool sanitize_mul;
+ const std::string& emit_cb;
+ const std::string& setemit_cb;
+};
+
+std::string DecompileProgram(const std::array<u32, MAX_PROGRAM_CODE_LENGTH>& program_code,
+ const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>& swizzle_data,
+ u32 main_offset,
+ const std::function<std::string(u32)>& inputreg_getter,
+ const std::function<std::string(u32)>& outputreg_getter,
+ bool sanitize_mul, const std::string& emit_cb,
+ const std::string& setemit_cb) {
+ Impl impl(program_code, swizzle_data, main_offset, inputreg_getter, outputreg_getter,
+ sanitize_mul, emit_cb, setemit_cb);
+ return impl.Decompile();
+}
+
+} // namespace Decompiler
+} // namespace Shader
+} // namespace Maxwell3D
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h
new file mode 100644
index 000000000..02ebfcbe8
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h
@@ -0,0 +1,27 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <functional>
+#include <string>
+#include "common/common_types.h"
+
+namespace Maxwell3D {
+namespace Shader {
+namespace Decompiler {
+
+constexpr size_t MAX_PROGRAM_CODE_LENGTH{0x100000};
+constexpr size_t MAX_SWIZZLE_DATA_LENGTH{0x100000};
+
+std::string DecompileProgram(const std::array<u32, MAX_PROGRAM_CODE_LENGTH>& program_code,
+ const std::array<u32, MAX_SWIZZLE_DATA_LENGTH>& swizzle_data,
+ u32 main_offset,
+ const std::function<std::string(u32)>& inputreg_getter,
+ const std::function<std::string(u32)>& outputreg_getter,
+ bool sanitize_mul, const std::string& emit_cb = "",
+ const std::string& setemit_cb = "");
+
+} // namespace Decompiler
+} // namespace Shader
+} // namespace Maxwell3D
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
new file mode 100644
index 000000000..8f3c98800
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -0,0 +1,20 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "video_core/renderer_opengl/gl_shader_gen.h"
+
+namespace GLShader {
+
+std::string GenerateVertexShader(const MaxwellVSConfig& config) {
+ UNREACHABLE();
+ return {};
+}
+
+std::string GenerateFragmentShader(const MaxwellFSConfig& config) {
+ UNREACHABLE();
+ return {};
+}
+
+} // namespace GLShader
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h
new file mode 100644
index 000000000..5101e7d30
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_shader_gen.h
@@ -0,0 +1,66 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstring>
+#include <string>
+#include <type_traits>
+#include "common/hash.h"
+
+namespace GLShader {
+
+enum Attributes {
+ ATTRIBUTE_POSITION,
+ ATTRIBUTE_COLOR,
+ ATTRIBUTE_TEXCOORD0,
+ ATTRIBUTE_TEXCOORD1,
+ ATTRIBUTE_TEXCOORD2,
+ ATTRIBUTE_TEXCOORD0_W,
+ ATTRIBUTE_NORMQUAT,
+ ATTRIBUTE_VIEW,
+};
+
+struct MaxwellShaderConfigCommon {
+ explicit MaxwellShaderConfigCommon(){};
+};
+
+struct MaxwellVSConfig : MaxwellShaderConfigCommon {
+ explicit MaxwellVSConfig() : MaxwellShaderConfigCommon() {}
+
+ bool operator==(const MaxwellVSConfig& o) const {
+ return std::memcmp(this, &o, sizeof(MaxwellVSConfig)) == 0;
+ };
+};
+
+struct MaxwellFSConfig : MaxwellShaderConfigCommon {
+ explicit MaxwellFSConfig() : MaxwellShaderConfigCommon() {}
+
+ bool operator==(const MaxwellFSConfig& o) const {
+ return std::memcmp(this, &o, sizeof(MaxwellFSConfig)) == 0;
+ };
+};
+
+std::string GenerateVertexShader(const MaxwellVSConfig& config);
+std::string GenerateFragmentShader(const MaxwellFSConfig& config);
+
+} // namespace GLShader
+
+namespace std {
+
+template <>
+struct hash<GLShader::MaxwellVSConfig> {
+ size_t operator()(const GLShader::MaxwellVSConfig& k) const {
+ return Common::ComputeHash64(&k, sizeof(GLShader::MaxwellVSConfig));
+ }
+};
+
+template <>
+struct hash<GLShader::MaxwellFSConfig> {
+ size_t operator()(const GLShader::MaxwellFSConfig& k) const {
+ return Common::ComputeHash64(&k, sizeof(GLShader::MaxwellFSConfig));
+ }
+};
+
+} // namespace std
diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp
index 4da241d83..a6c6204d5 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_util.cpp
@@ -10,53 +10,85 @@
namespace GLShader {
-GLuint LoadProgram(const char* vertex_shader, const char* fragment_shader) {
-
+GLuint LoadProgram(const char* vertex_shader, const char* geometry_shader,
+ const char* fragment_shader, const std::vector<const char*>& feedback_vars,
+ bool separable_program) {
// Create the shaders
- GLuint vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);
- GLuint fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER);
+ GLuint vertex_shader_id = vertex_shader ? glCreateShader(GL_VERTEX_SHADER) : 0;
+ GLuint geometry_shader_id = geometry_shader ? glCreateShader(GL_GEOMETRY_SHADER) : 0;
+ GLuint fragment_shader_id = fragment_shader ? glCreateShader(GL_FRAGMENT_SHADER) : 0;
GLint result = GL_FALSE;
int info_log_length;
- // Compile Vertex Shader
- LOG_DEBUG(Render_OpenGL, "Compiling vertex shader...");
-
- glShaderSource(vertex_shader_id, 1, &vertex_shader, nullptr);
- glCompileShader(vertex_shader_id);
-
- // Check Vertex Shader
- glGetShaderiv(vertex_shader_id, GL_COMPILE_STATUS, &result);
- glGetShaderiv(vertex_shader_id, GL_INFO_LOG_LENGTH, &info_log_length);
-
- if (info_log_length > 1) {
- std::vector<char> vertex_shader_error(info_log_length);
- glGetShaderInfoLog(vertex_shader_id, info_log_length, nullptr, &vertex_shader_error[0]);
- if (result == GL_TRUE) {
- LOG_DEBUG(Render_OpenGL, "%s", &vertex_shader_error[0]);
- } else {
- LOG_ERROR(Render_OpenGL, "Error compiling vertex shader:\n%s", &vertex_shader_error[0]);
+ if (vertex_shader) {
+ // Compile Vertex Shader
+ LOG_DEBUG(Render_OpenGL, "Compiling vertex shader...");
+
+ glShaderSource(vertex_shader_id, 1, &vertex_shader, nullptr);
+ glCompileShader(vertex_shader_id);
+
+ // Check Vertex Shader
+ glGetShaderiv(vertex_shader_id, GL_COMPILE_STATUS, &result);
+ glGetShaderiv(vertex_shader_id, GL_INFO_LOG_LENGTH, &info_log_length);
+
+ if (info_log_length > 1) {
+ std::vector<char> vertex_shader_error(info_log_length);
+ glGetShaderInfoLog(vertex_shader_id, info_log_length, nullptr, &vertex_shader_error[0]);
+ if (result == GL_TRUE) {
+ LOG_DEBUG(Render_OpenGL, "%s", &vertex_shader_error[0]);
+ } else {
+ LOG_CRITICAL(Render_OpenGL, "Error compiling vertex shader:\n%s",
+ &vertex_shader_error[0]);
+ }
}
}
- // Compile Fragment Shader
- LOG_DEBUG(Render_OpenGL, "Compiling fragment shader...");
-
- glShaderSource(fragment_shader_id, 1, &fragment_shader, nullptr);
- glCompileShader(fragment_shader_id);
-
- // Check Fragment Shader
- glGetShaderiv(fragment_shader_id, GL_COMPILE_STATUS, &result);
- glGetShaderiv(fragment_shader_id, GL_INFO_LOG_LENGTH, &info_log_length);
+ if (geometry_shader) {
+ // Compile Geometry Shader
+ LOG_DEBUG(Render_OpenGL, "Compiling geometry shader...");
+
+ glShaderSource(geometry_shader_id, 1, &geometry_shader, nullptr);
+ glCompileShader(geometry_shader_id);
+
+ // Check Geometry Shader
+ glGetShaderiv(geometry_shader_id, GL_COMPILE_STATUS, &result);
+ glGetShaderiv(geometry_shader_id, GL_INFO_LOG_LENGTH, &info_log_length);
+
+ if (info_log_length > 1) {
+ std::vector<char> geometry_shader_error(info_log_length);
+ glGetShaderInfoLog(geometry_shader_id, info_log_length, nullptr,
+ &geometry_shader_error[0]);
+ if (result == GL_TRUE) {
+ LOG_DEBUG(Render_OpenGL, "%s", &geometry_shader_error[0]);
+ } else {
+ LOG_CRITICAL(Render_OpenGL, "Error compiling geometry shader:\n%s",
+ &geometry_shader_error[0]);
+ }
+ }
+ }
- if (info_log_length > 1) {
- std::vector<char> fragment_shader_error(info_log_length);
- glGetShaderInfoLog(fragment_shader_id, info_log_length, nullptr, &fragment_shader_error[0]);
- if (result == GL_TRUE) {
- LOG_DEBUG(Render_OpenGL, "%s", &fragment_shader_error[0]);
- } else {
- LOG_ERROR(Render_OpenGL, "Error compiling fragment shader:\n%s",
- &fragment_shader_error[0]);
+ if (fragment_shader) {
+ // Compile Fragment Shader
+ LOG_DEBUG(Render_OpenGL, "Compiling fragment shader...");
+
+ glShaderSource(fragment_shader_id, 1, &fragment_shader, nullptr);
+ glCompileShader(fragment_shader_id);
+
+ // Check Fragment Shader
+ glGetShaderiv(fragment_shader_id, GL_COMPILE_STATUS, &result);
+ glGetShaderiv(fragment_shader_id, GL_INFO_LOG_LENGTH, &info_log_length);
+
+ if (info_log_length > 1) {
+ std::vector<char> fragment_shader_error(info_log_length);
+ glGetShaderInfoLog(fragment_shader_id, info_log_length, nullptr,
+ &fragment_shader_error[0]);
+ if (result == GL_TRUE) {
+ LOG_DEBUG(Render_OpenGL, "%s", &fragment_shader_error[0]);
+ } else {
+ LOG_CRITICAL(Render_OpenGL, "Error compiling fragment shader:\n%s",
+ &fragment_shader_error[0]);
+ }
}
}
@@ -64,8 +96,25 @@ GLuint LoadProgram(const char* vertex_shader, const char* fragment_shader) {
LOG_DEBUG(Render_OpenGL, "Linking program...");
GLuint program_id = glCreateProgram();
- glAttachShader(program_id, vertex_shader_id);
- glAttachShader(program_id, fragment_shader_id);
+ if (vertex_shader) {
+ glAttachShader(program_id, vertex_shader_id);
+ }
+ if (geometry_shader) {
+ glAttachShader(program_id, geometry_shader_id);
+ }
+ if (fragment_shader) {
+ glAttachShader(program_id, fragment_shader_id);
+ }
+
+ if (!feedback_vars.empty()) {
+ auto varyings = feedback_vars;
+ glTransformFeedbackVaryings(program_id, static_cast<GLsizei>(feedback_vars.size()),
+ &varyings[0], GL_INTERLEAVED_ATTRIBS);
+ }
+
+ if (separable_program) {
+ glProgramParameteri(program_id, GL_PROGRAM_SEPARABLE, GL_TRUE);
+ }
glLinkProgram(program_id);
@@ -79,19 +128,36 @@ GLuint LoadProgram(const char* vertex_shader, const char* fragment_shader) {
if (result == GL_TRUE) {
LOG_DEBUG(Render_OpenGL, "%s", &program_error[0]);
} else {
- LOG_ERROR(Render_OpenGL, "Error linking shader:\n%s", &program_error[0]);
+ LOG_CRITICAL(Render_OpenGL, "Error linking shader:\n%s", &program_error[0]);
}
}
// If the program linking failed at least one of the shaders was probably bad
if (result == GL_FALSE) {
- LOG_ERROR(Render_OpenGL, "Vertex shader:\n%s", vertex_shader);
- LOG_ERROR(Render_OpenGL, "Fragment shader:\n%s", fragment_shader);
+ if (vertex_shader) {
+ LOG_CRITICAL(Render_OpenGL, "Vertex shader:\n%s", vertex_shader);
+ }
+ if (geometry_shader) {
+ LOG_CRITICAL(Render_OpenGL, "Geometry shader:\n%s", geometry_shader);
+ }
+ if (fragment_shader) {
+ LOG_CRITICAL(Render_OpenGL, "Fragment shader:\n%s", fragment_shader);
+ }
}
ASSERT_MSG(result == GL_TRUE, "Shader not linked");
- glDeleteShader(vertex_shader_id);
- glDeleteShader(fragment_shader_id);
+ if (vertex_shader) {
+ glDetachShader(program_id, vertex_shader_id);
+ glDeleteShader(vertex_shader_id);
+ }
+ if (geometry_shader) {
+ glDetachShader(program_id, geometry_shader_id);
+ glDeleteShader(geometry_shader_id);
+ }
+ if (fragment_shader) {
+ glDetachShader(program_id, fragment_shader_id);
+ glDeleteShader(fragment_shader_id);
+ }
return program_id;
}
diff --git a/src/video_core/renderer_opengl/gl_shader_util.h b/src/video_core/renderer_opengl/gl_shader_util.h
index c66e8acd3..fc7b5e080 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.h
+++ b/src/video_core/renderer_opengl/gl_shader_util.h
@@ -4,6 +4,7 @@
#pragma once
+#include <vector>
#include <glad/glad.h>
namespace GLShader {
@@ -11,9 +12,12 @@ namespace GLShader {
/**
* Utility function to create and compile an OpenGL GLSL shader program (vertex + fragment shader)
* @param vertex_shader String of the GLSL vertex shader program
+ * @param geometry_shader String of the GLSL geometry shader program
* @param fragment_shader String of the GLSL fragment shader program
* @returns Handle of the newly created OpenGL shader object
*/
-GLuint LoadProgram(const char* vertex_shader, const char* fragment_shader);
+GLuint LoadProgram(const char* vertex_shader, const char* geometry_shader,
+ const char* fragment_shader, const std::vector<const char*>& feedback_vars = {},
+ bool separable_program = false);
-} // namespace
+} // namespace GLShader
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index 5770ae08f..1d396728b 100644
--- a/src/video_core/renderer_opengl/gl_state.cpp
+++ b/src/video_core/renderer_opengl/gl_state.cpp
@@ -33,7 +33,7 @@ OpenGLState::OpenGLState() {
stencil.action_depth_pass = GL_KEEP;
stencil.action_stencil_fail = GL_KEEP;
- blend.enabled = false;
+ blend.enabled = true;
blend.rgb_equation = GL_FUNC_ADD;
blend.a_equation = GL_FUNC_ADD;
blend.src_rgb_func = GL_ONE;
@@ -68,6 +68,18 @@ OpenGLState::OpenGLState() {
draw.vertex_buffer = 0;
draw.uniform_buffer = 0;
draw.shader_program = 0;
+ draw.program_pipeline = 0;
+
+ scissor.enabled = false;
+ scissor.x = 0;
+ scissor.y = 0;
+ scissor.width = 0;
+ scissor.height = 0;
+
+ viewport.x = 0;
+ viewport.y = 0;
+ viewport.width = 0;
+ viewport.height = 0;
clip_distance = {};
}
@@ -148,9 +160,6 @@ void OpenGLState::Apply() const {
if (blend.enabled != cur_state.blend.enabled) {
if (blend.enabled) {
glEnable(GL_BLEND);
-
- cur_state.logic_op = GL_COPY;
- glLogicOp(cur_state.logic_op);
glDisable(GL_COLOR_LOGIC_OP);
} else {
glDisable(GL_BLEND);
@@ -196,7 +205,7 @@ void OpenGLState::Apply() const {
// Lighting LUTs
if (lighting_lut.texture_buffer != cur_state.lighting_lut.texture_buffer) {
glActiveTexture(TextureUnits::LightingLUT.Enum());
- glBindTexture(GL_TEXTURE_BUFFER, cur_state.lighting_lut.texture_buffer);
+ glBindTexture(GL_TEXTURE_BUFFER, lighting_lut.texture_buffer);
}
// Fog LUT
@@ -263,6 +272,31 @@ void OpenGLState::Apply() const {
glUseProgram(draw.shader_program);
}
+ // Program pipeline
+ if (draw.program_pipeline != cur_state.draw.program_pipeline) {
+ glBindProgramPipeline(draw.program_pipeline);
+ }
+
+ // Scissor test
+ if (scissor.enabled != cur_state.scissor.enabled) {
+ if (scissor.enabled) {
+ glEnable(GL_SCISSOR_TEST);
+ } else {
+ glDisable(GL_SCISSOR_TEST);
+ }
+ }
+
+ if (scissor.x != cur_state.scissor.x || scissor.y != cur_state.scissor.y ||
+ scissor.width != cur_state.scissor.width || scissor.height != cur_state.scissor.height) {
+ glScissor(scissor.x, scissor.y, scissor.width, scissor.height);
+ }
+
+ if (viewport.x != cur_state.viewport.x || viewport.y != cur_state.viewport.y ||
+ viewport.width != cur_state.viewport.width ||
+ viewport.height != cur_state.viewport.height) {
+ glViewport(viewport.x, viewport.y, viewport.width, viewport.height);
+ }
+
// Clip distance
for (size_t i = 0; i < clip_distance.size(); ++i) {
if (clip_distance[i] != cur_state.clip_distance[i]) {
@@ -277,62 +311,75 @@ void OpenGLState::Apply() const {
cur_state = *this;
}
-void OpenGLState::ResetTexture(GLuint handle) {
- for (auto& unit : cur_state.texture_units) {
+OpenGLState& OpenGLState::ResetTexture(GLuint handle) {
+ for (auto& unit : texture_units) {
if (unit.texture_2d == handle) {
unit.texture_2d = 0;
}
}
- if (cur_state.lighting_lut.texture_buffer == handle)
- cur_state.lighting_lut.texture_buffer = 0;
- if (cur_state.fog_lut.texture_buffer == handle)
- cur_state.fog_lut.texture_buffer = 0;
- if (cur_state.proctex_noise_lut.texture_buffer == handle)
- cur_state.proctex_noise_lut.texture_buffer = 0;
- if (cur_state.proctex_color_map.texture_buffer == handle)
- cur_state.proctex_color_map.texture_buffer = 0;
- if (cur_state.proctex_alpha_map.texture_buffer == handle)
- cur_state.proctex_alpha_map.texture_buffer = 0;
- if (cur_state.proctex_lut.texture_buffer == handle)
- cur_state.proctex_lut.texture_buffer = 0;
- if (cur_state.proctex_diff_lut.texture_buffer == handle)
- cur_state.proctex_diff_lut.texture_buffer = 0;
+ if (lighting_lut.texture_buffer == handle)
+ lighting_lut.texture_buffer = 0;
+ if (fog_lut.texture_buffer == handle)
+ fog_lut.texture_buffer = 0;
+ if (proctex_noise_lut.texture_buffer == handle)
+ proctex_noise_lut.texture_buffer = 0;
+ if (proctex_color_map.texture_buffer == handle)
+ proctex_color_map.texture_buffer = 0;
+ if (proctex_alpha_map.texture_buffer == handle)
+ proctex_alpha_map.texture_buffer = 0;
+ if (proctex_lut.texture_buffer == handle)
+ proctex_lut.texture_buffer = 0;
+ if (proctex_diff_lut.texture_buffer == handle)
+ proctex_diff_lut.texture_buffer = 0;
+ return *this;
}
-void OpenGLState::ResetSampler(GLuint handle) {
- for (auto& unit : cur_state.texture_units) {
+OpenGLState& OpenGLState::ResetSampler(GLuint handle) {
+ for (auto& unit : texture_units) {
if (unit.sampler == handle) {
unit.sampler = 0;
}
}
+ return *this;
+}
+
+OpenGLState& OpenGLState::ResetProgram(GLuint handle) {
+ if (draw.shader_program == handle) {
+ draw.shader_program = 0;
+ }
+ return *this;
}
-void OpenGLState::ResetProgram(GLuint handle) {
- if (cur_state.draw.shader_program == handle) {
- cur_state.draw.shader_program = 0;
+OpenGLState& OpenGLState::ResetPipeline(GLuint handle) {
+ if (draw.program_pipeline == handle) {
+ draw.program_pipeline = 0;
}
+ return *this;
}
-void OpenGLState::ResetBuffer(GLuint handle) {
- if (cur_state.draw.vertex_buffer == handle) {
- cur_state.draw.vertex_buffer = 0;
+OpenGLState& OpenGLState::ResetBuffer(GLuint handle) {
+ if (draw.vertex_buffer == handle) {
+ draw.vertex_buffer = 0;
}
- if (cur_state.draw.uniform_buffer == handle) {
- cur_state.draw.uniform_buffer = 0;
+ if (draw.uniform_buffer == handle) {
+ draw.uniform_buffer = 0;
}
+ return *this;
}
-void OpenGLState::ResetVertexArray(GLuint handle) {
- if (cur_state.draw.vertex_array == handle) {
- cur_state.draw.vertex_array = 0;
+OpenGLState& OpenGLState::ResetVertexArray(GLuint handle) {
+ if (draw.vertex_array == handle) {
+ draw.vertex_array = 0;
}
+ return *this;
}
-void OpenGLState::ResetFramebuffer(GLuint handle) {
- if (cur_state.draw.read_framebuffer == handle) {
- cur_state.draw.read_framebuffer = 0;
+OpenGLState& OpenGLState::ResetFramebuffer(GLuint handle) {
+ if (draw.read_framebuffer == handle) {
+ draw.read_framebuffer = 0;
}
- if (cur_state.draw.draw_framebuffer == handle) {
- cur_state.draw.draw_framebuffer = 0;
+ if (draw.draw_framebuffer == handle) {
+ draw.draw_framebuffer = 0;
}
+ return *this;
}
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h
index 437fe34c4..c1f4efc8c 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -85,7 +85,7 @@ public:
struct {
GLuint texture_2d; // GL_TEXTURE_BINDING_2D
GLuint sampler; // GL_SAMPLER_BINDING
- } texture_units[3];
+ } texture_units[32];
struct {
GLuint texture_buffer; // GL_TEXTURE_BINDING_BUFFER
@@ -122,27 +122,44 @@ public:
GLuint vertex_buffer; // GL_ARRAY_BUFFER_BINDING
GLuint uniform_buffer; // GL_UNIFORM_BUFFER_BINDING
GLuint shader_program; // GL_CURRENT_PROGRAM
+ GLuint program_pipeline; // GL_PROGRAM_PIPELINE_BINDING
} draw;
+ struct {
+ bool enabled; // GL_SCISSOR_TEST
+ GLint x;
+ GLint y;
+ GLsizei width;
+ GLsizei height;
+ } scissor;
+
+ struct {
+ GLint x;
+ GLint y;
+ GLsizei width;
+ GLsizei height;
+ } viewport;
+
std::array<bool, 2> clip_distance; // GL_CLIP_DISTANCE
OpenGLState();
/// Get the currently active OpenGL state
- static const OpenGLState& GetCurState() {
+ static OpenGLState GetCurState() {
return cur_state;
}
/// Apply this state as the current OpenGL state
void Apply() const;
- /// Resets and unbinds any references to the given resource in the current OpenGL state
- static void ResetTexture(GLuint handle);
- static void ResetSampler(GLuint handle);
- static void ResetProgram(GLuint handle);
- static void ResetBuffer(GLuint handle);
- static void ResetVertexArray(GLuint handle);
- static void ResetFramebuffer(GLuint handle);
+ /// Resets any references to the given resource
+ OpenGLState& ResetTexture(GLuint handle);
+ OpenGLState& ResetSampler(GLuint handle);
+ OpenGLState& ResetProgram(GLuint handle);
+ OpenGLState& ResetPipeline(GLuint handle);
+ OpenGLState& ResetBuffer(GLuint handle);
+ OpenGLState& ResetVertexArray(GLuint handle);
+ OpenGLState& ResetFramebuffer(GLuint handle);
private:
static OpenGLState cur_state;
diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.cpp b/src/video_core/renderer_opengl/gl_stream_buffer.cpp
new file mode 100644
index 000000000..a2713e9f0
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_stream_buffer.cpp
@@ -0,0 +1,182 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <deque>
+#include <vector>
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "video_core/renderer_opengl/gl_state.h"
+#include "video_core/renderer_opengl/gl_stream_buffer.h"
+
+class OrphanBuffer : public OGLStreamBuffer {
+public:
+ explicit OrphanBuffer(GLenum target) : OGLStreamBuffer(target) {}
+ ~OrphanBuffer() override;
+
+private:
+ void Create(size_t size, size_t sync_subdivide) override;
+ void Release() override;
+
+ std::pair<u8*, GLintptr> Map(size_t size, size_t alignment) override;
+ void Unmap() override;
+
+ std::vector<u8> data;
+};
+
+class StorageBuffer : public OGLStreamBuffer {
+public:
+ explicit StorageBuffer(GLenum target) : OGLStreamBuffer(target) {}
+ ~StorageBuffer() override;
+
+private:
+ void Create(size_t size, size_t sync_subdivide) override;
+ void Release() override;
+
+ std::pair<u8*, GLintptr> Map(size_t size, size_t alignment) override;
+ void Unmap() override;
+
+ struct Fence {
+ OGLSync sync;
+ size_t offset;
+ };
+ std::deque<Fence> head;
+ std::deque<Fence> tail;
+
+ u8* mapped_ptr;
+};
+
+OGLStreamBuffer::OGLStreamBuffer(GLenum target) {
+ gl_target = target;
+}
+
+GLuint OGLStreamBuffer::GetHandle() const {
+ return gl_buffer.handle;
+}
+
+std::unique_ptr<OGLStreamBuffer> OGLStreamBuffer::MakeBuffer(bool storage_buffer, GLenum target) {
+ if (storage_buffer) {
+ return std::make_unique<StorageBuffer>(target);
+ }
+ return std::make_unique<OrphanBuffer>(target);
+}
+
+OrphanBuffer::~OrphanBuffer() {
+ Release();
+}
+
+void OrphanBuffer::Create(size_t size, size_t /*sync_subdivide*/) {
+ buffer_pos = 0;
+ buffer_size = size;
+ data.resize(buffer_size);
+
+ if (gl_buffer.handle == 0) {
+ gl_buffer.Create();
+ glBindBuffer(gl_target, gl_buffer.handle);
+ }
+
+ glBufferData(gl_target, static_cast<GLsizeiptr>(buffer_size), nullptr, GL_STREAM_DRAW);
+}
+
+void OrphanBuffer::Release() {
+ gl_buffer.Release();
+}
+
+std::pair<u8*, GLintptr> OrphanBuffer::Map(size_t size, size_t alignment) {
+ buffer_pos = Common::AlignUp(buffer_pos, alignment);
+
+ if (buffer_pos + size > buffer_size) {
+ Create(std::max(buffer_size, size), 0);
+ }
+
+ mapped_size = size;
+ return std::make_pair(&data[buffer_pos], static_cast<GLintptr>(buffer_pos));
+}
+
+void OrphanBuffer::Unmap() {
+ glBufferSubData(gl_target, static_cast<GLintptr>(buffer_pos),
+ static_cast<GLsizeiptr>(mapped_size), &data[buffer_pos]);
+ buffer_pos += mapped_size;
+}
+
+StorageBuffer::~StorageBuffer() {
+ Release();
+}
+
+void StorageBuffer::Create(size_t size, size_t sync_subdivide) {
+ if (gl_buffer.handle != 0)
+ return;
+
+ buffer_pos = 0;
+ buffer_size = size;
+ buffer_sync_subdivide = std::max<size_t>(sync_subdivide, 1);
+
+ gl_buffer.Create();
+ glBindBuffer(gl_target, gl_buffer.handle);
+
+ glBufferStorage(gl_target, static_cast<GLsizeiptr>(buffer_size), nullptr,
+ GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT);
+ mapped_ptr = reinterpret_cast<u8*>(
+ glMapBufferRange(gl_target, 0, static_cast<GLsizeiptr>(buffer_size),
+ GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_FLUSH_EXPLICIT_BIT));
+}
+
+void StorageBuffer::Release() {
+ if (gl_buffer.handle == 0)
+ return;
+
+ glUnmapBuffer(gl_target);
+
+ gl_buffer.Release();
+ head.clear();
+ tail.clear();
+}
+
+std::pair<u8*, GLintptr> StorageBuffer::Map(size_t size, size_t alignment) {
+ ASSERT(size <= buffer_size);
+
+ OGLSync sync;
+
+ buffer_pos = Common::AlignUp(buffer_pos, alignment);
+ size_t effective_offset = Common::AlignDown(buffer_pos, buffer_sync_subdivide);
+
+ if (!head.empty() &&
+ (effective_offset > head.back().offset || buffer_pos + size > buffer_size)) {
+ ASSERT(head.back().sync.handle == 0);
+ head.back().sync.Create();
+ }
+
+ if (buffer_pos + size > buffer_size) {
+ if (!tail.empty()) {
+ std::swap(sync, tail.back().sync);
+ tail.clear();
+ }
+ std::swap(tail, head);
+ buffer_pos = 0;
+ effective_offset = 0;
+ }
+
+ while (!tail.empty() && buffer_pos + size > tail.front().offset) {
+ std::swap(sync, tail.front().sync);
+ tail.pop_front();
+ }
+
+ if (sync.handle != 0) {
+ glClientWaitSync(sync.handle, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED);
+ sync.Release();
+ }
+
+ if (head.empty() || effective_offset > head.back().offset) {
+ head.emplace_back();
+ head.back().offset = effective_offset;
+ }
+
+ mapped_size = size;
+ return std::make_pair(&mapped_ptr[buffer_pos], static_cast<GLintptr>(buffer_pos));
+}
+
+void StorageBuffer::Unmap() {
+ glFlushMappedBufferRange(gl_target, static_cast<GLintptr>(buffer_pos),
+ static_cast<GLsizeiptr>(mapped_size));
+ buffer_pos += mapped_size;
+}
diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.h b/src/video_core/renderer_opengl/gl_stream_buffer.h
new file mode 100644
index 000000000..4bc2f52e0
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_stream_buffer.h
@@ -0,0 +1,34 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+#include <glad/glad.h>
+#include "common/common_types.h"
+#include "video_core/renderer_opengl/gl_resource_manager.h"
+
+class OGLStreamBuffer : private NonCopyable {
+public:
+ explicit OGLStreamBuffer(GLenum target);
+ virtual ~OGLStreamBuffer() = default;
+
+public:
+ static std::unique_ptr<OGLStreamBuffer> MakeBuffer(bool storage_buffer, GLenum target);
+
+ virtual void Create(size_t size, size_t sync_subdivide) = 0;
+ virtual void Release() {}
+
+ GLuint GetHandle() const;
+
+ virtual std::pair<u8*, GLintptr> Map(size_t size, size_t alignment) = 0;
+ virtual void Unmap() = 0;
+
+protected:
+ OGLBuffer gl_buffer;
+ GLenum gl_target;
+
+ size_t buffer_pos = 0;
+ size_t buffer_size = 0;
+ size_t buffer_sync_subdivide = 0;
+ size_t mapped_size = 0;
+};
diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h
new file mode 100644
index 000000000..d847317ac
--- /dev/null
+++ b/src/video_core/renderer_opengl/maxwell_to_gl.h
@@ -0,0 +1,50 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <glad/glad.h>
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "video_core/engines/maxwell_3d.h"
+
+namespace MaxwellToGL {
+
+using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+
+inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
+ switch (attrib.type) {
+ case Maxwell::VertexAttribute::Type::UnsignedNorm: {
+
+ switch (attrib.size) {
+ case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
+ return GL_UNSIGNED_BYTE;
+ }
+
+ LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size=%s", attrib.SizeString().c_str());
+ UNREACHABLE();
+ return {};
+ }
+
+ case Maxwell::VertexAttribute::Type::Float:
+ return GL_FLOAT;
+ }
+
+ LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex type=%s", attrib.TypeString().c_str());
+ UNREACHABLE();
+ return {};
+}
+
+inline GLenum PrimitiveTopology(Maxwell::PrimitiveTopology topology) {
+ switch (topology) {
+ case Maxwell::PrimitiveTopology::TriangleStrip:
+ return GL_TRIANGLE_STRIP;
+ }
+ LOG_CRITICAL(Render_OpenGL, "Unimplemented primitive topology=%d", topology);
+ UNREACHABLE();
+ return {};
+}
+
+} // namespace MaxwellToGL
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 50396b5c1..78b50b227 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -20,6 +20,7 @@
#include "core/settings.h"
#include "core/tracer/recorder.h"
#include "video_core/renderer_opengl/renderer_opengl.h"
+#include "video_core/utils.h"
#include "video_core/video_core.h"
static const char vertex_shader[] = R"(
@@ -98,195 +99,88 @@ RendererOpenGL::RendererOpenGL() = default;
RendererOpenGL::~RendererOpenGL() = default;
/// Swap buffers (render frame)
-void RendererOpenGL::SwapBuffers(boost::optional<const FramebufferInfo&> framebuffer_info) {
+void RendererOpenGL::SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) {
+ Core::System::GetInstance().perf_stats.EndSystemFrame();
+
// Maintain the rasterizer's state as a priority
OpenGLState prev_state = OpenGLState::GetCurState();
state.Apply();
- if (framebuffer_info != boost::none) {
- // If framebuffer_info is provided, reload it from memory to a texture
- if (screen_info.texture.width != (GLsizei)framebuffer_info->width ||
- screen_info.texture.height != (GLsizei)framebuffer_info->height ||
- screen_info.texture.pixel_format != framebuffer_info->pixel_format) {
+ if (framebuffer != boost::none) {
+ // If framebuffer is provided, reload it from memory to a texture
+ if (screen_info.texture.width != (GLsizei)framebuffer->width ||
+ screen_info.texture.height != (GLsizei)framebuffer->height ||
+ screen_info.texture.pixel_format != framebuffer->pixel_format) {
// Reallocate texture if the framebuffer size has changed.
// This is expected to not happen very often and hence should not be a
// performance problem.
- ConfigureFramebufferTexture(screen_info.texture, *framebuffer_info);
+ ConfigureFramebufferTexture(screen_info.texture, *framebuffer);
}
- LoadFBToScreenInfo(*framebuffer_info, screen_info);
- }
-
- DrawScreens();
- Core::System::GetInstance().perf_stats.EndSystemFrame();
+ // Load the framebuffer from memory, draw it to the screen, and swap buffers
+ LoadFBToScreenInfo(*framebuffer, screen_info);
+ DrawScreen();
+ render_window->SwapBuffers();
+ }
- // Swap buffers
render_window->PollEvents();
- render_window->SwapBuffers();
Core::System::GetInstance().frame_limiter.DoFrameLimiting(CoreTiming::GetGlobalTimeUs());
Core::System::GetInstance().perf_stats.BeginSystemFrame();
+ // Restore the rasterizer state
prev_state.Apply();
RefreshRasterizerSetting();
}
-static inline u32 MortonInterleave128(u32 x, u32 y) {
- // 128x128 Z-Order coordinate from 2D coordinates
- static constexpr u32 xlut[] = {
- 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042,
- 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809,
- 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000,
- 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043,
- 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a,
- 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001,
- 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048,
- 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b,
- 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002,
- 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049,
- 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840,
- 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003,
- 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a,
- 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841,
- 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008,
- 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b,
- 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842,
- 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009,
- 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800,
- 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843,
- 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a,
- 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801,
- 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848,
- 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b,
- 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802,
- 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849,
- 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040,
- 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803,
- 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a,
- 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041,
- 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808,
- 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b,
- 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042,
- 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809,
- 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b,
- };
- static constexpr u32 ylut[] = {
- 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090,
- 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124,
- 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200,
- 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294,
- 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330,
- 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404,
- 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0,
- 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534,
- 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610,
- 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4,
- 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780,
- 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014,
- 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0,
- 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184,
- 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220,
- 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4,
- 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390,
- 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424,
- 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500,
- 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594,
- 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630,
- 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704,
- 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0,
- 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034,
- 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110,
- 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4,
- 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280,
- 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314,
- 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0,
- 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484,
- 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520,
- 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4,
- 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690,
- 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724,
- 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4,
- };
- return xlut[x % 128] + ylut[y % 128];
-}
-
-static inline u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) {
- // Calculates the offset of the position of the pixel in Morton order
- // Framebuffer images are split into 128x128 tiles.
-
- const unsigned int block_height = 128;
- const unsigned int coarse_x = x & ~127;
-
- u32 i = MortonInterleave128(x, y);
-
- const unsigned int offset = coarse_x * block_height;
-
- return (i + offset) * bytes_per_pixel;
-}
-
-static void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, u32 gl_bytes_per_pixel,
- u8* morton_data, u8* gl_data, bool morton_to_gl) {
- u8* data_ptrs[2];
- for (unsigned y = 0; y < height; ++y) {
- for (unsigned x = 0; x < width; ++x) {
- const u32 coarse_y = y & ~127;
- u32 morton_offset =
- GetMortonOffset128(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel;
- u32 gl_pixel_index = (x + (height - 1 - y) * width) * gl_bytes_per_pixel;
-
- data_ptrs[morton_to_gl] = morton_data + morton_offset;
- data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index];
-
- memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
- }
- }
-}
-
/**
* Loads framebuffer from emulated memory into the active OpenGL texture.
*/
-void RendererOpenGL::LoadFBToScreenInfo(const FramebufferInfo& framebuffer_info,
+void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer,
ScreenInfo& screen_info) {
- const u32 bpp{FramebufferInfo::BytesPerPixel(framebuffer_info.pixel_format)};
- const u32 size_in_bytes{framebuffer_info.stride * framebuffer_info.height * bpp};
+ const u32 bytes_per_pixel{Tegra::FramebufferConfig::BytesPerPixel(framebuffer.pixel_format)};
+ const u64 size_in_bytes{framebuffer.stride * framebuffer.height * bytes_per_pixel};
+ const VAddr framebuffer_addr{framebuffer.address + framebuffer.offset};
- MortonCopyPixels128(framebuffer_info.width, framebuffer_info.height, bpp, 4,
- Memory::GetPointer(framebuffer_info.address), gl_framebuffer_data.data(),
- true);
-
- LOG_TRACE(Render_OpenGL, "0x%08x bytes from 0x%llx(%dx%d), fmt %x", size_in_bytes,
- framebuffer_info.address, framebuffer_info.width, framebuffer_info.height,
- (int)framebuffer_info.pixel_format);
+ // Framebuffer orientation handling
+ framebuffer_transform_flags = framebuffer.transform_flags;
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT, which by default
// only allows rows to have a memory alignement of 4.
- ASSERT(framebuffer_info.stride % 4 == 0);
+ ASSERT(framebuffer.stride % 4 == 0);
- // Reset the screen info's display texture to its own permanent texture
- screen_info.display_texture = screen_info.texture.resource.handle;
- screen_info.display_texcoords = MathUtil::Rectangle<float>(0.f, 0.f, 1.f, 1.f);
+ if (!Rasterizer()->AccelerateDisplay(framebuffer, framebuffer_addr, framebuffer.stride,
+ screen_info)) {
+ // Reset the screen info's display texture to its own permanent texture
+ screen_info.display_texture = screen_info.texture.resource.handle;
+ screen_info.display_texcoords = MathUtil::Rectangle<float>(0.f, 0.f, 1.f, 1.f);
- Memory::RasterizerFlushRegion(framebuffer_info.address, size_in_bytes);
+ Rasterizer()->FlushRegion(framebuffer_addr, size_in_bytes);
- state.texture_units[0].texture_2d = screen_info.texture.resource.handle;
- state.Apply();
+ VideoCore::MortonCopyPixels128(framebuffer.width, framebuffer.height, bytes_per_pixel, 4,
+ Memory::GetPointer(framebuffer_addr),
+ gl_framebuffer_data.data(), true);
- glActiveTexture(GL_TEXTURE0);
- glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)framebuffer_info.stride);
+ state.texture_units[0].texture_2d = screen_info.texture.resource.handle;
+ state.Apply();
- // Update existing texture
- // TODO: Test what happens on hardware when you change the framebuffer dimensions so that
- // they differ from the LCD resolution.
- // TODO: Applications could theoretically crash Citra here by specifying too large
- // framebuffer sizes. We should make sure that this cannot happen.
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer_info.width, framebuffer_info.height,
- screen_info.texture.gl_format, screen_info.texture.gl_type,
- gl_framebuffer_data.data());
+ glActiveTexture(GL_TEXTURE0);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride));
- glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ // Update existing texture
+ // TODO: Test what happens on hardware when you change the framebuffer dimensions so that
+ // they differ from the LCD resolution.
+ // TODO: Applications could theoretically crash yuzu here by specifying too large
+ // framebuffer sizes. We should make sure that this cannot happen.
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer.width, framebuffer.height,
+ screen_info.texture.gl_format, screen_info.texture.gl_type,
+ gl_framebuffer_data.data());
- state.texture_units[0].texture_2d = 0;
- state.Apply();
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+
+ state.texture_units[0].texture_2d = 0;
+ state.Apply();
+ }
}
/**
@@ -316,7 +210,7 @@ void RendererOpenGL::InitOpenGLObjects() {
0.0f);
// Link shaders and get variable locations
- shader.Create(vertex_shader, fragment_shader);
+ shader.Create(vertex_shader, nullptr, fragment_shader);
state.draw.shader_program = shader.handle;
state.Apply();
uniform_modelview_matrix = glGetUniformLocation(shader.handle, "modelview_matrix");
@@ -370,14 +264,14 @@ void RendererOpenGL::InitOpenGLObjects() {
}
void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
- const FramebufferInfo& framebuffer_info) {
+ const Tegra::FramebufferConfig& framebuffer) {
- texture.width = framebuffer_info.width;
- texture.height = framebuffer_info.height;
+ texture.width = framebuffer.width;
+ texture.height = framebuffer.height;
GLint internal_format;
- switch (framebuffer_info.pixel_format) {
- case FramebufferInfo::PixelFormat::ABGR8:
+ switch (framebuffer.pixel_format) {
+ case Tegra::FramebufferConfig::PixelFormat::ABGR8:
// Use RGBA8 and swap in the fragment shader
internal_format = GL_RGBA;
texture.gl_format = GL_RGBA;
@@ -385,7 +279,7 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
gl_framebuffer_data.resize(texture.width * texture.height * 4);
break;
default:
- UNIMPLEMENTED();
+ UNREACHABLE();
}
state.texture_units[0].texture_2d = texture.resource.handle;
@@ -399,15 +293,28 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
state.Apply();
}
-void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w,
- float h) {
- auto& texcoords = screen_info.display_texcoords;
+void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w,
+ float h) {
+ const auto& texcoords = screen_info.display_texcoords;
+ auto left = texcoords.left;
+ auto right = texcoords.right;
+ if (framebuffer_transform_flags != Tegra::FramebufferConfig::TransformFlags::Unset)
+ if (framebuffer_transform_flags == Tegra::FramebufferConfig::TransformFlags::FlipV) {
+ // Flip the framebuffer vertically
+ left = texcoords.right;
+ right = texcoords.left;
+ } else {
+ // Other transformations are unsupported
+ LOG_CRITICAL(Render_OpenGL, "Unsupported framebuffer_transform_flags=%d",
+ framebuffer_transform_flags);
+ UNIMPLEMENTED();
+ }
std::array<ScreenRectVertex, 4> vertices = {{
- ScreenRectVertex(x, y, texcoords.top, texcoords.right),
- ScreenRectVertex(x + w, y, texcoords.bottom, texcoords.right),
- ScreenRectVertex(x, y + h, texcoords.top, texcoords.left),
- ScreenRectVertex(x + w, y + h, texcoords.bottom, texcoords.left),
+ ScreenRectVertex(x, y, texcoords.top, right),
+ ScreenRectVertex(x + w, y, texcoords.bottom, right),
+ ScreenRectVertex(x, y + h, texcoords.top, left),
+ ScreenRectVertex(x + w, y + h, texcoords.bottom, left),
}};
state.texture_units[0].texture_2d = screen_info.display_texture;
@@ -423,7 +330,7 @@ void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, fl
/**
* Draws the emulated screens to the emulator window.
*/
-void RendererOpenGL::DrawScreens() {
+void RendererOpenGL::DrawScreen() {
const auto& layout = render_window->GetFramebufferLayout();
const auto& screen = layout.screen;
@@ -439,8 +346,8 @@ void RendererOpenGL::DrawScreens() {
glActiveTexture(GL_TEXTURE0);
glUniform1i(uniform_color_texture, 0);
- DrawSingleScreen(screen_info, (float)screen.left, (float)screen.top, (float)screen.GetWidth(),
- (float)screen.GetHeight());
+ DrawScreenTriangles(screen_info, (float)screen.left, (float)screen.top,
+ (float)screen.GetWidth(), (float)screen.GetHeight());
m_current_frame++;
}
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index db6c355a5..fffd0f9f4 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -21,7 +21,7 @@ struct TextureInfo {
GLsizei height;
GLenum gl_format;
GLenum gl_type;
- RendererBase::FramebufferInfo::PixelFormat pixel_format;
+ Tegra::FramebufferConfig::PixelFormat pixel_format;
};
/// Structure used for storing information about the display target for each 3DS screen
@@ -37,7 +37,7 @@ public:
~RendererOpenGL() override;
/// Swap buffers (render frame)
- void SwapBuffers(boost::optional<const FramebufferInfo&> framebuffer_info) override;
+ void SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) override;
/**
* Set the emulator window to use for renderer
@@ -53,13 +53,14 @@ public:
private:
void InitOpenGLObjects();
- void ConfigureFramebufferTexture(TextureInfo& texture, const FramebufferInfo& framebuffer_info);
- void DrawScreens();
- void DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, float h);
+ void ConfigureFramebufferTexture(TextureInfo& texture,
+ const Tegra::FramebufferConfig& framebuffer);
+ void DrawScreen();
+ void DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, float h);
void UpdateFramerate();
// Loads framebuffer from emulated memory into the display information structure
- void LoadFBToScreenInfo(const FramebufferInfo& framebuffer_info, ScreenInfo& screen_info);
+ void LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer, ScreenInfo& screen_info);
// Fills active OpenGL texture with the given RGBA color.
void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a,
const TextureInfo& texture);
@@ -86,4 +87,7 @@ private:
// Shader attribute input indices
GLuint attrib_position;
GLuint attrib_tex_coord;
+
+ /// Used for transforming the framebuffer orientation
+ Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags;
};
diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp
new file mode 100644
index 000000000..2e87281eb
--- /dev/null
+++ b/src/video_core/textures/decoders.cpp
@@ -0,0 +1,105 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include "common/assert.h"
+#include "video_core/textures/decoders.h"
+#include "video_core/textures/texture.h"
+
+namespace Tegra {
+namespace Texture {
+
+/**
+ * Calculates the offset of an (x, y) position within a swizzled texture.
+ * Taken from the Tegra X1 TRM.
+ */
+static u32 GetSwizzleOffset(u32 x, u32 y, u32 image_width, u32 bytes_per_pixel, u32 block_height) {
+ u32 image_width_in_gobs = image_width * bytes_per_pixel / 64;
+ u32 GOB_address = 0 + (y / (8 * block_height)) * 512 * block_height * image_width_in_gobs +
+ (x * bytes_per_pixel / 64) * 512 * block_height +
+ (y % (8 * block_height) / 8) * 512;
+ x *= bytes_per_pixel;
+ u32 address = GOB_address + ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 +
+ (y % 2) * 16 + (x % 16);
+
+ return address;
+}
+
+static void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel,
+ u8* swizzled_data, u8* unswizzled_data, bool unswizzle,
+ u32 block_height) {
+ u8* data_ptrs[2];
+ for (unsigned y = 0; y < height; ++y) {
+ for (unsigned x = 0; x < width; ++x) {
+ u32 swizzle_offset = GetSwizzleOffset(x, y, width, bytes_per_pixel, block_height);
+ u32 pixel_index = (x + y * width) * out_bytes_per_pixel;
+
+ data_ptrs[unswizzle] = swizzled_data + swizzle_offset;
+ data_ptrs[!unswizzle] = &unswizzled_data[pixel_index];
+
+ std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
+ }
+ }
+}
+
+u32 BytesPerPixel(TextureFormat format) {
+ switch (format) {
+ case TextureFormat::DXT1:
+ // In this case a 'pixel' actually refers to a 4x4 tile.
+ return 8;
+ case TextureFormat::A8R8G8B8:
+ return 4;
+ default:
+ UNIMPLEMENTED_MSG("Format not implemented");
+ break;
+ }
+}
+
+std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height) {
+ u8* data = Memory::GetPointer(address);
+ u32 bytes_per_pixel = BytesPerPixel(format);
+
+ static constexpr u32 DefaultBlockHeight = 16;
+
+ std::vector<u8> unswizzled_data(width * height * bytes_per_pixel);
+
+ switch (format) {
+ case TextureFormat::DXT1:
+ // In the DXT1 format, each 4x4 tile is swizzled instead of just individual pixel values.
+ CopySwizzledData(width / 4, height / 4, bytes_per_pixel, bytes_per_pixel, data,
+ unswizzled_data.data(), true, DefaultBlockHeight);
+ break;
+ case TextureFormat::A8R8G8B8:
+ CopySwizzledData(width, height, bytes_per_pixel, bytes_per_pixel, data,
+ unswizzled_data.data(), true, DefaultBlockHeight);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Format not implemented");
+ break;
+ }
+
+ return unswizzled_data;
+}
+
+std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width,
+ u32 height) {
+ std::vector<u8> rgba_data;
+
+ // TODO(Subv): Implement.
+ switch (format) {
+ case TextureFormat::DXT1:
+ case TextureFormat::A8R8G8B8:
+ // TODO(Subv): For the time being just forward the same data without any decoding.
+ rgba_data = texture_data;
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Format not implemented");
+ break;
+ }
+
+ return rgba_data;
+}
+
+} // namespace Texture
+} // namespace Tegra
diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h
new file mode 100644
index 000000000..0c21694ff
--- /dev/null
+++ b/src/video_core/textures/decoders.h
@@ -0,0 +1,26 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include "common/common_types.h"
+#include "video_core/textures/texture.h"
+
+namespace Tegra {
+namespace Texture {
+
+/**
+ * Unswizzles a swizzled texture without changing its format.
+ */
+std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height);
+
+/**
+ * Decodes an unswizzled texture into a A8R8G8B8 texture.
+ */
+std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width,
+ u32 height);
+
+} // namespace Texture
+} // namespace Tegra
diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h
new file mode 100644
index 000000000..07936f8a3
--- /dev/null
+++ b/src/video_core/textures/texture.h
@@ -0,0 +1,137 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "video_core/memory_manager.h"
+
+namespace Tegra {
+namespace Texture {
+
+enum class TextureFormat : u32 {
+ A8R8G8B8 = 8,
+ DXT1 = 0x24,
+};
+
+enum class TextureType : u32 {
+ Texture1D = 0,
+ Texture2D = 1,
+ Texture3D = 2,
+ TextureCubemap = 3,
+ Texture1DArray = 4,
+ Texture2DArray = 5,
+ Texture1DBuffer = 6,
+ Texture2DNoMipmap = 7,
+ TextureCubeArray = 8,
+};
+
+enum class TICHeaderVersion : u32 {
+ OneDBuffer = 0,
+ PitchColorKey = 1,
+ Pitch = 2,
+ BlockLinear = 3,
+ BlockLinearColorKey = 4,
+};
+
+union TextureHandle {
+ u32 raw;
+ BitField<0, 20, u32> tic_id;
+ BitField<20, 12, u32> tsc_id;
+};
+static_assert(sizeof(TextureHandle) == 4, "TextureHandle has wrong size");
+
+struct TICEntry {
+ union {
+ u32 raw;
+ BitField<0, 7, TextureFormat> format;
+ BitField<7, 3, u32> r_type;
+ BitField<10, 3, u32> g_type;
+ BitField<13, 3, u32> b_type;
+ BitField<16, 3, u32> a_type;
+ };
+ u32 address_low;
+ union {
+ BitField<0, 16, u32> address_high;
+ BitField<21, 3, TICHeaderVersion> header_version;
+ };
+ INSERT_PADDING_BYTES(4);
+ union {
+ BitField<0, 16, u32> width_minus_1;
+ BitField<23, 4, TextureType> texture_type;
+ };
+ u16 height_minus_1;
+ INSERT_PADDING_BYTES(10);
+
+ GPUVAddr Address() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | address_low);
+ }
+
+ u32 Width() const {
+ return width_minus_1 + 1;
+ }
+
+ u32 Height() const {
+ return height_minus_1 + 1;
+ }
+};
+static_assert(sizeof(TICEntry) == 0x20, "TICEntry has wrong size");
+
+enum class WrapMode : u32 {
+ Wrap = 0,
+ Mirror = 1,
+ ClampToEdge = 2,
+ Border = 3,
+ ClampOGL = 4,
+ MirrorOnceClampToEdge = 5,
+ MirrorOnceBorder = 6,
+ MirrorOnceClampOGL = 7,
+};
+
+enum class TextureFilter : u32 {
+ Nearest = 1,
+ Linear = 2,
+};
+
+enum class TextureMipmapFilter : u32 {
+ None = 1,
+ Nearest = 2,
+ Linear = 3,
+};
+
+struct TSCEntry {
+ union {
+ BitField<0, 3, WrapMode> wrap_u;
+ BitField<3, 3, WrapMode> wrap_v;
+ BitField<6, 3, WrapMode> wrap_p;
+ BitField<9, 1, u32> depth_compare_enabled;
+ BitField<10, 3, u32> depth_compare_func;
+ };
+ union {
+ BitField<0, 2, TextureFilter> mag_filter;
+ BitField<4, 2, TextureFilter> min_filter;
+ BitField<6, 2, TextureMipmapFilter> mip_filter;
+ };
+ INSERT_PADDING_BYTES(8);
+ u32 border_color_r;
+ u32 border_color_g;
+ u32 border_color_b;
+ u32 border_color_a;
+};
+static_assert(sizeof(TSCEntry) == 0x20, "TSCEntry has wrong size");
+
+struct FullTextureInfo {
+ u32 index;
+ TICEntry tic;
+ TSCEntry tsc;
+ bool enabled;
+};
+
+/// Returns the number of bytes per pixel of the input texture format.
+u32 BytesPerPixel(TextureFormat format);
+
+} // namespace Texture
+} // namespace Tegra
diff --git a/src/video_core/utils.h b/src/video_core/utils.h
index d8567f314..be0f7e22b 100644
--- a/src/video_core/utils.h
+++ b/src/video_core/utils.h
@@ -49,4 +49,116 @@ static inline u32 GetMortonOffset(u32 x, u32 y, u32 bytes_per_pixel) {
return (i + offset) * bytes_per_pixel;
}
-} // namespace
+static inline u32 MortonInterleave128(u32 x, u32 y) {
+ // 128x128 Z-Order coordinate from 2D coordinates
+ static constexpr u32 xlut[] = {
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042,
+ 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809,
+ 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000,
+ 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043,
+ 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a,
+ 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001,
+ 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048,
+ 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b,
+ 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002,
+ 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049,
+ 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840,
+ 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003,
+ 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a,
+ 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841,
+ 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008,
+ 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b,
+ 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842,
+ 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009,
+ 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800,
+ 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843,
+ 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a,
+ 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801,
+ 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848,
+ 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802,
+ 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849,
+ 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040,
+ 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803,
+ 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a,
+ 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041,
+ 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808,
+ 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b,
+ 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042,
+ 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809,
+ 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b,
+ };
+ static constexpr u32 ylut[] = {
+ 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090,
+ 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124,
+ 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200,
+ 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294,
+ 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330,
+ 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404,
+ 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0,
+ 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534,
+ 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610,
+ 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4,
+ 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780,
+ 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014,
+ 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0,
+ 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184,
+ 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220,
+ 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4,
+ 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390,
+ 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424,
+ 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500,
+ 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594,
+ 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630,
+ 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704,
+ 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0,
+ 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034,
+ 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110,
+ 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4,
+ 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280,
+ 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314,
+ 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0,
+ 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484,
+ 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520,
+ 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4,
+ 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690,
+ 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724,
+ 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4,
+ };
+ return xlut[x % 128] + ylut[y % 128];
+}
+
+static inline u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) {
+ // Calculates the offset of the position of the pixel in Morton order
+ // Framebuffer images are split into 128x128 tiles.
+
+ const unsigned int block_height = 128;
+ const unsigned int coarse_x = x & ~127;
+
+ u32 i = MortonInterleave128(x, y);
+
+ const unsigned int offset = coarse_x * block_height;
+
+ return (i + offset) * bytes_per_pixel;
+}
+
+static inline void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel,
+ u32 gl_bytes_per_pixel, u8* morton_data, u8* gl_data,
+ bool morton_to_gl) {
+ u8* data_ptrs[2];
+ for (unsigned y = 0; y < height; ++y) {
+ for (unsigned x = 0; x < width; ++x) {
+ const u32 coarse_y = y & ~127;
+ u32 morton_offset =
+ GetMortonOffset128(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel;
+ u32 gl_pixel_index = (x + (height - 1 - y) * width) * gl_bytes_per_pixel;
+
+ data_ptrs[morton_to_gl] = morton_data + morton_offset;
+ data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index];
+
+ memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
+ }
+ }
+}
+
+} // namespace VideoCore
diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp
index 106d62562..289140f31 100644
--- a/src/video_core/video_core.cpp
+++ b/src/video_core/video_core.cpp
@@ -26,7 +26,7 @@ bool Init(EmuWindow* emu_window) {
if (g_renderer->Init()) {
LOG_DEBUG(Render, "initialized OK");
} else {
- LOG_ERROR(Render, "initialization failed !");
+ LOG_CRITICAL(Render, "initialization failed !");
return false;
}
return true;
@@ -39,4 +39,4 @@ void Shutdown() {
LOG_DEBUG(Render, "shutdown OK");
}
-} // namespace
+} // namespace VideoCore
diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h
index 0b8785898..37da62436 100644
--- a/src/video_core/video_core.h
+++ b/src/video_core/video_core.h
@@ -15,6 +15,8 @@ class RendererBase;
namespace VideoCore {
+enum class Renderer { Software, OpenGL };
+
extern std::unique_ptr<RendererBase> g_renderer; ///< Renderer plugin
extern EmuWindow* g_emu_window; ///< Emu window
@@ -31,4 +33,4 @@ bool Init(EmuWindow* emu_window);
/// Shutdown the video core
void Shutdown();
-} // namespace
+} // namespace VideoCore
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index c52d5627a..5af3154d7 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -3,83 +3,91 @@ set(CMAKE_AUTORCC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
-set(SRCS
- about_dialog.cpp
- configuration/config.cpp
- configuration/configure_debug.cpp
- configuration/configure_dialog.cpp
- configuration/configure_general.cpp
- configuration/configure_graphics.cpp
- configuration/configure_input.cpp
- configuration/configure_system.cpp
- debugger/profiler.cpp
- debugger/registers.cpp
- debugger/wait_tree.cpp
- util/spinbox.cpp
- util/util.cpp
- bootmanager.cpp
- game_list.cpp
- hotkeys.cpp
- main.cpp
- ui_settings.cpp
- yuzu.rc
- Info.plist
- )
-
-set(HEADERS
- about_dialog.h
- configuration/config.h
- configuration/configure_debug.h
- configuration/configure_dialog.h
- configuration/configure_general.h
- configuration/configure_graphics.h
- configuration/configure_input.h
- configuration/configure_system.h
- debugger/profiler.h
- debugger/registers.h
- debugger/wait_tree.h
- util/spinbox.h
- util/util.h
- bootmanager.h
- game_list.h
- game_list_p.h
- hotkeys.h
- main.h
- ui_settings.h
- )
+add_executable(yuzu
+ Info.plist
+ about_dialog.cpp
+ about_dialog.h
+ bootmanager.cpp
+ bootmanager.h
+ configuration/config.cpp
+ configuration/config.h
+ configuration/configure_debug.cpp
+ configuration/configure_debug.h
+ configuration/configure_dialog.cpp
+ configuration/configure_dialog.h
+ configuration/configure_general.cpp
+ configuration/configure_general.h
+ configuration/configure_graphics.cpp
+ configuration/configure_graphics.h
+ configuration/configure_input.cpp
+ configuration/configure_input.h
+ configuration/configure_system.cpp
+ configuration/configure_system.h
+ debugger/graphics/graphics_breakpoint_observer.cpp
+ debugger/graphics/graphics_breakpoint_observer.h
+ debugger/graphics/graphics_breakpoints.cpp
+ debugger/graphics/graphics_breakpoints.h
+ debugger/graphics/graphics_breakpoints_p.h
+ debugger/graphics/graphics_surface.cpp
+ debugger/graphics/graphics_surface.h
+ debugger/profiler.cpp
+ debugger/profiler.h
+ debugger/registers.cpp
+ debugger/registers.h
+ debugger/wait_tree.cpp
+ debugger/wait_tree.h
+ game_list.cpp
+ game_list.h
+ game_list_p.h
+ hotkeys.cpp
+ hotkeys.h
+ main.cpp
+ main.h
+ ui_settings.cpp
+ ui_settings.h
+ util/spinbox.cpp
+ util/spinbox.h
+ util/util.cpp
+ util/util.h
+ yuzu.rc
+)
set(UIS
- aboutdialog.ui
- configuration/configure.ui
- configuration/configure_debug.ui
- configuration/configure_general.ui
- configuration/configure_graphics.ui
- configuration/configure_input.ui
- configuration/configure_system.ui
- debugger/registers.ui
- hotkeys.ui
- main.ui
- )
+ aboutdialog.ui
+ configuration/configure.ui
+ configuration/configure_debug.ui
+ configuration/configure_general.ui
+ configuration/configure_graphics.ui
+ configuration/configure_input.ui
+ configuration/configure_system.ui
+ debugger/registers.ui
+ hotkeys.ui
+ main.ui
+)
file(GLOB_RECURSE ICONS ${CMAKE_SOURCE_DIR}/dist/icons/*)
file(GLOB_RECURSE THEMES ${CMAKE_SOURCE_DIR}/dist/qt_themes/*)
-create_directory_groups(${SRCS} ${HEADERS} ${UIS})
+qt5_wrap_ui(UI_HDRS ${UIS})
-if (Qt5_FOUND)
- qt5_wrap_ui(UI_HDRS ${UIS})
-else()
- qt4_wrap_ui(UI_HDRS ${UIS})
-endif()
+target_sources(yuzu
+ PRIVATE
+ ${ICONS}
+ ${THEMES}
+ ${UI_HDRS}
+ ${UIS}
+)
if (APPLE)
set(MACOSX_ICON "../../dist/yuzu.icns")
set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
- add_executable(yuzu MACOSX_BUNDLE ${SRCS} ${HEADERS} ${UI_HDRS} ${MACOSX_ICON} ${ICONS})
+ target_sources(yuzu PRIVATE ${MACOSX_ICON})
+ set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE TRUE)
set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
-else()
- add_executable(yuzu ${SRCS} ${HEADERS} ${UI_HDRS} ${ICONS})
endif()
+
+create_target_directory_groups(yuzu)
+
target_link_libraries(yuzu PRIVATE common core input_common video_core)
target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets)
target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
diff --git a/src/yuzu/about_dialog.cpp b/src/yuzu/about_dialog.cpp
index da3429822..d6647eeea 100644
--- a/src/yuzu/about_dialog.cpp
+++ b/src/yuzu/about_dialog.cpp
@@ -2,12 +2,14 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <QIcon>
#include "common/scm_rev.h"
#include "ui_aboutdialog.h"
#include "yuzu/about_dialog.h"
AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDialog) {
ui->setupUi(this);
+ ui->labelLogo->setPixmap(QIcon::fromTheme("yuzu").pixmap(200));
ui->labelBuildInfo->setText(ui->labelBuildInfo->text().arg(
Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
}
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 843ac6ad7..469988d63 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -1,12 +1,8 @@
#include <QApplication>
#include <QHBoxLayout>
#include <QKeyEvent>
-
-#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
-// Required for screen DPI information
#include <QScreen>
#include <QWindow>
-#endif
#include "common/microprofile.h"
#include "common/scm_rev.h"
@@ -19,8 +15,7 @@
#include "input_common/motion_emu.h"
#include "yuzu/bootmanager.h"
-EmuThread::EmuThread(GRenderWindow* render_window)
- : exec_step(false), running(false), stop_run(false), render_window(render_window) {}
+EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {}
void EmuThread::run() {
render_window->MakeCurrent();
@@ -120,15 +115,13 @@ GRenderWindow::~GRenderWindow() {
void GRenderWindow::moveContext() {
DoneCurrent();
-// We need to move GL context to the swapping thread in Qt5
-#if QT_VERSION > QT_VERSION_CHECK(5, 0, 0)
+
// If the thread started running, move the GL Context to the new thread. Otherwise, move it
// back.
auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr)
? emu_thread
: qApp->thread();
child->context()->moveToThread(thread);
-#endif
}
void GRenderWindow::SwapBuffers() {
@@ -191,12 +184,8 @@ QByteArray GRenderWindow::saveGeometry() {
}
qreal GRenderWindow::windowPixelRatio() {
-#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
// windowHandle() might not be accessible until the window is displayed to screen.
return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f;
-#else
- return 1.0f;
-#endif
}
void GRenderWindow::closeEvent(QCloseEvent* event) {
@@ -299,9 +288,7 @@ void GRenderWindow::OnEmulationStopping() {
void GRenderWindow::showEvent(QShowEvent* event) {
QWidget::showEvent(event);
-#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
// windowHandle() is not initialized until the Window is shown, so we connect it here.
- connect(this->windowHandle(), SIGNAL(screenChanged(QScreen*)), this,
- SLOT(OnFramebufferSizeChanged()), Qt::UniqueConnection);
-#endif
+ connect(windowHandle(), &QWindow::screenChanged, this, &GRenderWindow::OnFramebufferSizeChanged,
+ Qt::UniqueConnection);
}
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 6974edcbb..130bc613b 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -58,7 +58,7 @@ public:
* @return True if the emulation thread is running, otherwise false
* @note This function is thread-safe
*/
- bool IsRunning() {
+ bool IsRunning() const {
return running;
}
@@ -68,12 +68,12 @@ public:
void RequestStop() {
stop_run = true;
SetRunning(false);
- };
+ }
private:
- bool exec_step;
- bool running;
- std::atomic<bool> stop_run;
+ bool exec_step = false;
+ bool running = false;
+ std::atomic<bool> stop_run{false};
std::mutex running_mutex;
std::condition_variable running_cv;
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 6a40f035c..8843f2078 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -26,10 +26,18 @@ const std::array<int, Settings::NativeButton::NumButtons> Config::default_button
const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{
{
- Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, Qt::Key_E,
+ 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,
+ Qt::Key_I,
+ Qt::Key_K,
+ Qt::Key_J,
+ Qt::Key_L,
+ Qt::Key_R,
},
}};
@@ -69,8 +77,7 @@ void Config::ReadValues() {
qt_config->endGroup();
qt_config->beginGroup("Core");
- Settings::values.cpu_core =
- static_cast<Settings::CpuCore>(qt_config->value("cpu_core", 0).toInt());
+ Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool();
qt_config->endGroup();
qt_config->beginGroup("Renderer");
@@ -86,6 +93,10 @@ void Config::ReadValues() {
Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool();
qt_config->endGroup();
+ qt_config->beginGroup("System");
+ Settings::values.use_docked_mode = qt_config->value("use_docked_mode", true).toBool();
+ qt_config->endGroup();
+
qt_config->beginGroup("Miscellaneous");
Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString();
qt_config->endGroup();
@@ -96,6 +107,8 @@ void Config::ReadValues() {
qt_config->endGroup();
qt_config->beginGroup("UI");
+ UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
+
qt_config->beginGroup("UILayout");
UISettings::values.geometry = qt_config->value("geometry").toByteArray();
UISettings::values.state = qt_config->value("state").toByteArray();
@@ -163,7 +176,7 @@ void Config::SaveValues() {
qt_config->endGroup();
qt_config->beginGroup("Core");
- qt_config->setValue("cpu_core", static_cast<int>(Settings::values.cpu_core));
+ qt_config->setValue("use_cpu_jit", Settings::values.use_cpu_jit);
qt_config->endGroup();
qt_config->beginGroup("Renderer");
@@ -180,6 +193,10 @@ void Config::SaveValues() {
qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd);
qt_config->endGroup();
+ qt_config->beginGroup("System");
+ qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode);
+ qt_config->endGroup();
+
qt_config->beginGroup("Miscellaneous");
qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter));
qt_config->endGroup();
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index babd583a2..c5303851c 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>740</width>
+ <width>461</width>
<height>500</height>
</rect>
</property>
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 92fd6ab02..2d73fc5aa 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -13,9 +13,14 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
ui->setupUi(this);
+ for (auto theme : UISettings::themes) {
+ ui->theme_combobox->addItem(theme.first, theme.second);
+ }
+
this->setConfiguration();
- ui->cpu_core_combobox->setEnabled(!Core::System::GetInstance().IsPoweredOn());
+ ui->use_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn());
+ ui->use_docked_mode->setEnabled(!Core::System::GetInstance().IsPoweredOn());
}
ConfigureGeneral::~ConfigureGeneral() {}
@@ -23,13 +28,18 @@ ConfigureGeneral::~ConfigureGeneral() {}
void ConfigureGeneral::setConfiguration() {
ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan);
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
- ui->cpu_core_combobox->setCurrentIndex(static_cast<int>(Settings::values.cpu_core));
+ ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
+ ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit);
+ ui->use_docked_mode->setChecked(Settings::values.use_docked_mode);
}
void ConfigureGeneral::applyConfiguration() {
UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
- Settings::values.cpu_core =
- static_cast<Settings::CpuCore>(ui->cpu_core_combobox->currentIndex());
+ UISettings::values.theme =
+ ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
+
+ Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked();
+ Settings::values.use_docked_mode = ui->use_docked_mode->isChecked();
Settings::Apply();
}
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index 573c4cb0e..1775c4d40 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -13,17 +13,17 @@
<property name="windowTitle">
<string>Form</string>
</property>
- <layout class="QHBoxLayout" name="horizontalLayout">
+ <layout class="QHBoxLayout" name="HorizontalLayout">
<item>
- <layout class="QVBoxLayout" name="verticalLayout">
+ <layout class="QVBoxLayout" name="VerticalLayout">
<item>
- <widget class="QGroupBox" name="groupBox">
+ <widget class="QGroupBox" name="GeneralGroupBox">
<property name="title">
<string>General</string>
</property>
- <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <layout class="QHBoxLayout" name="GeneralHorizontalLayout">
<item>
- <layout class="QVBoxLayout" name="verticalLayout_2">
+ <layout class="QVBoxLayout" name="GeneralVerticalLayout">
<item>
<widget class="QCheckBox" name="toggle_deepscan">
<property name="text">
@@ -44,40 +44,80 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox_2">
- <property name="title">
- <string>CPU Core</string>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout_7">
- <item>
- <layout class="QVBoxLayout" name="verticalLayout_5">
- <item>
- <widget class="QComboBox" name="cpu_core_combobox">
- <item>
- <property name="text">
- <string>Unicorn</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Dynarmic</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </item>
+ <widget class="QGroupBox" name="PerformanceGroupBox">
+ <property name="title">
+ <string>Performance</string>
+ </property>
+ <layout class="QHBoxLayout" name="PerformanceHorizontalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="PerformanceVerticalLayout">
+ <item>
+ <widget class="QCheckBox" name="use_cpu_jit">
+ <property name="text">
+ <string>Enable CPU JIT</string>
+ </property>
+ </widget>
+ </item>
</layout>
- </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="EmulationGroupBox">
+ <property name="title">
+ <string>Emulation</string>
+ </property>
+ <layout class="QHBoxLayout" name="EmulationHorizontalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="EmulationVerticalLayout">
+ <item>
+ <widget class="QCheckBox" name="use_docked_mode">
+ <property name="text">
+ <string>Enable docked mode</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="theme_group_box">
+ <property name="title">
+ <string>Theme</string>
+ </property>
+ <layout class="QHBoxLayout" name="theme_qhbox_layout">
+ <item>
+ <layout class="QVBoxLayout" name="theme_qvbox_layout">
+ <item>
+ <layout class="QHBoxLayout" name="theme_qhbox_layout_2">
+ <item>
+ <widget class="QLabel" name="theme_label">
+ <property name="text">
+ <string>Theme:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="theme_combobox"/>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox_3">
+ <widget class="QGroupBox" name="HotKeysGroupBox">
<property name="title">
<string>Hotkeys</string>
</property>
- <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <layout class="QHBoxLayout" name="HotKeysHorizontalLayout">
<item>
- <layout class="QVBoxLayout" name="verticalLayout_4">
+ <layout class="QVBoxLayout" name="HotKeysVerticalLayout">
<item>
<widget class="GHotkeysDialog" name="widget" native="true"/>
</item>
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 10043e6e8..78559e2bb 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -14,7 +14,11 @@
const std::array<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM>
ConfigureInput::analog_sub_buttons{{
- "up", "down", "left", "right", "modifier",
+ "up",
+ "down",
+ "left",
+ "right",
+ "modifier",
}};
static QString getKeyName(int key_code) {
@@ -36,7 +40,8 @@ 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") {
analog_param = {
- {"engine", "analog_from_button"}, {"modifier_scale", "0.5"},
+ {"engine", "analog_from_button"},
+ {"modifier_scale", "0.5"},
};
}
analog_param.Set(button_name, input_param.Serialize());
@@ -107,11 +112,17 @@ ConfigureInput::ConfigureInput(QWidget* parent)
analog_map_buttons = {{
{
- ui->buttonLStickUp, ui->buttonLStickDown, ui->buttonLStickLeft, ui->buttonLStickRight,
+ ui->buttonLStickUp,
+ ui->buttonLStickDown,
+ ui->buttonLStickLeft,
+ ui->buttonLStickRight,
ui->buttonLStickMod,
},
{
- ui->buttonRStickUp, ui->buttonRStickDown, ui->buttonRStickLeft, ui->buttonRStickRight,
+ ui->buttonRStickUp,
+ ui->buttonRStickDown,
+ ui->buttonRStickLeft,
+ ui->buttonRStickRight,
ui->buttonRStickMod,
},
}};
diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui
index c162ca02c..377b79c77 100644
--- a/src/yuzu/configuration/configure_input.ui
+++ b/src/yuzu/configuration/configure_input.ui
@@ -15,9 +15,9 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
- <layout class="QGridLayout" name="gridLayout_7">
+ <layout class="QGridLayout" name="buttons">
<item row="3" column="1">
- <widget class="QGroupBox" name="faceButtons_6">
+ <widget class="QGroupBox" name="misc">
<property name="title">
<string>Misc.</string>
</property>
@@ -29,9 +29,9 @@
</property>
<layout class="QGridLayout" name="gridLayout_6">
<item row="0" column="0">
- <layout class="QVBoxLayout" name="verticalLayout_25">
+ <layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout">
<item>
- <widget class="QLabel" name="label_29">
+ <widget class="QLabel" name="labelPlus">
<property name="text">
<string>Plus:</string>
</property>
@@ -47,9 +47,9 @@
</layout>
</item>
<item row="0" column="1">
- <layout class="QVBoxLayout" name="verticalLayout_26">
+ <layout class="QVBoxLayout" name="buttonMiscMinusVerticalLayout">
<item>
- <widget class="QLabel" name="label_30">
+ <widget class="QLabel" name="labelMinus">
<property name="text">
<string>Minus:</string>
</property>
@@ -65,9 +65,9 @@
</layout>
</item>
<item row="1" column="0">
- <layout class="QVBoxLayout" name="verticalLayout_27">
+ <layout class="QVBoxLayout" name="buttonMiscHomeVerticalLayout">
<item>
- <widget class="QLabel" name="label_31">
+ <widget class="QLabel" name="labelHome">
<property name="text">
<string>Home:</string>
</property>
@@ -83,9 +83,9 @@
</layout>
</item>
<item row="1" column="1">
- <layout class="QVBoxLayout" name="verticalLayout_28">
+ <layout class="QVBoxLayout" name="buttonMiscScrCapVerticalLayout">
<item>
- <widget class="QLabel" name="label_11">
+ <widget class="QLabel" name="labelScrCap">
<property name="text">
<string>Screen
Capture:</string>
@@ -130,9 +130,9 @@ Capture:</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
- <layout class="QVBoxLayout" name="verticalLayout">
+ <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout">
<item>
- <widget class="QLabel" name="label">
+ <widget class="QLabel" name="labelA">
<property name="text">
<string>A:</string>
</property>
@@ -148,9 +148,9 @@ Capture:</string>
</layout>
</item>
<item row="0" column="1">
- <layout class="QVBoxLayout" name="verticalLayout_2">
+ <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout">
<item>
- <widget class="QLabel" name="label_2">
+ <widget class="QLabel" name="labelB">
<property name="text">
<string>B:</string>
</property>
@@ -166,9 +166,9 @@ Capture:</string>
</layout>
</item>
<item row="1" column="0">
- <layout class="QVBoxLayout" name="verticalLayout_3">
+ <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout">
<item>
- <widget class="QLabel" name="label_3">
+ <widget class="QLabel" name="labelX">
<property name="text">
<string>X:</string>
</property>
@@ -184,9 +184,9 @@ Capture:</string>
</layout>
</item>
<item row="1" column="1">
- <layout class="QVBoxLayout" name="verticalLayout_4">
+ <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout">
<item>
- <widget class="QLabel" name="label_4">
+ <widget class="QLabel" name="labelY">
<property name="text">
<string>Y:</string>
</property>
@@ -205,7 +205,7 @@ Capture:</string>
</widget>
</item>
<item row="0" column="1">
- <widget class="QGroupBox" name="faceButtons_2">
+ <widget class="QGroupBox" name="Dpad">
<property name="title">
<string>Directional Pad</string>
</property>
@@ -217,9 +217,9 @@ Capture:</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
- <layout class="QVBoxLayout" name="verticalLayout_12">
+ <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout">
<item>
- <widget class="QLabel" name="label_34">
+ <widget class="QLabel" name="labelDpadUp">
<property name="text">
<string>Up:</string>
</property>
@@ -235,9 +235,9 @@ Capture:</string>
</layout>
</item>
<item row="1" column="1">
- <layout class="QVBoxLayout" name="verticalLayout_9">
+ <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout">
<item>
- <widget class="QLabel" name="label_35">
+ <widget class="QLabel" name="labelDpadDown">
<property name="text">
<string>Down:</string>
</property>
@@ -253,9 +253,9 @@ Capture:</string>
</layout>
</item>
<item row="0" column="0">
- <layout class="QVBoxLayout" name="verticalLayout_10">
+ <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout">
<item>
- <widget class="QLabel" name="label_32">
+ <widget class="QLabel" name="labelDpadLeft">
<property name="text">
<string>Left:</string>
</property>
@@ -271,9 +271,9 @@ Capture:</string>
</layout>
</item>
<item row="0" column="1">
- <layout class="QVBoxLayout" name="verticalLayout_11">
+ <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout">
<item>
- <widget class="QLabel" name="label_33">
+ <widget class="QLabel" name="labelDpadRight">
<property name="text">
<string>Right:</string>
</property>
@@ -292,7 +292,7 @@ Capture:</string>
</widget>
</item>
<item row="3" column="0">
- <widget class="QGroupBox" name="faceButtons_3">
+ <widget class="QGroupBox" name="shoulderButtons">
<property name="title">
<string>Shoulder Buttons</string>
</property>
@@ -304,9 +304,9 @@ Capture:</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
- <layout class="QVBoxLayout" name="verticalLayout_13">
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsLVerticalLayout">
<item>
- <widget class="QLabel" name="label_17">
+ <widget class="QLabel" name="labelL">
<property name="text">
<string>L:</string>
</property>
@@ -322,9 +322,9 @@ Capture:</string>
</layout>
</item>
<item row="0" column="1">
- <layout class="QVBoxLayout" name="verticalLayout_14">
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsRVerticalLayout">
<item>
- <widget class="QLabel" name="label_19">
+ <widget class="QLabel" name="labelR">
<property name="text">
<string>R:</string>
</property>
@@ -340,9 +340,9 @@ Capture:</string>
</layout>
</item>
<item row="1" column="0">
- <layout class="QVBoxLayout" name="verticalLayout_15">
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsZLVerticalLayout">
<item>
- <widget class="QLabel" name="label_20">
+ <widget class="QLabel" name="labelZL">
<property name="text">
<string>ZL:</string>
</property>
@@ -358,9 +358,9 @@ Capture:</string>
</layout>
</item>
<item row="1" column="1">
- <layout class="QVBoxLayout" name="verticalLayout_16">
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsZRVerticalLayout">
<item>
- <widget class="QLabel" name="label_18">
+ <widget class="QLabel" name="labelZR">
<property name="text">
<string>ZR:</string>
</property>
@@ -376,9 +376,9 @@ Capture:</string>
</layout>
</item>
<item row="2" column="0">
- <layout class="QVBoxLayout" name="verticalLayout_8">
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsSLVerticalLayout">
<item>
- <widget class="QLabel" name="label_7">
+ <widget class="QLabel" name="labelSL">
<property name="text">
<string>SL:</string>
</property>
@@ -394,9 +394,9 @@ Capture:</string>
</layout>
</item>
<item row="2" column="1">
- <layout class="QVBoxLayout" name="verticalLayout_29">
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsSRVerticalLayout">
<item>
- <widget class="QLabel" name="label_8">
+ <widget class="QLabel" name="labelSR">
<property name="text">
<string>SR:</string>
</property>
@@ -415,7 +415,7 @@ Capture:</string>
</widget>
</item>
<item row="1" column="1">
- <widget class="QGroupBox" name="faceButtons_5">
+ <widget class="QGroupBox" name="RStick">
<property name="title">
<string>Right Stick</string>
</property>
@@ -430,9 +430,9 @@ Capture:</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="1" column="1">
- <layout class="QVBoxLayout" name="verticalLayout_24">
+ <layout class="QVBoxLayout" name="buttonRStickDownVerticalLayout">
<item>
- <widget class="QLabel" name="label_26">
+ <widget class="QLabel" name="labelRStickDown">
<property name="text">
<string>Down:</string>
</property>
@@ -448,9 +448,9 @@ Capture:</string>
</layout>
</item>
<item row="0" column="1">
- <layout class="QVBoxLayout" name="verticalLayout_22">
+ <layout class="QVBoxLayout" name="buttonRStickRightVerticalLayout">
<item>
- <widget class="QLabel" name="label_27">
+ <widget class="QLabel" name="labelRStickRight">
<property name="text">
<string>Right:</string>
</property>
@@ -473,9 +473,9 @@ Capture:</string>
</widget>
</item>
<item row="1" column="0">
- <layout class="QVBoxLayout" name="verticalLayout_21">
+ <layout class="QVBoxLayout" name="buttonRStickLeftVerticalLayout">
<item>
- <widget class="QLabel" name="label_25">
+ <widget class="QLabel" name="labelRStickLeft">
<property name="text">
<string>Left:</string>
</property>
@@ -491,9 +491,9 @@ Capture:</string>
</layout>
</item>
<item row="0" column="0">
- <layout class="QVBoxLayout" name="verticalLayout_25">
+ <layout class="QVBoxLayout" name="buttonRStickUpVerticalLayout">
<item>
- <widget class="QLabel" name="label_28">
+ <widget class="QLabel" name="labelRStickUp">
<property name="text">
<string>Up:</string>
</property>
@@ -509,9 +509,9 @@ Capture:</string>
</layout>
</item>
<item row="2" column="0">
- <layout class="QVBoxLayout" name="verticalLayout_6">
+ <layout class="QVBoxLayout" name="buttonRStickPressedVerticalLayout">
<item>
- <widget class="QLabel" name="label_5">
+ <widget class="QLabel" name="labelRStickPressed">
<property name="text">
<string>Pressed:</string>
</property>
@@ -527,9 +527,9 @@ Capture:</string>
</layout>
</item>
<item row="2" column="1">
- <layout class="QVBoxLayout" name="verticalLayout_32">
+ <layout class="QVBoxLayout" name="buttonRStickModVerticalLayout">
<item>
- <widget class="QLabel" name="label_10">
+ <widget class="QLabel" name="labelRStickMod">
<property name="text">
<string>Modifier:</string>
</property>
@@ -548,7 +548,7 @@ Capture:</string>
</widget>
</item>
<item row="1" column="0">
- <widget class="QGroupBox" name="faceButtons_4">
+ <widget class="QGroupBox" name="LStick">
<property name="title">
<string>Left Stick</string>
</property>
@@ -560,9 +560,9 @@ Capture:</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="1" column="1">
- <layout class="QVBoxLayout" name="verticalLayout_20">
+ <layout class="QVBoxLayout" name="buttonLStickDownVerticalLayout">
<item>
- <widget class="QLabel" name="label_22">
+ <widget class="QLabel" name="labelLStickDown">
<property name="text">
<string>Down:</string>
</property>
@@ -585,9 +585,9 @@ Capture:</string>
</widget>
</item>
<item row="0" column="1">
- <layout class="QVBoxLayout" name="verticalLayout_18">
+ <layout class="QVBoxLayout" name="buttonLStickRightVerticalLayout">
<item>
- <widget class="QLabel" name="label_23">
+ <widget class="QLabel" name="labelLStickRight">
<property name="text">
<string>Right:</string>
</property>
@@ -603,9 +603,9 @@ Capture:</string>
</layout>
</item>
<item row="0" column="0">
- <layout class="QVBoxLayout" name="verticalLayout_17">
+ <layout class="QVBoxLayout" name="buttonLStickLeftVerticalLayout">
<item>
- <widget class="QLabel" name="label_21">
+ <widget class="QLabel" name="labelLStickLeft">
<property name="text">
<string>Left:</string>
</property>
@@ -621,9 +621,9 @@ Capture:</string>
</layout>
</item>
<item row="1" column="0">
- <layout class="QVBoxLayout" name="verticalLayout_19">
+ <layout class="QVBoxLayout" name="buttonLStickUpVerticalLayout">
<item>
- <widget class="QLabel" name="label_24">
+ <widget class="QLabel" name="labelLStickUp">
<property name="text">
<string>Up:</string>
</property>
@@ -639,9 +639,9 @@ Capture:</string>
</layout>
</item>
<item row="3" column="0">
- <layout class="QVBoxLayout" name="verticalLayout_31">
+ <layout class="QVBoxLayout" name="buttonLStickModVerticalLayout">
<item>
- <widget class="QLabel" name="label_9">
+ <widget class="QLabel" name="labelLStickMod">
<property name="text">
<string>Modifier:</string>
</property>
@@ -657,9 +657,9 @@ Capture:</string>
</layout>
</item>
<item row="3" column="1">
- <layout class="QVBoxLayout" name="verticalLayout_7" stretch="0,0">
+ <layout class="QVBoxLayout" name="buttonLStickPressedVerticalLayout" stretch="0,0">
<item>
- <widget class="QLabel" name="label_6">
+ <widget class="QLabel" name="labelLStickPressed">
<property name="text">
<string>Pressed:</string>
</property>
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index d198e38ae..d09505a0f 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -9,7 +9,18 @@
#include "yuzu/ui_settings.h"
static const std::array<int, 12> days_in_month = {{
- 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
+ 31,
+ 29,
+ 31,
+ 30,
+ 31,
+ 30,
+ 31,
+ 31,
+ 30,
+ 31,
+ 30,
+ 31,
}};
ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) {
@@ -72,5 +83,6 @@ void ConfigureSystem::refreshConsoleID() {
if (reply == QMessageBox::No)
return;
u64 console_id{};
- ui->label_console_id->setText("Console ID: 0x" + QString::number(console_id, 16).toUpper());
+ ui->label_console_id->setText(
+ tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));
}
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp
new file mode 100644
index 000000000..d6d61a739
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp
@@ -0,0 +1,27 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QMetaType>
+#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h"
+
+BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Tegra::DebugContext> debug_context,
+ const QString& title, QWidget* parent)
+ : QDockWidget(title, parent), BreakPointObserver(debug_context) {
+ qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event");
+
+ connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed()));
+
+ // NOTE: This signal is emitted from a non-GUI thread, but connect() takes
+ // care of delaying its handling to the GUI thread.
+ connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this,
+ SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection);
+}
+
+void BreakPointObserverDock::OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) {
+ emit BreakPointHit(event, data);
+}
+
+void BreakPointObserverDock::OnMaxwellResume() {
+ emit Resumed();
+}
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h
new file mode 100644
index 000000000..9d05493cf
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h
@@ -0,0 +1,33 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <QDockWidget>
+#include "video_core/debug_utils/debug_utils.h"
+
+/**
+ * Utility class which forwards calls to OnMaxwellBreakPointHit and OnMaxwellResume to public slots.
+ * This is because the Maxwell breakpoint callbacks are called from a non-GUI thread, while
+ * the widget usually wants to perform reactions in the GUI thread.
+ */
+class BreakPointObserverDock : public QDockWidget,
+ protected Tegra::DebugContext::BreakPointObserver {
+ Q_OBJECT
+
+public:
+ BreakPointObserverDock(std::shared_ptr<Tegra::DebugContext> debug_context, const QString& title,
+ QWidget* parent = nullptr);
+
+ void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override;
+ void OnMaxwellResume() override;
+
+private slots:
+ virtual void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) = 0;
+ virtual void OnResumed() = 0;
+
+signals:
+ void Resumed();
+ void BreakPointHit(Tegra::DebugContext::Event event, void* data);
+};
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.cpp b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp
new file mode 100644
index 000000000..f98cc8152
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp
@@ -0,0 +1,212 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QLabel>
+#include <QMetaType>
+#include <QPushButton>
+#include <QTreeView>
+#include <QVBoxLayout>
+#include "common/assert.h"
+#include "yuzu/debugger/graphics/graphics_breakpoints.h"
+#include "yuzu/debugger/graphics/graphics_breakpoints_p.h"
+
+BreakPointModel::BreakPointModel(std::shared_ptr<Tegra::DebugContext> debug_context,
+ QObject* parent)
+ : QAbstractListModel(parent), context_weak(debug_context),
+ at_breakpoint(debug_context->at_breakpoint),
+ active_breakpoint(debug_context->active_breakpoint) {}
+
+int BreakPointModel::columnCount(const QModelIndex& parent) const {
+ return 1;
+}
+
+int BreakPointModel::rowCount(const QModelIndex& parent) const {
+ return static_cast<int>(Tegra::DebugContext::Event::NumEvents);
+}
+
+QVariant BreakPointModel::data(const QModelIndex& index, int role) const {
+ const auto event = static_cast<Tegra::DebugContext::Event>(index.row());
+
+ switch (role) {
+ case Qt::DisplayRole: {
+ if (index.column() == 0) {
+ static const std::map<Tegra::DebugContext::Event, QString> map = {
+ {Tegra::DebugContext::Event::MaxwellCommandLoaded, tr("Maxwell command loaded")},
+ {Tegra::DebugContext::Event::MaxwellCommandProcessed,
+ tr("Maxwell command processed")},
+ {Tegra::DebugContext::Event::IncomingPrimitiveBatch,
+ tr("Incoming primitive batch")},
+ {Tegra::DebugContext::Event::FinishedPrimitiveBatch,
+ tr("Finished primitive batch")},
+ };
+
+ DEBUG_ASSERT(map.size() == static_cast<size_t>(Tegra::DebugContext::Event::NumEvents));
+ return (map.find(event) != map.end()) ? map.at(event) : QString();
+ }
+
+ break;
+ }
+
+ case Qt::CheckStateRole: {
+ if (index.column() == 0)
+ return data(index, Role_IsEnabled).toBool() ? Qt::Checked : Qt::Unchecked;
+ break;
+ }
+
+ case Qt::BackgroundRole: {
+ if (at_breakpoint && index.row() == static_cast<int>(active_breakpoint)) {
+ return QBrush(QColor(0xE0, 0xE0, 0x10));
+ }
+ break;
+ }
+
+ case Role_IsEnabled: {
+ auto context = context_weak.lock();
+ return context && context->breakpoints[(int)event].enabled;
+ }
+
+ default:
+ break;
+ }
+ return QVariant();
+}
+
+Qt::ItemFlags BreakPointModel::flags(const QModelIndex& index) const {
+ if (!index.isValid())
+ return 0;
+
+ Qt::ItemFlags flags = Qt::ItemIsEnabled;
+ if (index.column() == 0)
+ flags |= Qt::ItemIsUserCheckable;
+ return flags;
+}
+
+bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, int role) {
+ const auto event = static_cast<Tegra::DebugContext::Event>(index.row());
+
+ switch (role) {
+ case Qt::CheckStateRole: {
+ if (index.column() != 0)
+ return false;
+
+ auto context = context_weak.lock();
+ if (!context)
+ return false;
+
+ context->breakpoints[(int)event].enabled = value == Qt::Checked;
+ QModelIndex changed_index = createIndex(index.row(), 0);
+ emit dataChanged(changed_index, changed_index);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void BreakPointModel::OnBreakPointHit(Tegra::DebugContext::Event event) {
+ auto context = context_weak.lock();
+ if (!context)
+ return;
+
+ active_breakpoint = context->active_breakpoint;
+ at_breakpoint = context->at_breakpoint;
+ emit dataChanged(createIndex(static_cast<int>(event), 0),
+ createIndex(static_cast<int>(event), 0));
+}
+
+void BreakPointModel::OnResumed() {
+ auto context = context_weak.lock();
+ if (!context)
+ return;
+
+ at_breakpoint = context->at_breakpoint;
+ emit dataChanged(createIndex(static_cast<int>(active_breakpoint), 0),
+ createIndex(static_cast<int>(active_breakpoint), 0));
+ active_breakpoint = context->active_breakpoint;
+}
+
+GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(
+ std::shared_ptr<Tegra::DebugContext> debug_context, QWidget* parent)
+ : QDockWidget(tr("Maxwell Breakpoints"), parent), Tegra::DebugContext::BreakPointObserver(
+ debug_context) {
+ setObjectName("TegraBreakPointsWidget");
+
+ status_text = new QLabel(tr("Emulation running"));
+ resume_button = new QPushButton(tr("Resume"));
+ resume_button->setEnabled(false);
+
+ breakpoint_model = new BreakPointModel(debug_context, this);
+ breakpoint_list = new QTreeView;
+ breakpoint_list->setRootIsDecorated(false);
+ breakpoint_list->setHeaderHidden(true);
+ breakpoint_list->setModel(breakpoint_model);
+
+ qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event");
+
+ connect(breakpoint_list, SIGNAL(doubleClicked(const QModelIndex&)), this,
+ SLOT(OnItemDoubleClicked(const QModelIndex&)));
+
+ connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested()));
+
+ connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this,
+ SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection);
+ connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed()));
+
+ connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), breakpoint_model,
+ SLOT(OnBreakPointHit(Tegra::DebugContext::Event)), Qt::BlockingQueuedConnection);
+ connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed()));
+
+ connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&, const QModelIndex&)),
+ breakpoint_model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)));
+
+ QWidget* main_widget = new QWidget;
+ auto main_layout = new QVBoxLayout;
+ {
+ auto sub_layout = new QHBoxLayout;
+ sub_layout->addWidget(status_text);
+ sub_layout->addWidget(resume_button);
+ main_layout->addLayout(sub_layout);
+ }
+ main_layout->addWidget(breakpoint_list);
+ main_widget->setLayout(main_layout);
+
+ setWidget(main_widget);
+}
+
+void GraphicsBreakPointsWidget::OnMaxwellBreakPointHit(Event event, void* data) {
+ // Process in GUI thread
+ emit BreakPointHit(event, data);
+}
+
+void GraphicsBreakPointsWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) {
+ status_text->setText(tr("Emulation halted at breakpoint"));
+ resume_button->setEnabled(true);
+}
+
+void GraphicsBreakPointsWidget::OnMaxwellResume() {
+ // Process in GUI thread
+ emit Resumed();
+}
+
+void GraphicsBreakPointsWidget::OnResumed() {
+ status_text->setText(tr("Emulation running"));
+ resume_button->setEnabled(false);
+}
+
+void GraphicsBreakPointsWidget::OnResumeRequested() {
+ if (auto context = context_weak.lock())
+ context->Resume();
+}
+
+void GraphicsBreakPointsWidget::OnItemDoubleClicked(const QModelIndex& index) {
+ if (!index.isValid())
+ return;
+
+ QModelIndex check_index = breakpoint_list->model()->index(index.row(), 0);
+ QVariant enabled = breakpoint_list->model()->data(check_index, Qt::CheckStateRole);
+ QVariant new_state = Qt::Unchecked;
+ if (enabled == Qt::Unchecked)
+ new_state = Qt::Checked;
+ breakpoint_list->model()->setData(check_index, new_state, Qt::CheckStateRole);
+}
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.h b/src/yuzu/debugger/graphics/graphics_breakpoints.h
new file mode 100644
index 000000000..ae0ede2e8
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_breakpoints.h
@@ -0,0 +1,46 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QDockWidget>
+#include "video_core/debug_utils/debug_utils.h"
+
+class QLabel;
+class QPushButton;
+class QTreeView;
+
+class BreakPointModel;
+
+class GraphicsBreakPointsWidget : public QDockWidget, Tegra::DebugContext::BreakPointObserver {
+ Q_OBJECT
+
+ using Event = Tegra::DebugContext::Event;
+
+public:
+ explicit GraphicsBreakPointsWidget(std::shared_ptr<Tegra::DebugContext> debug_context,
+ QWidget* parent = nullptr);
+
+ void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override;
+ void OnMaxwellResume() override;
+
+public slots:
+ void OnBreakPointHit(Tegra::DebugContext::Event event, void* data);
+ void OnItemDoubleClicked(const QModelIndex&);
+ void OnResumeRequested();
+ void OnResumed();
+
+signals:
+ void Resumed();
+ void BreakPointHit(Tegra::DebugContext::Event event, void* data);
+ void BreakPointsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
+
+private:
+ QLabel* status_text;
+ QPushButton* resume_button;
+
+ BreakPointModel* breakpoint_model;
+ QTreeView* breakpoint_list;
+};
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints_p.h b/src/yuzu/debugger/graphics/graphics_breakpoints_p.h
new file mode 100644
index 000000000..35a6876ae
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_breakpoints_p.h
@@ -0,0 +1,36 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QAbstractListModel>
+#include "video_core/debug_utils/debug_utils.h"
+
+class BreakPointModel : public QAbstractListModel {
+ Q_OBJECT
+
+public:
+ enum {
+ Role_IsEnabled = Qt::UserRole,
+ };
+
+ BreakPointModel(std::shared_ptr<Tegra::DebugContext> context, QObject* parent);
+
+ int columnCount(const QModelIndex& parent = QModelIndex()) const override;
+ int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
+ Qt::ItemFlags flags(const QModelIndex& index) const override;
+
+ bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
+
+public slots:
+ void OnBreakPointHit(Tegra::DebugContext::Event event);
+ void OnResumed();
+
+private:
+ std::weak_ptr<Tegra::DebugContext> context_weak;
+ bool at_breakpoint;
+ Tegra::DebugContext::Event active_breakpoint;
+};
diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp
new file mode 100644
index 000000000..1e4844b57
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_surface.cpp
@@ -0,0 +1,451 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QBoxLayout>
+#include <QComboBox>
+#include <QDebug>
+#include <QFileDialog>
+#include <QLabel>
+#include <QMouseEvent>
+#include <QPushButton>
+#include <QScrollArea>
+#include <QSpinBox>
+#include "core/core.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/gpu.h"
+#include "video_core/textures/decoders.h"
+#include "video_core/textures/texture.h"
+#include "video_core/utils.h"
+#include "yuzu/debugger/graphics/graphics_surface.h"
+#include "yuzu/util/spinbox.h"
+
+static Tegra::Texture::TextureFormat ConvertToTextureFormat(
+ Tegra::RenderTargetFormat render_target_format) {
+ switch (render_target_format) {
+ case Tegra::RenderTargetFormat::RGBA8_UNORM:
+ return Tegra::Texture::TextureFormat::A8R8G8B8;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented RT format");
+ }
+}
+
+SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_)
+ : QLabel(parent), surface_widget(surface_widget_) {}
+SurfacePicture::~SurfacePicture() {}
+
+void SurfacePicture::mousePressEvent(QMouseEvent* event) {
+ // Only do something while the left mouse button is held down
+ if (!(event->buttons() & Qt::LeftButton))
+ return;
+
+ if (pixmap() == nullptr)
+ return;
+
+ if (surface_widget)
+ surface_widget->Pick(event->x() * pixmap()->width() / width(),
+ event->y() * pixmap()->height() / height());
+}
+
+void SurfacePicture::mouseMoveEvent(QMouseEvent* event) {
+ // We also want to handle the event if the user moves the mouse while holding down the LMB
+ mousePressEvent(event);
+}
+
+GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context,
+ QWidget* parent)
+ : BreakPointObserverDock(debug_context, tr("Maxwell Surface Viewer"), parent),
+ surface_source(Source::RenderTarget0) {
+ setObjectName("MaxwellSurface");
+
+ surface_source_list = new QComboBox;
+ surface_source_list->addItem(tr("Render Target 0"));
+ surface_source_list->addItem(tr("Render Target 1"));
+ surface_source_list->addItem(tr("Render Target 2"));
+ surface_source_list->addItem(tr("Render Target 3"));
+ surface_source_list->addItem(tr("Render Target 4"));
+ surface_source_list->addItem(tr("Render Target 5"));
+ surface_source_list->addItem(tr("Render Target 6"));
+ surface_source_list->addItem(tr("Render Target 7"));
+ surface_source_list->addItem(tr("Z Buffer"));
+ surface_source_list->addItem(tr("Custom"));
+ surface_source_list->setCurrentIndex(static_cast<int>(surface_source));
+
+ surface_address_control = new CSpinBox;
+ surface_address_control->SetBase(16);
+ surface_address_control->SetRange(0, 0x7FFFFFFFFFFFFFFF);
+ surface_address_control->SetPrefix("0x");
+
+ unsigned max_dimension = 16384; // TODO: Find actual maximum
+
+ surface_width_control = new QSpinBox;
+ surface_width_control->setRange(0, max_dimension);
+
+ surface_height_control = new QSpinBox;
+ surface_height_control->setRange(0, max_dimension);
+
+ surface_picker_x_control = new QSpinBox;
+ surface_picker_x_control->setRange(0, max_dimension - 1);
+
+ surface_picker_y_control = new QSpinBox;
+ surface_picker_y_control->setRange(0, max_dimension - 1);
+
+ surface_format_control = new QComboBox;
+
+ // Color formats sorted by Maxwell texture format index
+ surface_format_control->addItem(tr("None"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("A8R8G8B8"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("Unknown"));
+ surface_format_control->addItem(tr("DXT1"));
+ surface_format_control->addItem(tr("DXT23"));
+ surface_format_control->addItem(tr("DXT45"));
+ surface_format_control->addItem(tr("DXN1"));
+ surface_format_control->addItem(tr("DXN2"));
+
+ surface_info_label = new QLabel();
+ surface_info_label->setWordWrap(true);
+
+ surface_picture_label = new SurfacePicture(0, this);
+ surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop);
+ surface_picture_label->setScaledContents(false);
+
+ auto scroll_area = new QScrollArea();
+ scroll_area->setBackgroundRole(QPalette::Dark);
+ scroll_area->setWidgetResizable(false);
+ scroll_area->setWidget(surface_picture_label);
+
+ save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save"));
+
+ // Connections
+ connect(this, SIGNAL(Update()), this, SLOT(OnUpdate()));
+ connect(surface_source_list, SIGNAL(currentIndexChanged(int)), this,
+ SLOT(OnSurfaceSourceChanged(int)));
+ connect(surface_address_control, SIGNAL(ValueChanged(qint64)), this,
+ SLOT(OnSurfaceAddressChanged(qint64)));
+ connect(surface_width_control, SIGNAL(valueChanged(int)), this,
+ SLOT(OnSurfaceWidthChanged(int)));
+ connect(surface_height_control, SIGNAL(valueChanged(int)), this,
+ SLOT(OnSurfaceHeightChanged(int)));
+ connect(surface_format_control, SIGNAL(currentIndexChanged(int)), this,
+ SLOT(OnSurfaceFormatChanged(int)));
+ connect(surface_picker_x_control, SIGNAL(valueChanged(int)), this,
+ SLOT(OnSurfacePickerXChanged(int)));
+ connect(surface_picker_y_control, SIGNAL(valueChanged(int)), this,
+ SLOT(OnSurfacePickerYChanged(int)));
+ connect(save_surface, SIGNAL(clicked()), this, SLOT(SaveSurface()));
+
+ auto main_widget = new QWidget;
+ auto main_layout = new QVBoxLayout;
+ {
+ auto sub_layout = new QHBoxLayout;
+ sub_layout->addWidget(new QLabel(tr("Source:")));
+ sub_layout->addWidget(surface_source_list);
+ main_layout->addLayout(sub_layout);
+ }
+ {
+ auto sub_layout = new QHBoxLayout;
+ sub_layout->addWidget(new QLabel(tr("GPU Address:")));
+ sub_layout->addWidget(surface_address_control);
+ main_layout->addLayout(sub_layout);
+ }
+ {
+ auto sub_layout = new QHBoxLayout;
+ sub_layout->addWidget(new QLabel(tr("Width:")));
+ sub_layout->addWidget(surface_width_control);
+ main_layout->addLayout(sub_layout);
+ }
+ {
+ auto sub_layout = new QHBoxLayout;
+ sub_layout->addWidget(new QLabel(tr("Height:")));
+ sub_layout->addWidget(surface_height_control);
+ main_layout->addLayout(sub_layout);
+ }
+ {
+ auto sub_layout = new QHBoxLayout;
+ sub_layout->addWidget(new QLabel(tr("Format:")));
+ sub_layout->addWidget(surface_format_control);
+ main_layout->addLayout(sub_layout);
+ }
+ main_layout->addWidget(scroll_area);
+
+ auto info_layout = new QHBoxLayout;
+ {
+ auto xy_layout = new QVBoxLayout;
+ {
+ {
+ auto sub_layout = new QHBoxLayout;
+ sub_layout->addWidget(new QLabel(tr("X:")));
+ sub_layout->addWidget(surface_picker_x_control);
+ xy_layout->addLayout(sub_layout);
+ }
+ {
+ auto sub_layout = new QHBoxLayout;
+ sub_layout->addWidget(new QLabel(tr("Y:")));
+ sub_layout->addWidget(surface_picker_y_control);
+ xy_layout->addLayout(sub_layout);
+ }
+ }
+ info_layout->addLayout(xy_layout);
+ surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
+ info_layout->addWidget(surface_info_label);
+ }
+ main_layout->addLayout(info_layout);
+
+ main_layout->addWidget(save_surface);
+ main_widget->setLayout(main_layout);
+ setWidget(main_widget);
+
+ // Load current data - TODO: Make sure this works when emulation is not running
+ if (debug_context && debug_context->at_breakpoint) {
+ emit Update();
+ widget()->setEnabled(debug_context->at_breakpoint);
+ } else {
+ widget()->setEnabled(false);
+ }
+}
+
+void GraphicsSurfaceWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) {
+ emit Update();
+ widget()->setEnabled(true);
+}
+
+void GraphicsSurfaceWidget::OnResumed() {
+ widget()->setEnabled(false);
+}
+
+void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) {
+ surface_source = static_cast<Source>(new_value);
+ emit Update();
+}
+
+void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) {
+ if (surface_address != new_value) {
+ surface_address = static_cast<Tegra::GPUVAddr>(new_value);
+
+ surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
+ emit Update();
+ }
+}
+
+void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value) {
+ if (surface_width != static_cast<unsigned>(new_value)) {
+ surface_width = static_cast<unsigned>(new_value);
+
+ surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
+ emit Update();
+ }
+}
+
+void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value) {
+ if (surface_height != static_cast<unsigned>(new_value)) {
+ surface_height = static_cast<unsigned>(new_value);
+
+ surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
+ emit Update();
+ }
+}
+
+void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value) {
+ if (surface_format != static_cast<Tegra::Texture::TextureFormat>(new_value)) {
+ surface_format = static_cast<Tegra::Texture::TextureFormat>(new_value);
+
+ surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
+ emit Update();
+ }
+}
+
+void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value) {
+ if (surface_picker_x != new_value) {
+ surface_picker_x = new_value;
+ Pick(surface_picker_x, surface_picker_y);
+ }
+}
+
+void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value) {
+ if (surface_picker_y != new_value) {
+ surface_picker_y = new_value;
+ Pick(surface_picker_x, surface_picker_y);
+ }
+}
+
+void GraphicsSurfaceWidget::Pick(int x, int y) {
+ surface_picker_x_control->setValue(x);
+ surface_picker_y_control->setValue(y);
+
+ if (x < 0 || x >= static_cast<int>(surface_width) || y < 0 ||
+ y >= static_cast<int>(surface_height)) {
+ surface_info_label->setText(tr("Pixel out of bounds"));
+ surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
+ return;
+ }
+
+ surface_info_label->setText(QString("Raw: <Unimplemented>\n(%1)").arg("<Unimplemented>"));
+ surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
+}
+
+void GraphicsSurfaceWidget::OnUpdate() {
+ auto& gpu = Core::System::GetInstance().GPU();
+
+ QPixmap pixmap;
+
+ switch (surface_source) {
+ case Source::RenderTarget0:
+ case Source::RenderTarget1:
+ case Source::RenderTarget2:
+ case Source::RenderTarget3:
+ case Source::RenderTarget4:
+ case Source::RenderTarget5:
+ case Source::RenderTarget6:
+ case Source::RenderTarget7: {
+ // TODO: Store a reference to the registers in the debug context instead of accessing them
+ // directly...
+
+ auto& registers = gpu.Get3DEngine().regs;
+ auto& rt = registers.rt[static_cast<size_t>(surface_source) -
+ static_cast<size_t>(Source::RenderTarget0)];
+
+ surface_address = rt.Address();
+ surface_width = rt.width;
+ surface_height = rt.height;
+ if (rt.format != Tegra::RenderTargetFormat::NONE) {
+ surface_format = ConvertToTextureFormat(rt.format);
+ }
+
+ break;
+ }
+
+ case Source::Custom: {
+ // Keep user-specified values
+ break;
+ }
+
+ default:
+ qDebug() << "Unknown surface source " << static_cast<int>(surface_source);
+ break;
+ }
+
+ surface_address_control->SetValue(surface_address);
+ surface_width_control->setValue(surface_width);
+ surface_height_control->setValue(surface_height);
+ surface_format_control->setCurrentIndex(static_cast<int>(surface_format));
+
+ if (surface_address == 0) {
+ surface_picture_label->hide();
+ surface_info_label->setText(tr("(invalid surface address)"));
+ surface_info_label->setAlignment(Qt::AlignCenter);
+ surface_picker_x_control->setEnabled(false);
+ surface_picker_y_control->setEnabled(false);
+ save_surface->setEnabled(false);
+ return;
+ }
+
+ // TODO: Implement a good way to visualize alpha components!
+
+ QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32);
+ VAddr address = gpu.memory_manager->PhysicalToVirtualAddress(surface_address);
+
+ auto unswizzled_data =
+ Tegra::Texture::UnswizzleTexture(address, surface_format, surface_width, surface_height);
+
+ auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format,
+ surface_width, surface_height);
+
+ surface_picture_label->show();
+
+ for (unsigned int y = 0; y < surface_height; ++y) {
+ for (unsigned int x = 0; x < surface_width; ++x) {
+ Math::Vec4<u8> color;
+ color[0] = texture_data[x + y * surface_width + 0];
+ color[1] = texture_data[x + y * surface_width + 1];
+ color[2] = texture_data[x + y * surface_width + 2];
+ color[3] = texture_data[x + y * surface_width + 3];
+ decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
+ }
+ }
+
+ pixmap = QPixmap::fromImage(decoded_image);
+ surface_picture_label->setPixmap(pixmap);
+ surface_picture_label->resize(pixmap.size());
+
+ // Update the info with pixel data
+ surface_picker_x_control->setEnabled(true);
+ surface_picker_y_control->setEnabled(true);
+ Pick(surface_picker_x, surface_picker_y);
+
+ // Enable saving the converted pixmap to file
+ save_surface->setEnabled(true);
+}
+
+void GraphicsSurfaceWidget::SaveSurface() {
+ QString png_filter = tr("Portable Network Graphic (*.png)");
+ QString bin_filter = tr("Binary data (*.bin)");
+
+ QString selectedFilter;
+ QString filename = QFileDialog::getSaveFileName(
+ this, tr("Save Surface"),
+ QString("texture-0x%1.png").arg(QString::number(surface_address, 16)),
+ QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter);
+
+ if (filename.isEmpty()) {
+ // If the user canceled the dialog, don't save anything.
+ return;
+ }
+
+ if (selectedFilter == png_filter) {
+ const QPixmap* pixmap = surface_picture_label->pixmap();
+ ASSERT_MSG(pixmap != nullptr, "No pixmap set");
+
+ QFile file(filename);
+ file.open(QIODevice::WriteOnly);
+ if (pixmap)
+ pixmap->save(&file, "PNG");
+ } else if (selectedFilter == bin_filter) {
+ auto& gpu = Core::System::GetInstance().GPU();
+ VAddr address = gpu.memory_manager->PhysicalToVirtualAddress(surface_address);
+
+ const u8* buffer = Memory::GetPointer(address);
+ ASSERT_MSG(buffer != nullptr, "Memory not accessible");
+
+ QFile file(filename);
+ file.open(QIODevice::WriteOnly);
+ int size = surface_width * surface_height * Tegra::Texture::BytesPerPixel(surface_format);
+ QByteArray data(reinterpret_cast<const char*>(buffer), size);
+ file.write(data);
+ } else {
+ UNREACHABLE_MSG("Unhandled filter selected");
+ }
+}
diff --git a/src/yuzu/debugger/graphics/graphics_surface.h b/src/yuzu/debugger/graphics/graphics_surface.h
new file mode 100644
index 000000000..6a344bdfc
--- /dev/null
+++ b/src/yuzu/debugger/graphics/graphics_surface.h
@@ -0,0 +1,97 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <QLabel>
+#include <QPushButton>
+#include "video_core/memory_manager.h"
+#include "video_core/textures/texture.h"
+#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h"
+
+class QComboBox;
+class QSpinBox;
+class CSpinBox;
+
+class GraphicsSurfaceWidget;
+
+class SurfacePicture : public QLabel {
+ Q_OBJECT
+
+public:
+ explicit SurfacePicture(QWidget* parent = nullptr,
+ GraphicsSurfaceWidget* surface_widget = nullptr);
+ ~SurfacePicture();
+
+protected slots:
+ virtual void mouseMoveEvent(QMouseEvent* event);
+ virtual void mousePressEvent(QMouseEvent* event);
+
+private:
+ GraphicsSurfaceWidget* surface_widget;
+};
+
+class GraphicsSurfaceWidget : public BreakPointObserverDock {
+ Q_OBJECT
+
+ using Event = Tegra::DebugContext::Event;
+
+ enum class Source {
+ RenderTarget0 = 0,
+ RenderTarget1 = 1,
+ RenderTarget2 = 2,
+ RenderTarget3 = 3,
+ RenderTarget4 = 4,
+ RenderTarget5 = 5,
+ RenderTarget6 = 6,
+ RenderTarget7 = 7,
+ ZBuffer = 8,
+ Custom = 9,
+ };
+
+public:
+ explicit GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context,
+ QWidget* parent = nullptr);
+ void Pick(int x, int y);
+
+public slots:
+ void OnSurfaceSourceChanged(int new_value);
+ void OnSurfaceAddressChanged(qint64 new_value);
+ void OnSurfaceWidthChanged(int new_value);
+ void OnSurfaceHeightChanged(int new_value);
+ void OnSurfaceFormatChanged(int new_value);
+ void OnSurfacePickerXChanged(int new_value);
+ void OnSurfacePickerYChanged(int new_value);
+ void OnUpdate();
+
+private slots:
+ void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) override;
+ void OnResumed() override;
+
+ void SaveSurface();
+
+signals:
+ void Update();
+
+private:
+ QComboBox* surface_source_list;
+ CSpinBox* surface_address_control;
+ QSpinBox* surface_width_control;
+ QSpinBox* surface_height_control;
+ QComboBox* surface_format_control;
+
+ SurfacePicture* surface_picture_label;
+ QSpinBox* surface_picker_x_control;
+ QSpinBox* surface_picker_y_control;
+ QLabel* surface_info_label;
+ QPushButton* save_surface;
+
+ Source surface_source;
+ Tegra::GPUVAddr surface_address;
+ unsigned surface_width;
+ unsigned surface_height;
+ Tegra::Texture::TextureFormat surface_format;
+ int surface_picker_x = 0;
+ int surface_picker_y = 0;
+};
diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp
index cc9babe84..8b30e0a85 100644
--- a/src/yuzu/debugger/profiler.cpp
+++ b/src/yuzu/debugger/profiler.cpp
@@ -74,7 +74,7 @@ QAction* MicroProfileDialog::toggleViewAction() {
toggle_view_action = new QAction(windowTitle(), this);
toggle_view_action->setCheckable(true);
toggle_view_action->setChecked(isVisible());
- connect(toggle_view_action, SIGNAL(toggled(bool)), SLOT(setVisible(bool)));
+ connect(toggle_view_action, &QAction::toggled, this, &MicroProfileDialog::setVisible);
}
return toggle_view_action;
@@ -107,7 +107,8 @@ MicroProfileWidget::MicroProfileWidget(QWidget* parent) : QWidget(parent) {
MicroProfileSetDisplayMode(1); // Timers screen
MicroProfileInitUI();
- connect(&update_timer, SIGNAL(timeout()), SLOT(update()));
+ connect(&update_timer, &QTimer::timeout, this,
+ static_cast<void (MicroProfileWidget::*)()>(&MicroProfileWidget::update));
}
void MicroProfileWidget::paintEvent(QPaintEvent* ev) {
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index 70471923e..cae2864e5 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -5,6 +5,7 @@
#include "yuzu/debugger/wait_tree.h"
#include "yuzu/util/util.h"
+#include "core/core.h"
#include "core/hle/kernel/condition_variable.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/mutex.h"
@@ -50,7 +51,7 @@ std::size_t WaitTreeItem::Row() const {
}
std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() {
- const auto& threads = Kernel::GetThreadList();
+ const auto& threads = Core::System::GetInstance().Scheduler().GetThreadList();
std::vector<std::unique_ptr<WaitTreeThread>> item_list;
item_list.reserve(threads.size());
for (std::size_t i = 0; i < threads.size(); ++i) {
@@ -149,8 +150,8 @@ QString WaitTreeThread::GetText() const {
case THREADSTATUS_READY:
status = tr("ready");
break;
- case THREADSTATUS_WAIT_ARB:
- status = tr("waiting for address 0x%1").arg(thread.wait_address, 8, 16, QLatin1Char('0'));
+ case THREADSTATUS_WAIT_HLE_EVENT:
+ status = tr("waiting for HLE return");
break;
case THREADSTATUS_WAIT_SLEEP:
status = tr("sleeping");
@@ -168,7 +169,7 @@ QString WaitTreeThread::GetText() const {
}
QString pc_info = tr(" PC = 0x%1 LR = 0x%2")
.arg(thread.context.pc, 8, 16, QLatin1Char('0'))
- .arg(thread.context.cpu_registers[31], 8, 16, QLatin1Char('0'));
+ .arg(thread.context.cpu_registers[30], 8, 16, QLatin1Char('0'));
return WaitTreeWaitObject::GetText() + pc_info + " (" + status + ") ";
}
@@ -179,7 +180,7 @@ QColor WaitTreeThread::GetColor() const {
return QColor(Qt::GlobalColor::darkGreen);
case THREADSTATUS_READY:
return QColor(Qt::GlobalColor::darkBlue);
- case THREADSTATUS_WAIT_ARB:
+ case THREADSTATUS_WAIT_HLE_EVENT:
return QColor(Qt::GlobalColor::darkRed);
case THREADSTATUS_WAIT_SLEEP:
return QColor(Qt::GlobalColor::darkYellow);
diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h
index 4034e909b..e538174eb 100644
--- a/src/yuzu/debugger/wait_tree.h
+++ b/src/yuzu/debugger/wait_tree.h
@@ -20,7 +20,7 @@ class Mutex;
class ConditionVariable;
class Thread;
class Timer;
-}
+} // namespace Kernel
class WaitTreeThread;
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 679c89828..76ced4de4 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -114,8 +114,7 @@ GameList::SearchField::SearchField(GameList* parent) : QWidget{parent} {
edit_filter->setPlaceholderText(tr("Enter pattern to filter"));
edit_filter->installEventFilter(keyReleaseEater);
edit_filter->setClearButtonEnabled(true);
- connect(edit_filter, SIGNAL(textChanged(const QString&)), parent,
- SLOT(onTextChanged(const QString&)));
+ connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::onTextChanged);
label_filter_result = new QLabel;
button_filter_close = new QToolButton(this);
button_filter_close->setText("X");
@@ -124,7 +123,7 @@ GameList::SearchField::SearchField(GameList* parent) : QWidget{parent} {
"#000000; font-weight: bold; background: #F0F0F0; }"
"QToolButton:hover{ border: none; padding: 0px; color: "
"#EEEEEE; font-weight: bold; background: #E81123}");
- connect(button_filter_close, SIGNAL(clicked()), parent, SLOT(onFilterCloseClicked()));
+ connect(button_filter_close, &QToolButton::clicked, parent, &GameList::onFilterCloseClicked);
layout_filter->setSpacing(10);
layout_filter->addWidget(label_filter);
layout_filter->addWidget(edit_filter);
@@ -137,8 +136,8 @@ GameList::SearchField::SearchField(GameList* parent) : QWidget{parent} {
* Checks if all words separated by spaces are contained in another string
* This offers a word order insensitive search function
*
- * @param String that gets checked if it contains all words of the userinput string
- * @param String containing all words getting checked
+ * @param haystack String that gets checked if it contains all words of the userinput string
+ * @param userinput String containing all words getting checked
* @return true if the haystack contains all words of userinput
*/
bool GameList::containsAllWords(QString haystack, QString userinput) {
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 4823a1296..7aff597b7 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -49,7 +49,7 @@ public:
QString edit_filter_text_old;
protected:
- bool eventFilter(QObject* obj, QEvent* event);
+ bool eventFilter(QObject* obj, QEvent* event) override;
};
QHBoxLayout* layout_filter = nullptr;
QTreeView* tree_view = nullptr;
diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp
index 42f026464..61acb38ee 100644
--- a/src/yuzu/hotkeys.cpp
+++ b/src/yuzu/hotkeys.cpp
@@ -5,6 +5,7 @@
#include <map>
#include <QKeySequence>
#include <QShortcut>
+#include <QTreeWidgetItem>
#include <QtGlobal>
#include "yuzu/hotkeys.h"
#include "yuzu/ui_settings.h"
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 31f2825ee..265502c2a 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -18,7 +18,6 @@
#include "common/logging/log.h"
#include "common/logging/text_formatter.h"
#include "common/microprofile.h"
-#include "common/platform.h"
#include "common/scm_rev.h"
#include "common/scope_exit.h"
#include "common/string_util.h"
@@ -26,10 +25,13 @@
#include "core/gdbstub/gdbstub.h"
#include "core/loader/loader.h"
#include "core/settings.h"
+#include "video_core/debug_utils/debug_utils.h"
#include "yuzu/about_dialog.h"
#include "yuzu/bootmanager.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_dialog.h"
+#include "yuzu/debugger/graphics/graphics_breakpoints.h"
+#include "yuzu/debugger/graphics/graphics_surface.h"
#include "yuzu/debugger/profiler.h"
#include "yuzu/debugger/registers.h"
#include "yuzu/debugger/wait_tree.h"
@@ -69,10 +71,16 @@ static void ShowCalloutMessage(const QString& message, CalloutFlag flag) {
void GMainWindow::ShowCallouts() {}
GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
+
+ debug_context = Tegra::DebugContext::Construct();
+
setAcceptDrops(true);
ui.setupUi(this);
statusBar()->hide();
+ default_theme_paths = QIcon::themeSearchPaths();
+ UpdateUITheme();
+
InitializeWidgets();
InitializeDebugWidgets();
InitializeRecentFileMenuActions();
@@ -161,6 +169,16 @@ void GMainWindow::InitializeDebugWidgets() {
connect(this, &GMainWindow::EmulationStopping, registersWidget,
&RegistersWidget::OnEmulationStopping);
+ graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(debug_context, this);
+ addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget);
+ graphicsBreakpointsWidget->hide();
+ debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction());
+
+ graphicsSurfaceWidget = new GraphicsSurfaceWidget(debug_context, this);
+ addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceWidget);
+ graphicsSurfaceWidget->hide();
+ debug_menu->addAction(graphicsSurfaceWidget->toggleViewAction());
+
waitTreeWidget = new WaitTreeWidget(this);
addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget);
waitTreeWidget->hide();
@@ -175,7 +193,7 @@ void GMainWindow::InitializeRecentFileMenuActions() {
for (int i = 0; i < max_recent_files_item; ++i) {
actions_recent_files[i] = new QAction(this);
actions_recent_files[i]->setVisible(false);
- connect(actions_recent_files[i], SIGNAL(triggered()), this, SLOT(OnMenuRecentFile()));
+ connect(actions_recent_files[i], &QAction::triggered, this, &GMainWindow::OnMenuRecentFile);
ui.menu_recent_files->addAction(actions_recent_files[i]);
}
@@ -187,13 +205,14 @@ void GMainWindow::InitializeHotkeys() {
RegisterHotkey("Main Window", "Load File", QKeySequence::Open);
RegisterHotkey("Main Window", "Start Emulation");
RegisterHotkey("Main Window", "Fullscreen", QKeySequence::FullScreen);
- RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence::Cancel, Qt::ApplicationShortcut);
+ RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence(Qt::Key_Escape),
+ Qt::ApplicationShortcut);
LoadHotkeys();
- connect(GetHotkey("Main Window", "Load File", this), SIGNAL(activated()), this,
- SLOT(OnMenuLoadFile()));
- connect(GetHotkey("Main Window", "Start Emulation", this), SIGNAL(activated()), this,
- SLOT(OnStartGame()));
+ connect(GetHotkey("Main Window", "Load File", this), &QShortcut::activated, this,
+ &GMainWindow::OnMenuLoadFile);
+ connect(GetHotkey("Main Window", "Start Emulation", this), &QShortcut::activated, this,
+ &GMainWindow::OnStartGame);
connect(GetHotkey("Main Window", "Fullscreen", render_window), &QShortcut::activated,
ui.action_Fullscreen, &QAction::trigger);
connect(GetHotkey("Main Window", "Fullscreen", render_window), &QShortcut::activatedAmbiguously,
@@ -245,13 +264,14 @@ void GMainWindow::RestoreUIState() {
}
void GMainWindow::ConnectWidgetEvents() {
- connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString)));
- connect(game_list, SIGNAL(OpenSaveFolderRequested(u64)), this,
- SLOT(OnGameListOpenSaveFolder(u64)));
+ connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);
+ connect(game_list, &GameList::OpenSaveFolderRequested, this,
+ &GMainWindow::OnGameListOpenSaveFolder);
- connect(this, SIGNAL(EmulationStarting(EmuThread*)), render_window,
- SLOT(OnEmulationStarting(EmuThread*)));
- connect(this, SIGNAL(EmulationStopping()), render_window, SLOT(OnEmulationStopping()));
+ connect(this, &GMainWindow::EmulationStarting, render_window,
+ &GRenderWindow::OnEmulationStarting);
+ connect(this, &GMainWindow::EmulationStopping, render_window,
+ &GRenderWindow::OnEmulationStopping);
connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar);
}
@@ -323,6 +343,8 @@ bool GMainWindow::LoadROM(const QString& filename) {
Core::System& system{Core::System::GetInstance()};
+ system.SetGPUDebugContext(debug_context);
+
const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())};
Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt");
@@ -398,17 +420,17 @@ void GMainWindow::BootGame(const QString& filename) {
render_window->moveContext();
emu_thread->start();
- connect(render_window, SIGNAL(Closed()), this, SLOT(OnStopGame()));
+ connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
// BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views
// before the CPU continues
- connect(emu_thread.get(), SIGNAL(DebugModeEntered()), registersWidget,
- SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection);
- connect(emu_thread.get(), SIGNAL(DebugModeEntered()), waitTreeWidget,
- SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection);
- connect(emu_thread.get(), SIGNAL(DebugModeLeft()), registersWidget, SLOT(OnDebugModeLeft()),
- Qt::BlockingQueuedConnection);
- connect(emu_thread.get(), SIGNAL(DebugModeLeft()), waitTreeWidget, SLOT(OnDebugModeLeft()),
- Qt::BlockingQueuedConnection);
+ connect(emu_thread.get(), &EmuThread::DebugModeEntered, registersWidget,
+ &RegistersWidget::OnDebugModeEntered, Qt::BlockingQueuedConnection);
+ connect(emu_thread.get(), &EmuThread::DebugModeEntered, waitTreeWidget,
+ &WaitTreeWidget::OnDebugModeEntered, Qt::BlockingQueuedConnection);
+ connect(emu_thread.get(), &EmuThread::DebugModeLeft, registersWidget,
+ &RegistersWidget::OnDebugModeLeft, Qt::BlockingQueuedConnection);
+ connect(emu_thread.get(), &EmuThread::DebugModeLeft, waitTreeWidget,
+ &WaitTreeWidget::OnDebugModeLeft, Qt::BlockingQueuedConnection);
// Update the GUI
registersWidget->OnDebugModeEntered();
@@ -437,7 +459,7 @@ void GMainWindow::ShutdownGame() {
emu_thread = nullptr;
// The emulation is stopped, so closing the window or not does not matter anymore
- disconnect(render_window, SIGNAL(Closed()), this, SLOT(OnStopGame()));
+ disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
// Update the GUI
ui.action_Start->setEnabled(false);
@@ -548,8 +570,7 @@ void GMainWindow::OnStartGame() {
emu_thread->SetRunning(true);
qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus");
qRegisterMetaType<std::string>("std::string");
- connect(emu_thread.get(), SIGNAL(ErrorThrown(Core::System::ResultStatus, std::string)), this,
- SLOT(OnCoreError(Core::System::ResultStatus, std::string)));
+ connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError);
ui.action_Start->setEnabled(false);
ui.action_Start->setText(tr("Continue"));
@@ -635,6 +656,7 @@ void GMainWindow::OnConfigure() {
auto result = configureDialog.exec();
if (result == QDialog::Accepted) {
configureDialog.applyConfiguration();
+ UpdateUITheme();
config->Save();
}
}
@@ -815,6 +837,32 @@ void GMainWindow::filterBarSetChecked(bool state) {
emit(OnToggleFilterBar());
}
+void GMainWindow::UpdateUITheme() {
+ QStringList theme_paths(default_theme_paths);
+ if (UISettings::values.theme != UISettings::themes[0].second &&
+ !UISettings::values.theme.isEmpty()) {
+ QString theme_uri(":" + UISettings::values.theme + "/style.qss");
+ QFile f(theme_uri);
+ if (!f.exists()) {
+ LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found");
+ } else {
+ f.open(QFile::ReadOnly | QFile::Text);
+ QTextStream ts(&f);
+ qApp->setStyleSheet(ts.readAll());
+ GMainWindow::setStyleSheet(ts.readAll());
+ }
+ theme_paths.append(QStringList{":/icons/default", ":/icons/" + UISettings::values.theme});
+ QIcon::setThemeName(":/icons/" + UISettings::values.theme);
+ } else {
+ qApp->setStyleSheet("");
+ GMainWindow::setStyleSheet("");
+ theme_paths.append(QStringList{":/icons/default"});
+ QIcon::setThemeName(":/icons/default");
+ }
+ QIcon::setThemeSearchPaths(theme_paths);
+ emit UpdateThemedIcons();
+}
+
#ifdef main
#undef main
#endif
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 4a0d912bb..20ff65314 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -2,8 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#ifndef _CITRA_QT_MAIN_HXX_
-#define _CITRA_QT_MAIN_HXX_
+#pragma once
#include <memory>
#include <QMainWindow>
@@ -15,17 +14,18 @@ class Config;
class EmuThread;
class GameList;
class GImageInfo;
-class GPUCommandStreamWidget;
-class GPUCommandListWidget;
class GraphicsBreakPointsWidget;
-class GraphicsTracingWidget;
-class GraphicsVertexShaderWidget;
+class GraphicsSurfaceWidget;
class GRenderWindow;
class MicroProfileDialog;
class ProfilerWidget;
class RegistersWidget;
class WaitTreeWidget;
+namespace Tegra {
+class DebugContext;
+}
+
class GMainWindow : public QMainWindow {
Q_OBJECT
@@ -64,6 +64,9 @@ signals:
*/
void EmulationStopping();
+ // Signal that tells widgets to update icons to use the current theme
+ void UpdateThemedIcons();
+
private:
void InitializeWidgets();
void InitializeDebugWidgets();
@@ -138,6 +141,8 @@ private:
Ui::MainWindow ui;
+ std::shared_ptr<Tegra::DebugContext> debug_context;
+
GRenderWindow* render_window;
GameList* game_list;
@@ -150,7 +155,7 @@ private:
std::unique_ptr<Config> config;
- // Whether emulation is currently running in Citra.
+ // Whether emulation is currently running in yuzu.
bool emulation_running = false;
std::unique_ptr<EmuThread> emu_thread;
@@ -158,14 +163,17 @@ private:
ProfilerWidget* profilerWidget;
MicroProfileDialog* microProfileDialog;
RegistersWidget* registersWidget;
+ GraphicsBreakPointsWidget* graphicsBreakpointsWidget;
+ GraphicsSurfaceWidget* graphicsSurfaceWidget;
WaitTreeWidget* waitTreeWidget;
QAction* actions_recent_files[max_recent_files_item];
+ // stores default icon theme search paths for the platform
+ QStringList default_theme_paths;
+
protected:
void dropEvent(QDropEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override;
void dragMoveEvent(QDragMoveEvent* event) override;
};
-
-#endif // _CITRA_QT_MAIN_HXX_
diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h
index d093da641..8e215a002 100644
--- a/src/yuzu/ui_settings.h
+++ b/src/yuzu/ui_settings.h
@@ -15,6 +15,10 @@ namespace UISettings {
using ContextualShortcut = std::pair<QString, int>;
using Shortcut = std::pair<QString, ContextualShortcut>;
+static const std::array<std::pair<QString, QString>, 2> themes = {
+ {std::make_pair(QString("Default"), QString("default")),
+ std::make_pair(QString("Dark"), QString("qdarkstyle"))}};
+
struct Values {
QByteArray geometry;
QByteArray state;
@@ -50,4 +54,4 @@ struct Values {
};
extern Values values;
-}
+} // namespace UISettings
diff --git a/src/yuzu/util/spinbox.cpp b/src/yuzu/util/spinbox.cpp
index 92753ec1c..14ef1e884 100644
--- a/src/yuzu/util/spinbox.cpp
+++ b/src/yuzu/util/spinbox.cpp
@@ -39,7 +39,7 @@ CSpinBox::CSpinBox(QWidget* parent)
// TODO: Might be nice to not immediately call the slot.
// Think of an address that is being replaced by a different one, in which case a lot
// invalid intermediate addresses would be read from during editing.
- connect(lineEdit(), SIGNAL(textEdited(QString)), this, SLOT(OnEditingFinished()));
+ connect(lineEdit(), &QLineEdit::textEdited, this, &CSpinBox::OnEditingFinished);
UpdateText();
}
diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt
index 433e210b0..297dab653 100644
--- a/src/yuzu_cmd/CMakeLists.txt
+++ b/src/yuzu_cmd/CMakeLists.txt
@@ -1,21 +1,18 @@
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
-set(SRCS
- emu_window/emu_window_sdl2.cpp
- config.cpp
- yuzu.cpp
- yuzu.rc
- )
-set(HEADERS
- emu_window/emu_window_sdl2.h
- config.h
- default_ini.h
- resource.h
- )
+add_executable(yuzu-cmd
+ config.cpp
+ config.h
+ default_ini.h
+ emu_window/emu_window_sdl2.cpp
+ emu_window/emu_window_sdl2.h
+ resource.h
+ yuzu.cpp
+ yuzu.rc
+)
-create_directory_groups(${SRCS} ${HEADERS})
+create_target_directory_groups(yuzu-cmd)
-add_executable(yuzu-cmd ${SRCS} ${HEADERS})
target_link_libraries(yuzu-cmd PRIVATE common core input_common)
target_link_libraries(yuzu-cmd PRIVATE inih glad)
if (MSVC)
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index e5be72213..8b479bc6d 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -49,10 +49,18 @@ static const std::array<int, Settings::NativeButton::NumButtons> default_buttons
static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs{{
{
- SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_D,
+ SDL_SCANCODE_UP,
+ SDL_SCANCODE_DOWN,
+ SDL_SCANCODE_LEFT,
+ SDL_SCANCODE_RIGHT,
+ SDL_SCANCODE_D,
},
{
- SDL_SCANCODE_I, SDL_SCANCODE_K, SDL_SCANCODE_J, SDL_SCANCODE_L, SDL_SCANCODE_D,
+ SDL_SCANCODE_I,
+ SDL_SCANCODE_K,
+ SDL_SCANCODE_J,
+ SDL_SCANCODE_L,
+ SDL_SCANCODE_D,
},
}};
@@ -82,8 +90,7 @@ void Config::ReadValues() {
sdl2_config->Get("Controls", "touch_device", "engine:emu_window");
// Core
- Settings::values.cpu_core =
- static_cast<Settings::CpuCore>(sdl2_config->GetInteger("Core", "cpu_core", 0));
+ Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
// Renderer
Settings::values.resolution_factor =
@@ -99,6 +106,9 @@ void Config::ReadValues() {
Settings::values.use_virtual_sd =
sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
+ // System
+ Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", true);
+
// Miscellaneous
Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace");
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index 5a2f539b1..cf157bf27 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -76,9 +76,9 @@ motion_device=
touch_device=
[Core]
-# Which CPU core to use for CPU emulation
-# 0 (default): Unicorn (slow), 1: Dynarmic (faster)
-cpu_core =
+# Whether to use the Just-In-Time (JIT) compiler for CPU emulation
+# 0: Interpreter (slow), 1 (default): JIT (fast)
+use_cpu_jit =
[Renderer]
# Whether to use software or hardware rendering.
@@ -124,8 +124,8 @@ custom_bottom_top =
custom_bottom_right =
custom_bottom_bottom =
-#Whether to toggle frame limiter on or off.
-# 0: Off , 1 (default): On
+# Whether to toggle frame limiter on or off.
+# 0: Off, 1 (default): On
toggle_framelimit =
# Swaps the prominent screen with the other screen.
@@ -154,9 +154,9 @@ output_device =
use_virtual_sd =
[System]
-# The system model that Citra will try to emulate
-# 0: Old 3DS (default), 1: New 3DS
-is_new_3ds =
+# Whether the system is docked
+# 1 (default): Yes, 0: No
+use_docked_mode =
# The system region that Citra will use during emulation
# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
diff --git a/src/yuzu_cmd/yuzu.rc b/src/yuzu_cmd/yuzu.rc
index 7cb8a14e1..7de8ef3d9 100644
--- a/src/yuzu_cmd/yuzu.rc
+++ b/src/yuzu_cmd/yuzu.rc
@@ -6,7 +6,7 @@
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
-CITRA_ICON ICON "../../dist/yuzu.ico"
+YUZU_ICON ICON "../../dist/yuzu.ico"
/////////////////////////////////////////////////////////////////////////////