summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/audio_core/CMakeLists.txt1
-rw-r--r--src/audio_core/audio_core.cpp18
-rw-r--r--src/audio_core/audio_core.h26
-rw-r--r--src/audio_core/audio_event.h4
-rw-r--r--src/audio_core/audio_in_manager.cpp2
-rw-r--r--src/audio_core/audio_in_manager.h5
-rw-r--r--src/audio_core/audio_manager.h2
-rw-r--r--src/audio_core/audio_out_manager.cpp2
-rw-r--r--src/audio_core/audio_render_manager.h6
-rw-r--r--src/audio_core/device/audio_buffer.h4
-rw-r--r--src/audio_core/device/audio_buffers.h17
-rw-r--r--src/audio_core/device/device_session.cpp52
-rw-r--r--src/audio_core/device/device_session.h30
-rw-r--r--src/audio_core/in/audio_in_system.cpp10
-rw-r--r--src/audio_core/in/audio_in_system.h2
-rw-r--r--src/audio_core/out/audio_out_system.cpp10
-rw-r--r--src/audio_core/out/audio_out_system.h2
-rw-r--r--src/audio_core/renderer/adsp/adsp.h2
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.cpp9
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.h6
-rw-r--r--src/audio_core/renderer/adsp/command_list_processor.h13
-rw-r--r--src/audio_core/renderer/audio_device.cpp34
-rw-r--r--src/audio_core/renderer/audio_device.h22
-rw-r--r--src/audio_core/renderer/behavior/behavior_info.cpp14
-rw-r--r--src/audio_core/renderer/command/command_buffer.h12
-rw-r--r--src/audio_core/renderer/command/command_generator.h46
-rw-r--r--src/audio_core/renderer/command/effect/compressor.cpp11
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp.cpp18
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp.h8
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp_grouped.h4
-rw-r--r--src/audio_core/renderer/command/sink/device.cpp4
-rw-r--r--src/audio_core/renderer/effect/effect_context.h14
-rw-r--r--src/audio_core/renderer/effect/effect_info_base.h4
-rw-r--r--src/audio_core/renderer/memory/address_info.h5
-rw-r--r--src/audio_core/renderer/nodes/node_states.h4
-rw-r--r--src/audio_core/renderer/performance/performance_manager.h8
-rw-r--r--src/audio_core/renderer/system_manager.cpp46
-rw-r--r--src/audio_core/renderer/system_manager.h9
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_manager.h2
-rw-r--r--src/audio_core/renderer/voice/voice_info.h26
-rw-r--r--src/audio_core/sink/cubeb_sink.cpp380
-rw-r--r--src/audio_core/sink/cubeb_sink.h17
-rw-r--r--src/audio_core/sink/null_sink.h49
-rw-r--r--src/audio_core/sink/sdl2_sink.cpp377
-rw-r--r--src/audio_core/sink/sdl2_sink.h17
-rw-r--r--src/audio_core/sink/sink.h15
-rw-r--r--src/audio_core/sink/sink_details.cpp6
-rw-r--r--src/audio_core/sink/sink_stream.cpp279
-rw-r--r--src/audio_core/sink/sink_stream.h173
-rw-r--r--src/common/CMakeLists.txt4
-rw-r--r--src/common/announce_multiplayer_room.h11
-rw-r--r--src/common/input.h5
-rw-r--r--src/common/microprofile.h9
-rw-r--r--src/common/parent_of_member.h2
-rw-r--r--src/common/settings.cpp3
-rw-r--r--src/common/settings.h3
-rw-r--r--src/common/socket_types.h51
-rw-r--r--src/common/uint128.h1
-rw-r--r--src/core/CMakeLists.txt17
-rw-r--r--src/core/arm/arm_interface.h11
-rw-r--r--src/core/arm/cpu_interrupt_handler.cpp24
-rw-r--r--src/core/arm/cpu_interrupt_handler.h39
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.cpp51
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.h5
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.cpp34
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.h6
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_cp15.cpp31
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_cp15.h2
-rw-r--r--src/core/core.cpp13
-rw-r--r--src/core/core_timing.cpp14
-rw-r--r--src/core/core_timing.h6
-rw-r--r--src/core/debugger/debugger.cpp9
-rw-r--r--src/core/file_sys/ips_layer.cpp7
-rw-r--r--src/core/file_sys/patch_manager.cpp14
-rw-r--r--src/core/file_sys/system_archive/shared_font.cpp2
-rw-r--r--src/core/hid/emulated_controller.cpp80
-rw-r--r--src/core/hid/emulated_controller.h8
-rw-r--r--src/core/hid/hid_types.h12
-rw-r--r--src/core/hid/input_converter.cpp3
-rw-r--r--src/core/hle/kernel/kernel.cpp41
-rw-r--r--src/core/hle/kernel/kernel.h8
-rw-r--r--src/core/hle/kernel/physical_core.cpp29
-rw-r--r--src/core/hle/kernel/physical_core.h17
-rw-r--r--src/core/hle/result.h2
-rw-r--r--src/core/hle/service/acc/acc.cpp2
-rw-r--r--src/core/hle/service/am/am.cpp2
-rw-r--r--src/core/hle/service/am/applets/applet_web_browser.cpp2
-rw-r--r--src/core/hle/service/apm/apm_controller.cpp2
-rw-r--r--src/core/hle/service/audio/audout_u.cpp3
-rw-r--r--src/core/hle/service/audio/audren_u.cpp31
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp3
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp77
-rw-r--r--src/core/hle/service/hid/hid.cpp20
-rw-r--r--src/core/hle/service/ldn/errors.h12
-rw-r--r--src/core/hle/service/ldn/ldn.cpp436
-rw-r--r--src/core/hle/service/ldn/ldn.h6
-rw-r--r--src/core/hle/service/ldn/ldn_results.h27
-rw-r--r--src/core/hle/service/ldn/ldn_types.h292
-rw-r--r--src/core/hle/service/mm/mm_u.cpp10
-rw-r--r--src/core/hle/service/nifm/nifm.cpp342
-rw-r--r--src/core/hle/service/nifm/nifm.h27
-rw-r--r--src/core/hle/service/ns/iplatform_service_manager.cpp (renamed from src/core/hle/service/ns/pl_u.cpp)34
-rw-r--r--src/core/hle/service/ns/iplatform_service_manager.h (renamed from src/core/hle/service/ns/pl_u.h)6
-rw-r--r--src/core/hle/service/ns/ns.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp7
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.cpp4
-rw-r--r--src/core/hle/service/pcv/pcv.cpp93
-rw-r--r--src/core/hle/service/pcv/pcv.h91
-rw-r--r--src/core/hle/service/service.cpp7
-rw-r--r--src/core/hle/service/sockets/bsd.cpp46
-rw-r--r--src/core/hle/service/sockets/bsd.h13
-rw-r--r--src/core/hle/service/sockets/sockets.h6
-rw-r--r--src/core/hle/service/sockets/sockets_translate.cpp2
-rw-r--r--src/core/internal_network/network.cpp62
-rw-r--r--src/core/internal_network/network.h43
-rw-r--r--src/core/internal_network/socket_proxy.cpp290
-rw-r--r--src/core/internal_network/socket_proxy.h97
-rw-r--r--src/core/internal_network/sockets.h132
-rw-r--r--src/core/loader/elf.cpp263
-rw-r--r--src/core/loader/elf.h36
-rw-r--r--src/core/loader/kip.cpp2
-rw-r--r--src/core/loader/loader.cpp11
-rw-r--r--src/core/loader/loader.h1
-rw-r--r--src/core/loader/nro.cpp2
-rw-r--r--src/core/loader/nso.cpp2
-rw-r--r--src/core/memory.cpp81
-rw-r--r--src/core/memory.h6
-rw-r--r--src/dedicated_room/CMakeLists.txt27
-rw-r--r--src/dedicated_room/yuzu_room.cpp382
-rw-r--r--src/dedicated_room/yuzu_room.rc20
-rw-r--r--src/input_common/drivers/sdl_driver.cpp11
-rw-r--r--src/input_common/input_poller.cpp1
-rw-r--r--src/network/CMakeLists.txt6
-rw-r--r--src/network/announce_multiplayer_session.cpp (renamed from src/core/announce_multiplayer_session.cpp)6
-rw-r--r--src/network/announce_multiplayer_session.h (renamed from src/core/announce_multiplayer_session.h)0
-rw-r--r--src/network/room.cpp182
-rw-r--r--src/network/room.h14
-rw-r--r--src/network/room_member.cpp127
-rw-r--r--src/network/room_member.h72
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_image.cpp2
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_image.cpp1
-rw-r--r--src/shader_recompiler/backend/glsl/glsl_emit_context.cpp1
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_image.cpp1
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp6
-rw-r--r--src/shader_recompiler/frontend/ir/ir_emitter.cpp5
-rw-r--r--src/shader_recompiler/frontend/ir/ir_emitter.h2
-rw-r--r--src/shader_recompiler/ir_opt/rescaling_pass.cpp7
-rw-r--r--src/shader_recompiler/ir_opt/texture_pass.cpp23
-rw-r--r--src/shader_recompiler/shader_info.h3
-rw-r--r--src/tests/video_core/buffer_base.cpp7
-rw-r--r--src/video_core/buffer_cache/buffer_base.h7
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h49
-rw-r--r--src/video_core/memory_manager.cpp4
-rw-r--r--src/video_core/query_cache.h12
-rw-r--r--src/video_core/rasterizer_accelerated.cpp17
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp4
-rw-r--r--src/video_core/renderer_opengl/gl_shader_util.cpp1
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp9
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp19
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp371
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.h3
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp34
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp8
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp11
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.cpp5
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp21
-rw-r--r--src/video_core/shader_cache.cpp12
-rw-r--r--src/video_core/shader_cache.h4
-rw-r--r--src/video_core/shader_environment.cpp6
-rw-r--r--src/video_core/texture_cache/texture_cache.h12
-rw-r--r--src/video_core/texture_cache/texture_cache_base.h10
-rw-r--r--src/video_core/textures/decoders.cpp60
-rw-r--r--src/video_core/textures/decoders.h3
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp234
-rw-r--r--src/web_service/verify_user_jwt.cpp4
-rw-r--r--src/yuzu/CMakeLists.txt14
-rw-r--r--src/yuzu/aboutdialog.ui3
-rw-r--r--src/yuzu/applets/qt_profile_select.cpp1
-rw-r--r--src/yuzu/bootmanager.cpp24
-rw-r--r--src/yuzu/configuration/config.cpp4
-rw-r--r--src/yuzu/configuration/configure_audio.ui4
-rw-r--r--src/yuzu/configuration/configure_camera.cpp20
-rw-r--r--src/yuzu/configuration/configure_debug.cpp2
-rw-r--r--src/yuzu/configuration/configure_debug.h4
-rw-r--r--src/yuzu/configuration/configure_debug.ui7
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.cpp8
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.h1
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.ui12
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp25
-rw-r--r--src/yuzu/configuration/configure_tas.ui3
-rw-r--r--src/yuzu/configuration/configure_ui.cpp1
-rw-r--r--src/yuzu/game_list.cpp40
-rw-r--r--src/yuzu/game_list.h3
-rw-r--r--src/yuzu/game_list_p.h5
-rw-r--r--src/yuzu/loading_screen.cpp4
-rw-r--r--src/yuzu/main.cpp126
-rw-r--r--src/yuzu/main.h7
-rw-r--r--src/yuzu/multiplayer/chat_room.cpp39
-rw-r--r--src/yuzu/multiplayer/client_room.cpp3
-rw-r--r--src/yuzu/multiplayer/direct_connect.cpp27
-rw-r--r--src/yuzu/multiplayer/direct_connect.h7
-rw-r--r--src/yuzu/multiplayer/direct_connect.ui2
-rw-r--r--src/yuzu/multiplayer/host_room.cpp23
-rw-r--r--src/yuzu/multiplayer/host_room.h6
-rw-r--r--src/yuzu/multiplayer/lobby.cpp22
-rw-r--r--src/yuzu/multiplayer/lobby.h9
-rw-r--r--src/yuzu/multiplayer/message.cpp17
-rw-r--r--src/yuzu/multiplayer/message.h12
-rw-r--r--src/yuzu/multiplayer/state.cpp28
-rw-r--r--src/yuzu/multiplayer/state.h9
-rw-r--r--src/yuzu/multiplayer/validation.h2
-rw-r--r--src/yuzu/uisettings.h5
-rw-r--r--src/yuzu_cmd/config.cpp1
-rw-r--r--src/yuzu_cmd/default_ini.h4
-rw-r--r--src/yuzu_cmd/yuzu.cpp10
217 files changed, 4558 insertions, 2852 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index fc177fa52..54de1dc94 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -162,6 +162,7 @@ add_subdirectory(video_core)
162add_subdirectory(network) 162add_subdirectory(network)
163add_subdirectory(input_common) 163add_subdirectory(input_common)
164add_subdirectory(shader_recompiler) 164add_subdirectory(shader_recompiler)
165add_subdirectory(dedicated_room)
165 166
166if (YUZU_TESTS) 167if (YUZU_TESTS)
167 add_subdirectory(tests) 168 add_subdirectory(tests)
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 5fe1d5fa5..144f1bab2 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -194,6 +194,7 @@ add_library(audio_core STATIC
194 sink/sink.h 194 sink/sink.h
195 sink/sink_details.cpp 195 sink/sink_details.cpp
196 sink/sink_details.h 196 sink/sink_details.h
197 sink/sink_stream.cpp
197 sink/sink_stream.h 198 sink/sink_stream.h
198) 199)
199 200
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp
index 78e615a10..c845330cd 100644
--- a/src/audio_core/audio_core.cpp
+++ b/src/audio_core/audio_core.cpp
@@ -47,22 +47,12 @@ AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() {
47 return *adsp; 47 return *adsp;
48} 48}
49 49
50void AudioCore::PauseSinks(const bool pausing) const { 50void AudioCore::SetNVDECActive(bool active) {
51 if (pausing) { 51 nvdec_active = active;
52 output_sink->PauseStreams();
53 input_sink->PauseStreams();
54 } else {
55 output_sink->UnpauseStreams();
56 input_sink->UnpauseStreams();
57 }
58} 52}
59 53
60u32 AudioCore::GetStreamQueue() const { 54bool AudioCore::IsNVDECActive() const {
61 return estimated_queue.load(); 55 return nvdec_active;
62}
63
64void AudioCore::SetStreamQueue(u32 size) {
65 estimated_queue.store(size);
66} 56}
67 57
68} // namespace AudioCore 58} // namespace AudioCore
diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h
index 0f7d61ee4..e33e00a3e 100644
--- a/src/audio_core/audio_core.h
+++ b/src/audio_core/audio_core.h
@@ -17,7 +17,7 @@ namespace AudioCore {
17 17
18class AudioManager; 18class AudioManager;
19/** 19/**
20 * Main audio class, sotred inside the core, and holding the audio manager, all sinks, and the ADSP. 20 * Main audio class, stored inside the core, and holding the audio manager, all sinks, and the ADSP.
21 */ 21 */
22class AudioCore { 22class AudioCore {
23public: 23public:
@@ -58,26 +58,16 @@ public:
58 AudioRenderer::ADSP::ADSP& GetADSP(); 58 AudioRenderer::ADSP::ADSP& GetADSP();
59 59
60 /** 60 /**
61 * Pause the sink. Called from the core. 61 * Toggle NVDEC state, used to avoid stall in playback.
62 * 62 *
63 * @param pausing - Is this pause due to an actual pause, or shutdown? 63 * @param active - Set true if nvdec is active, otherwise false.
64 * Unfortunately, shutdown also pauses streams, which can cause issues.
65 */ 64 */
66 void PauseSinks(bool pausing) const; 65 void SetNVDECActive(bool active);
67 66
68 /** 67 /**
69 * Get the size of the current stream queue. 68 * Get NVDEC state.
70 *
71 * @return Current stream queue size.
72 */
73 u32 GetStreamQueue() const;
74
75 /**
76 * Get the size of the current stream queue.
77 *
78 * @param size - New stream size.
79 */ 69 */
80 void SetStreamQueue(u32 size); 70 bool IsNVDECActive() const;
81 71
82private: 72private:
83 /** 73 /**
@@ -93,8 +83,8 @@ private:
93 std::unique_ptr<Sink::Sink> input_sink; 83 std::unique_ptr<Sink::Sink> input_sink;
94 /// The ADSP in the sysmodule 84 /// The ADSP in the sysmodule
95 std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp; 85 std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp;
96 /// Current size of the stream queue 86 /// Is NVDec currently active?
97 std::atomic<u32> estimated_queue{0}; 87 bool nvdec_active{false};
98}; 88};
99 89
100} // namespace AudioCore 90} // namespace AudioCore
diff --git a/src/audio_core/audio_event.h b/src/audio_core/audio_event.h
index 82dd32dca..012d2ed70 100644
--- a/src/audio_core/audio_event.h
+++ b/src/audio_core/audio_event.h
@@ -14,7 +14,7 @@ namespace AudioCore {
14 * Responsible for the input/output events, set by the stream backend when buffers are consumed, and 14 * Responsible for the input/output events, set by the stream backend when buffers are consumed, and
15 * waited on by the audio manager. These callbacks signal the game's events to keep the audio buffer 15 * waited on by the audio manager. These callbacks signal the game's events to keep the audio buffer
16 * recycling going. 16 * recycling going.
17 * In a real Switch this is not a seprate class, and exists entirely within the audio manager. 17 * In a real Switch this is not a separate class, and exists entirely within the audio manager.
18 * On the Switch it's implemented more simply through a MultiWaitEventHolder, where it can 18 * On the Switch it's implemented more simply through a MultiWaitEventHolder, where it can
19 * wait on multiple events at once, and the events are not needed by the backend. 19 * wait on multiple events at once, and the events are not needed by the backend.
20 */ 20 */
@@ -81,7 +81,7 @@ public:
81 void ClearEvents(); 81 void ClearEvents();
82 82
83private: 83private:
84 /// Lock, used bythe audio manager 84 /// Lock, used by the audio manager
85 std::mutex event_lock; 85 std::mutex event_lock;
86 /// Array of events, one per system type (see Type), last event is used to terminate 86 /// Array of events, one per system type (see Type), last event is used to terminate
87 std::array<std::atomic<bool>, 4> events_signalled; 87 std::array<std::atomic<bool>, 4> events_signalled;
diff --git a/src/audio_core/audio_in_manager.cpp b/src/audio_core/audio_in_manager.cpp
index 4aadb7fd6..f39fb4002 100644
--- a/src/audio_core/audio_in_manager.cpp
+++ b/src/audio_core/audio_in_manager.cpp
@@ -82,7 +82,7 @@ u32 Manager::GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceN
82 82
83 auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)}; 83 auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)};
84 if (input_devices.size() > 1) { 84 if (input_devices.size() > 1) {
85 names.push_back(AudioRenderer::AudioDevice::AudioDeviceName("Uac")); 85 names.emplace_back("Uac");
86 return 1; 86 return 1;
87 } 87 }
88 return 0; 88 return 0;
diff --git a/src/audio_core/audio_in_manager.h b/src/audio_core/audio_in_manager.h
index 75b73a0b6..8a519df99 100644
--- a/src/audio_core/audio_in_manager.h
+++ b/src/audio_core/audio_in_manager.h
@@ -59,9 +59,10 @@ public:
59 /** 59 /**
60 * Get a list of audio in device names. 60 * Get a list of audio in device names.
61 * 61 *
62 * @oaram names - Output container to write names to. 62 * @param names - Output container to write names to.
63 * @param max_count - Maximum numebr of deivce names to write. Unused 63 * @param max_count - Maximum number of device names to write. Unused
64 * @param filter - Should the list be filtered? Unused. 64 * @param filter - Should the list be filtered? Unused.
65 *
65 * @return Number of names written. 66 * @return Number of names written.
66 */ 67 */
67 u32 GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names, 68 u32 GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names,
diff --git a/src/audio_core/audio_manager.h b/src/audio_core/audio_manager.h
index 70316e9cb..8cbd95e22 100644
--- a/src/audio_core/audio_manager.h
+++ b/src/audio_core/audio_manager.h
@@ -76,7 +76,7 @@ public:
76 76
77private: 77private:
78 /** 78 /**
79 * Main thread, waiting on a manager signal and calling the registered fucntion. 79 * Main thread, waiting on a manager signal and calling the registered function.
80 */ 80 */
81 void ThreadFunc(); 81 void ThreadFunc();
82 82
diff --git a/src/audio_core/audio_out_manager.cpp b/src/audio_core/audio_out_manager.cpp
index 71d67de64..1766efde1 100644
--- a/src/audio_core/audio_out_manager.cpp
+++ b/src/audio_core/audio_out_manager.cpp
@@ -74,7 +74,7 @@ void Manager::BufferReleaseAndRegister() {
74 74
75u32 Manager::GetAudioOutDeviceNames( 75u32 Manager::GetAudioOutDeviceNames(
76 std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const { 76 std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const {
77 names.push_back({"DeviceOut"}); 77 names.emplace_back("DeviceOut");
78 return 1; 78 return 1;
79} 79}
80 80
diff --git a/src/audio_core/audio_render_manager.h b/src/audio_core/audio_render_manager.h
index 6a508ec56..7119e1b99 100644
--- a/src/audio_core/audio_render_manager.h
+++ b/src/audio_core/audio_render_manager.h
@@ -64,10 +64,10 @@ public:
64 64
65 /** 65 /**
66 * Add a renderer system to the manager. 66 * Add a renderer system to the manager.
67 * The system will be reguarly called to generate commands for the AudioRenderer. 67 * The system will be regularly called to generate commands for the AudioRenderer.
68 * 68 *
69 * @param system - The system to add. 69 * @param system - The system to add.
70 * @return True if the system was sucessfully added, otherwise false. 70 * @return True if the system was successfully added, otherwise false.
71 */ 71 */
72 bool AddSystem(System& system); 72 bool AddSystem(System& system);
73 73
@@ -75,7 +75,7 @@ public:
75 * Remove a renderer system from the manager. 75 * Remove a renderer system from the manager.
76 * 76 *
77 * @param system - The system to remove. 77 * @param system - The system to remove.
78 * @return True if the system was sucessfully removed, otherwise false. 78 * @return True if the system was successfully removed, otherwise false.
79 */ 79 */
80 bool RemoveSystem(System& system); 80 bool RemoveSystem(System& system);
81 81
diff --git a/src/audio_core/device/audio_buffer.h b/src/audio_core/device/audio_buffer.h
index cae7fa970..7128ef72a 100644
--- a/src/audio_core/device/audio_buffer.h
+++ b/src/audio_core/device/audio_buffer.h
@@ -8,6 +8,10 @@
8namespace AudioCore { 8namespace AudioCore {
9 9
10struct AudioBuffer { 10struct AudioBuffer {
11 /// Timestamp this buffer started playing.
12 u64 start_timestamp;
13 /// Timestamp this buffer should finish playing.
14 u64 end_timestamp;
11 /// Timestamp this buffer completed playing. 15 /// Timestamp this buffer completed playing.
12 s64 played_timestamp; 16 s64 played_timestamp;
13 /// Game memory address for these samples. 17 /// Game memory address for these samples.
diff --git a/src/audio_core/device/audio_buffers.h b/src/audio_core/device/audio_buffers.h
index 5d1979ea0..3ecbbb63f 100644
--- a/src/audio_core/device/audio_buffers.h
+++ b/src/audio_core/device/audio_buffers.h
@@ -58,6 +58,7 @@ public:
58 if (index < 0) { 58 if (index < 0) {
59 index += N; 59 index += N;
60 } 60 }
61
61 out_buffers.push_back(buffers[index]); 62 out_buffers.push_back(buffers[index]);
62 registered_count++; 63 registered_count++;
63 registered_index = (registered_index + 1) % append_limit; 64 registered_index = (registered_index + 1) % append_limit;
@@ -87,7 +88,9 @@ public:
87 /** 88 /**
88 * Release all registered buffers. 89 * Release all registered buffers.
89 * 90 *
90 * @param timestamp - The released timestamp for this buffer. 91 * @param core_timing - The CoreTiming instance
92 * @param session - The device session
93 *
91 * @return Is the buffer was released. 94 * @return Is the buffer was released.
92 */ 95 */
93 bool ReleaseBuffers(Core::Timing::CoreTiming& core_timing, DeviceSession& session) { 96 bool ReleaseBuffers(Core::Timing::CoreTiming& core_timing, DeviceSession& session) {
@@ -100,7 +103,7 @@ public:
100 } 103 }
101 104
102 // Check with the backend if this buffer can be released yet. 105 // Check with the backend if this buffer can be released yet.
103 if (!session.IsBufferConsumed(buffers[index].tag)) { 106 if (!session.IsBufferConsumed(buffers[index])) {
104 break; 107 break;
105 } 108 }
106 109
@@ -280,6 +283,16 @@ public:
280 return true; 283 return true;
281 } 284 }
282 285
286 u64 GetNextTimestamp() const {
287 // Iterate backwards through the buffer queue, and take the most recent buffer's end
288 std::scoped_lock l{lock};
289 auto index{appended_index - 1};
290 if (index < 0) {
291 index += append_limit;
292 }
293 return buffers[index].end_timestamp;
294 }
295
283private: 296private:
284 /// Buffer lock 297 /// Buffer lock
285 mutable std::recursive_mutex lock{}; 298 mutable std::recursive_mutex lock{};
diff --git a/src/audio_core/device/device_session.cpp b/src/audio_core/device/device_session.cpp
index 095fc96ce..c71c3a376 100644
--- a/src/audio_core/device/device_session.cpp
+++ b/src/audio_core/device/device_session.cpp
@@ -7,11 +7,20 @@
7#include "audio_core/device/device_session.h" 7#include "audio_core/device/device_session.h"
8#include "audio_core/sink/sink_stream.h" 8#include "audio_core/sink/sink_stream.h"
9#include "core/core.h" 9#include "core/core.h"
10#include "core/core_timing.h"
10#include "core/memory.h" 11#include "core/memory.h"
11 12
12namespace AudioCore { 13namespace AudioCore {
13 14
14DeviceSession::DeviceSession(Core::System& system_) : system{system_} {} 15using namespace std::literals;
16constexpr auto INCREMENT_TIME{5ms};
17
18DeviceSession::DeviceSession(Core::System& system_)
19 : system{system_}, thread_event{Core::Timing::CreateEvent(
20 "AudioOutSampleTick",
21 [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
22 return ThreadFunc();
23 })} {}
15 24
16DeviceSession::~DeviceSession() { 25DeviceSession::~DeviceSession() {
17 Finalize(); 26 Finalize();
@@ -50,20 +59,21 @@ void DeviceSession::Finalize() {
50} 59}
51 60
52void DeviceSession::Start() { 61void DeviceSession::Start() {
53 stream->SetPlayedSampleCount(played_sample_count); 62 if (stream) {
54 stream->Start(); 63 stream->Start();
64 system.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds::zero(), INCREMENT_TIME,
65 thread_event);
66 }
55} 67}
56 68
57void DeviceSession::Stop() { 69void DeviceSession::Stop() {
58 if (stream) { 70 if (stream) {
59 played_sample_count = stream->GetPlayedSampleCount();
60 stream->Stop(); 71 stream->Stop();
72 system.CoreTiming().UnscheduleEvent(thread_event, {});
61 } 73 }
62} 74}
63 75
64void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const { 76void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const {
65 auto& memory{system.Memory()};
66
67 for (size_t i = 0; i < buffers.size(); i++) { 77 for (size_t i = 0; i < buffers.size(); i++) {
68 Sink::SinkBuffer new_buffer{ 78 Sink::SinkBuffer new_buffer{
69 .frames = buffers[i].size / (channel_count * sizeof(s16)), 79 .frames = buffers[i].size / (channel_count * sizeof(s16)),
@@ -77,7 +87,7 @@ void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const {
77 stream->AppendBuffer(new_buffer, samples); 87 stream->AppendBuffer(new_buffer, samples);
78 } else { 88 } else {
79 std::vector<s16> samples(buffers[i].size / sizeof(s16)); 89 std::vector<s16> samples(buffers[i].size / sizeof(s16));
80 memory.ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size); 90 system.Memory().ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size);
81 stream->AppendBuffer(new_buffer, samples); 91 stream->AppendBuffer(new_buffer, samples);
82 } 92 }
83 } 93 }
@@ -85,17 +95,13 @@ void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const {
85 95
86void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const { 96void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const {
87 if (type == Sink::StreamType::In) { 97 if (type == Sink::StreamType::In) {
88 auto& memory{system.Memory()};
89 auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))}; 98 auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))};
90 memory.WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size); 99 system.Memory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size);
91 } 100 }
92} 101}
93 102
94bool DeviceSession::IsBufferConsumed(u64 tag) const { 103bool DeviceSession::IsBufferConsumed(AudioBuffer& buffer) const {
95 if (stream) { 104 return played_sample_count >= buffer.end_timestamp;
96 return stream->IsBufferConsumed(tag);
97 }
98 return true;
99} 105}
100 106
101void DeviceSession::SetVolume(f32 volume) const { 107void DeviceSession::SetVolume(f32 volume) const {
@@ -105,10 +111,22 @@ void DeviceSession::SetVolume(f32 volume) const {
105} 111}
106 112
107u64 DeviceSession::GetPlayedSampleCount() const { 113u64 DeviceSession::GetPlayedSampleCount() const {
108 if (stream) { 114 return played_sample_count;
109 return stream->GetPlayedSampleCount(); 115}
116
117std::optional<std::chrono::nanoseconds> DeviceSession::ThreadFunc() {
118 // Add 5ms of samples at a 48K sample rate.
119 played_sample_count += 48'000 * INCREMENT_TIME / 1s;
120 if (type == Sink::StreamType::Out) {
121 system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioOutManager, true);
122 } else {
123 system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioInManager, true);
110 } 124 }
111 return 0; 125 return std::nullopt;
126}
127
128void DeviceSession::SetRingSize(u32 ring_size) {
129 stream->SetRingSize(ring_size);
112} 130}
113 131
114} // namespace AudioCore 132} // namespace AudioCore
diff --git a/src/audio_core/device/device_session.h b/src/audio_core/device/device_session.h
index 4a031b765..53b649c61 100644
--- a/src/audio_core/device/device_session.h
+++ b/src/audio_core/device/device_session.h
@@ -3,6 +3,9 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <chrono>
7#include <memory>
8#include <optional>
6#include <span> 9#include <span>
7 10
8#include "audio_core/common/common.h" 11#include "audio_core/common/common.h"
@@ -11,9 +14,13 @@
11 14
12namespace Core { 15namespace Core {
13class System; 16class System;
14} 17namespace Timing {
18struct EventType;
19} // namespace Timing
20} // namespace Core
15 21
16namespace AudioCore { 22namespace AudioCore {
23
17namespace Sink { 24namespace Sink {
18class SinkStream; 25class SinkStream;
19struct SinkBuffer; 26struct SinkBuffer;
@@ -67,10 +74,11 @@ public:
67 /** 74 /**
68 * Check if the buffer for the given tag has been consumed by the backend. 75 * Check if the buffer for the given tag has been consumed by the backend.
69 * 76 *
70 * @param tag - Unqiue tag of the buffer to check. 77 * @param buffer - the buffer to check.
78 *
71 * @return true if the buffer has been consumed, otherwise false. 79 * @return true if the buffer has been consumed, otherwise false.
72 */ 80 */
73 bool IsBufferConsumed(u64 tag) const; 81 bool IsBufferConsumed(AudioBuffer& buffer) const;
74 82
75 /** 83 /**
76 * Start this device session, starting the backend stream. 84 * Start this device session, starting the backend stream.
@@ -96,6 +104,16 @@ public:
96 */ 104 */
97 u64 GetPlayedSampleCount() const; 105 u64 GetPlayedSampleCount() const;
98 106
107 /*
108 * CoreTiming callback to increment played_sample_count over time.
109 */
110 std::optional<std::chrono::nanoseconds> ThreadFunc();
111
112 /*
113 * Set the size of the ring buffer.
114 */
115 void SetRingSize(u32 ring_size);
116
99private: 117private:
100 /// System 118 /// System
101 Core::System& system; 119 Core::System& system;
@@ -118,9 +136,13 @@ private:
118 /// Applet resource user id of this device session 136 /// Applet resource user id of this device session
119 u64 applet_resource_user_id{}; 137 u64 applet_resource_user_id{};
120 /// Total number of samples played by this device session 138 /// Total number of samples played by this device session
121 u64 played_sample_count{}; 139 std::atomic<u64> played_sample_count{};
140 /// Event increasing the played sample count every 5ms
141 std::shared_ptr<Core::Timing::EventType> thread_event;
122 /// Is this session initialised? 142 /// Is this session initialised?
123 bool initialized{}; 143 bool initialized{};
144 /// Buffer queue
145 std::vector<AudioBuffer> buffer_queue{};
124}; 146};
125 147
126} // namespace AudioCore 148} // namespace AudioCore
diff --git a/src/audio_core/in/audio_in_system.cpp b/src/audio_core/in/audio_in_system.cpp
index ec5d37ed4..7e80ba03c 100644
--- a/src/audio_core/in/audio_in_system.cpp
+++ b/src/audio_core/in/audio_in_system.cpp
@@ -93,6 +93,7 @@ Result System::Start() {
93 std::vector<AudioBuffer> buffers_to_flush{}; 93 std::vector<AudioBuffer> buffers_to_flush{};
94 buffers.RegisterBuffers(buffers_to_flush); 94 buffers.RegisterBuffers(buffers_to_flush);
95 session->AppendBuffers(buffers_to_flush); 95 session->AppendBuffers(buffers_to_flush);
96 session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
96 97
97 return ResultSuccess; 98 return ResultSuccess;
98} 99}
@@ -112,8 +113,13 @@ bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) {
112 return false; 113 return false;
113 } 114 }
114 115
115 AudioBuffer new_buffer{ 116 const auto timestamp{buffers.GetNextTimestamp()};
116 .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; 117 AudioBuffer new_buffer{.start_timestamp = timestamp,
118 .end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
119 .played_timestamp = 0,
120 .samples = buffer.samples,
121 .tag = tag,
122 .size = buffer.size};
117 123
118 buffers.AppendBuffer(new_buffer); 124 buffers.AppendBuffer(new_buffer);
119 RegisterBuffers(); 125 RegisterBuffers();
diff --git a/src/audio_core/in/audio_in_system.h b/src/audio_core/in/audio_in_system.h
index 165e35d83..9ddc8daae 100644
--- a/src/audio_core/in/audio_in_system.h
+++ b/src/audio_core/in/audio_in_system.h
@@ -208,7 +208,7 @@ public:
208 /** 208 /**
209 * Set this system's current volume. 209 * Set this system's current volume.
210 * 210 *
211 * @param The new volume. 211 * @param volume The new volume.
212 */ 212 */
213 void SetVolume(f32 volume); 213 void SetVolume(f32 volume);
214 214
diff --git a/src/audio_core/out/audio_out_system.cpp b/src/audio_core/out/audio_out_system.cpp
index 35afddf06..8941b09a0 100644
--- a/src/audio_core/out/audio_out_system.cpp
+++ b/src/audio_core/out/audio_out_system.cpp
@@ -92,6 +92,7 @@ Result System::Start() {
92 std::vector<AudioBuffer> buffers_to_flush{}; 92 std::vector<AudioBuffer> buffers_to_flush{};
93 buffers.RegisterBuffers(buffers_to_flush); 93 buffers.RegisterBuffers(buffers_to_flush);
94 session->AppendBuffers(buffers_to_flush); 94 session->AppendBuffers(buffers_to_flush);
95 session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
95 96
96 return ResultSuccess; 97 return ResultSuccess;
97} 98}
@@ -111,8 +112,13 @@ bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) {
111 return false; 112 return false;
112 } 113 }
113 114
114 AudioBuffer new_buffer{ 115 const auto timestamp{buffers.GetNextTimestamp()};
115 .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; 116 AudioBuffer new_buffer{.start_timestamp = timestamp,
117 .end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
118 .played_timestamp = 0,
119 .samples = buffer.samples,
120 .tag = tag,
121 .size = buffer.size};
116 122
117 buffers.AppendBuffer(new_buffer); 123 buffers.AppendBuffer(new_buffer);
118 RegisterBuffers(); 124 RegisterBuffers();
diff --git a/src/audio_core/out/audio_out_system.h b/src/audio_core/out/audio_out_system.h
index 4ca2f3417..205ead861 100644
--- a/src/audio_core/out/audio_out_system.h
+++ b/src/audio_core/out/audio_out_system.h
@@ -199,7 +199,7 @@ public:
199 /** 199 /**
200 * Set this system's current volume. 200 * Set this system's current volume.
201 * 201 *
202 * @param The new volume. 202 * @param volume The new volume.
203 */ 203 */
204 void SetVolume(f32 volume); 204 void SetVolume(f32 volume);
205 205
diff --git a/src/audio_core/renderer/adsp/adsp.h b/src/audio_core/renderer/adsp/adsp.h
index 4dfcef4a5..523184dc2 100644
--- a/src/audio_core/renderer/adsp/adsp.h
+++ b/src/audio_core/renderer/adsp/adsp.h
@@ -63,8 +63,6 @@ public:
63 63
64 /** 64 /**
65 * Stop the ADSP. 65 * Stop the ADSP.
66 *
67 * @return True if started or already running, otherwise false.
68 */ 66 */
69 void Stop(); 67 void Stop();
70 68
diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp
index 3967ccfe6..bcd889ecb 100644
--- a/src/audio_core/renderer/adsp/audio_renderer.cpp
+++ b/src/audio_core/renderer/adsp/audio_renderer.cpp
@@ -106,9 +106,6 @@ void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) {
106 106
107 mailbox = mailbox_; 107 mailbox = mailbox_;
108 thread = std::thread(&AudioRenderer::ThreadFunc, this); 108 thread = std::thread(&AudioRenderer::ThreadFunc, this);
109 for (auto& stream : streams) {
110 stream->Start();
111 }
112 running = true; 109 running = true;
113} 110}
114 111
@@ -130,6 +127,7 @@ void AudioRenderer::CreateSinkStreams() {
130 std::string name{fmt::format("ADSP_RenderStream-{}", i)}; 127 std::string name{fmt::format("ADSP_RenderStream-{}", i)};
131 streams[i] = 128 streams[i] =
132 sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render); 129 sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render);
130 streams[i]->SetRingSize(4);
133 } 131 }
134} 132}
135 133
@@ -198,11 +196,6 @@ void AudioRenderer::ThreadFunc() {
198 command_list_processor.Process(index) - start_time; 196 command_list_processor.Process(index) - start_time;
199 } 197 }
200 198
201 if (index == 0) {
202 auto stream{command_list_processor.GetOutputSinkStream()};
203 system.AudioCore().SetStreamQueue(stream->GetQueueSize());
204 }
205
206 const auto end_time{system.CoreTiming().GetClockTicks()}; 199 const auto end_time{system.CoreTiming().GetClockTicks()};
207 200
208 command_buffer.remaining_command_count = 201 command_buffer.remaining_command_count =
diff --git a/src/audio_core/renderer/adsp/audio_renderer.h b/src/audio_core/renderer/adsp/audio_renderer.h
index b6ced9d2b..49f66f21c 100644
--- a/src/audio_core/renderer/adsp/audio_renderer.h
+++ b/src/audio_core/renderer/adsp/audio_renderer.h
@@ -52,7 +52,7 @@ public:
52 /** 52 /**
53 * Send a message from the host to the AudioRenderer. 53 * Send a message from the host to the AudioRenderer.
54 * 54 *
55 * @param message_ - The message to send to the AudioRenderer. 55 * @param message - The message to send to the AudioRenderer.
56 */ 56 */
57 void HostSendMessage(RenderMessage message); 57 void HostSendMessage(RenderMessage message);
58 58
@@ -66,7 +66,7 @@ public:
66 /** 66 /**
67 * Send a message from the AudioRenderer to the host. 67 * Send a message from the AudioRenderer to the host.
68 * 68 *
69 * @param message_ - The message to send to the host. 69 * @param message - The message to send to the host.
70 */ 70 */
71 void ADSPSendMessage(RenderMessage message); 71 void ADSPSendMessage(RenderMessage message);
72 72
@@ -163,7 +163,7 @@ public:
163 /** 163 /**
164 * Start the AudioRenderer. 164 * Start the AudioRenderer.
165 * 165 *
166 * @param The mailbox to use for this session. 166 * @param mailbox The mailbox to use for this session.
167 */ 167 */
168 void Start(AudioRenderer_Mailbox* mailbox); 168 void Start(AudioRenderer_Mailbox* mailbox);
169 169
diff --git a/src/audio_core/renderer/adsp/command_list_processor.h b/src/audio_core/renderer/adsp/command_list_processor.h
index 3f99173e3..d78269e1d 100644
--- a/src/audio_core/renderer/adsp/command_list_processor.h
+++ b/src/audio_core/renderer/adsp/command_list_processor.h
@@ -33,10 +33,10 @@ public:
33 /** 33 /**
34 * Initialize the processor. 34 * Initialize the processor.
35 * 35 *
36 * @param system_ - The core system. 36 * @param system - The core system.
37 * @param buffer - The command buffer to process. 37 * @param buffer - The command buffer to process.
38 * @param size - The size of the buffer. 38 * @param size - The size of the buffer.
39 * @param stream_ - The stream to be used for sending the samples. 39 * @param stream - The stream to be used for sending the samples.
40 */ 40 */
41 void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream); 41 void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream);
42 42
@@ -72,7 +72,8 @@ public:
72 /** 72 /**
73 * Process the command list. 73 * Process the command list.
74 * 74 *
75 * @param index - Index of the current command list. 75 * @param session_id - Session ID for the commands being processed.
76 *
76 * @return The time taken to process. 77 * @return The time taken to process.
77 */ 78 */
78 u64 Process(u32 session_id); 79 u64 Process(u32 session_id);
@@ -89,7 +90,7 @@ public:
89 u8* commands{}; 90 u8* commands{};
90 /// The command buffer size 91 /// The command buffer size
91 u64 commands_buffer_size{}; 92 u64 commands_buffer_size{};
92 /// The maximum processing time alloted 93 /// The maximum processing time allotted
93 u64 max_process_time{}; 94 u64 max_process_time{};
94 /// The number of commands in the buffer 95 /// The number of commands in the buffer
95 u32 command_count{}; 96 u32 command_count{};
diff --git a/src/audio_core/renderer/audio_device.cpp b/src/audio_core/renderer/audio_device.cpp
index d5886e55e..0d9d8f6ce 100644
--- a/src/audio_core/renderer/audio_device.cpp
+++ b/src/audio_core/renderer/audio_device.cpp
@@ -1,6 +1,9 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <array>
5#include <span>
6
4#include "audio_core/audio_core.h" 7#include "audio_core/audio_core.h"
5#include "audio_core/common/feature_support.h" 8#include "audio_core/common/feature_support.h"
6#include "audio_core/renderer/audio_device.h" 9#include "audio_core/renderer/audio_device.h"
@@ -9,14 +12,33 @@
9 12
10namespace AudioCore::AudioRenderer { 13namespace AudioCore::AudioRenderer {
11 14
15constexpr std::array usb_device_names{
16 AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
17 AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
18 AudioDevice::AudioDeviceName{"AudioTvOutput"},
19 AudioDevice::AudioDeviceName{"AudioUsbDeviceOutput"},
20};
21
22constexpr std::array device_names{
23 AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
24 AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
25 AudioDevice::AudioDeviceName{"AudioTvOutput"},
26};
27
28constexpr std::array output_device_names{
29 AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
30 AudioDevice::AudioDeviceName{"AudioTvOutput"},
31 AudioDevice::AudioDeviceName{"AudioExternalOutput"},
32};
33
12AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_, 34AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_,
13 const u32 revision) 35 const u32 revision)
14 : output_sink{system.AudioCore().GetOutputSink()}, 36 : output_sink{system.AudioCore().GetOutputSink()},
15 applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {} 37 applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {}
16 38
17u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, 39u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
18 const size_t max_count) { 40 const size_t max_count) const {
19 std::span<AudioDeviceName> names{}; 41 std::span<const AudioDeviceName> names{};
20 42
21 if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) { 43 if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) {
22 names = usb_device_names; 44 names = usb_device_names;
@@ -24,7 +46,7 @@ u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
24 names = device_names; 46 names = device_names;
25 } 47 }
26 48
27 u32 out_count{static_cast<u32>(std::min(max_count, names.size()))}; 49 const u32 out_count{static_cast<u32>(std::min(max_count, names.size()))};
28 for (u32 i = 0; i < out_count; i++) { 50 for (u32 i = 0; i < out_count; i++) {
29 out_buffer.push_back(names[i]); 51 out_buffer.push_back(names[i]);
30 } 52 }
@@ -32,8 +54,8 @@ u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
32} 54}
33 55
34u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, 56u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer,
35 const size_t max_count) { 57 const size_t max_count) const {
36 u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))}; 58 const u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))};
37 59
38 for (u32 i = 0; i < out_count; i++) { 60 for (u32 i = 0; i < out_count; i++) {
39 out_buffer.push_back(output_device_names[i]); 61 out_buffer.push_back(output_device_names[i]);
@@ -45,7 +67,7 @@ void AudioDevice::SetDeviceVolumes(const f32 volume) {
45 output_sink.SetDeviceVolume(volume); 67 output_sink.SetDeviceVolume(volume);
46} 68}
47 69
48f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) { 70f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) const {
49 return output_sink.GetDeviceVolume(); 71 return output_sink.GetDeviceVolume();
50} 72}
51 73
diff --git a/src/audio_core/renderer/audio_device.h b/src/audio_core/renderer/audio_device.h
index 1f449f261..dd6be70ee 100644
--- a/src/audio_core/renderer/audio_device.h
+++ b/src/audio_core/renderer/audio_device.h
@@ -3,7 +3,7 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <span> 6#include <string_view>
7 7
8#include "audio_core/audio_render_manager.h" 8#include "audio_core/audio_render_manager.h"
9 9
@@ -23,21 +23,13 @@ namespace AudioRenderer {
23class AudioDevice { 23class AudioDevice {
24public: 24public:
25 struct AudioDeviceName { 25 struct AudioDeviceName {
26 std::array<char, 0x100> name; 26 std::array<char, 0x100> name{};
27 27
28 AudioDeviceName(const char* name_) { 28 constexpr AudioDeviceName(std::string_view name_) {
29 std::strncpy(name.data(), name_, name.size()); 29 name_.copy(name.data(), name.size() - 1);
30 } 30 }
31 }; 31 };
32 32
33 std::array<AudioDeviceName, 4> usb_device_names{"AudioStereoJackOutput",
34 "AudioBuiltInSpeakerOutput", "AudioTvOutput",
35 "AudioUsbDeviceOutput"};
36 std::array<AudioDeviceName, 3> device_names{"AudioStereoJackOutput",
37 "AudioBuiltInSpeakerOutput", "AudioTvOutput"};
38 std::array<AudioDeviceName, 3> output_device_names{"AudioBuiltInSpeakerOutput", "AudioTvOutput",
39 "AudioExternalOutput"};
40
41 explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision); 33 explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision);
42 34
43 /** 35 /**
@@ -47,7 +39,7 @@ public:
47 * @param max_count - Maximum number of devices to write (count of out_buffer). 39 * @param max_count - Maximum number of devices to write (count of out_buffer).
48 * @return Number of device names written. 40 * @return Number of device names written.
49 */ 41 */
50 u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count); 42 u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const;
51 43
52 /** 44 /**
53 * Get a list of the available output devices. 45 * Get a list of the available output devices.
@@ -57,7 +49,7 @@ public:
57 * @param max_count - Maximum number of devices to write (count of out_buffer). 49 * @param max_count - Maximum number of devices to write (count of out_buffer).
58 * @return Number of device names written. 50 * @return Number of device names written.
59 */ 51 */
60 u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count); 52 u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const;
61 53
62 /** 54 /**
63 * Set the volume of all streams in the backend sink. 55 * Set the volume of all streams in the backend sink.
@@ -73,7 +65,7 @@ public:
73 * @param name - Name of the device to check. Unused. 65 * @param name - Name of the device to check. Unused.
74 * @return Volume of the device. 66 * @return Volume of the device.
75 */ 67 */
76 f32 GetDeviceVolume(std::string_view name); 68 f32 GetDeviceVolume(std::string_view name) const;
77 69
78private: 70private:
79 /// Backend output sink for the device 71 /// Backend output sink for the device
diff --git a/src/audio_core/renderer/behavior/behavior_info.cpp b/src/audio_core/renderer/behavior/behavior_info.cpp
index c5d4d66d8..92140aaea 100644
--- a/src/audio_core/renderer/behavior/behavior_info.cpp
+++ b/src/audio_core/renderer/behavior/behavior_info.cpp
@@ -43,13 +43,15 @@ void BehaviorInfo::AppendError(ErrorInfo& error) {
43} 43}
44 44
45void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) { 45void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) {
46 auto error_count_{std::min(error_count, MaxErrors)}; 46 out_count = std::min(error_count, MaxErrors);
47 std::memset(out_errors.data(), 0, MaxErrors * sizeof(ErrorInfo)); 47
48 48 for (size_t i = 0; i < MaxErrors; i++) {
49 for (size_t i = 0; i < error_count_; i++) { 49 if (i < out_count) {
50 out_errors[i] = errors[i]; 50 out_errors[i] = errors[i];
51 } else {
52 out_errors[i] = {};
53 }
51 } 54 }
52 out_count = error_count_;
53} 55}
54 56
55void BehaviorInfo::UpdateFlags(const Flags flags_) { 57void BehaviorInfo::UpdateFlags(const Flags flags_) {
diff --git a/src/audio_core/renderer/command/command_buffer.h b/src/audio_core/renderer/command/command_buffer.h
index 496b0e50a..162170846 100644
--- a/src/audio_core/renderer/command/command_buffer.h
+++ b/src/audio_core/renderer/command/command_buffer.h
@@ -191,6 +191,7 @@ public:
191 * @param volume - Current mix volume used for calculating the ramp. 191 * @param volume - Current mix volume used for calculating the ramp.
192 * @param prev_volume - Previous mix volume, used for calculating the ramp, 192 * @param prev_volume - Previous mix volume, used for calculating the ramp,
193 * also applied to the input. 193 * also applied to the input.
194 * @param prev_samples - Previous sample buffer. Used for depopping.
194 * @param precision - Number of decimal bits for fixed point operations. 195 * @param precision - Number of decimal bits for fixed point operations.
195 */ 196 */
196 void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index, 197 void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index,
@@ -208,6 +209,7 @@ public:
208 * @param volumes - Current mix volumes used for calculating the ramp. 209 * @param volumes - Current mix volumes used for calculating the ramp.
209 * @param prev_volumes - Previous mix volumes, used for calculating the ramp, 210 * @param prev_volumes - Previous mix volumes, used for calculating the ramp,
210 * also applied to the input. 211 * also applied to the input.
212 * @param prev_samples - Previous sample buffer. Used for depopping.
211 * @param precision - Number of decimal bits for fixed point operations. 213 * @param precision - Number of decimal bits for fixed point operations.
212 */ 214 */
213 void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index, 215 void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index,
@@ -297,11 +299,11 @@ public:
297 /** 299 /**
298 * Generate a device sink command, adding it to the command list. 300 * Generate a device sink command, adding it to the command list.
299 * 301 *
300 * @param node_id - Node id of the voice this command is generated for. 302 * @param node_id - Node id of the voice this command is generated for.
301 * @param buffer_offset - Base mix buffer offset to use. 303 * @param buffer_offset - Base mix buffer offset to use.
302 * @param sink_info - The sink_info to generate this command from. 304 * @param sink_info - The sink_info to generate this command from.
303 * @session_id - System session id this command is generated from. 305 * @param session_id - System session id this command is generated from.
304 * @samples_buffer - The buffer to be sent to the sink if upsampling is not used. 306 * @param samples_buffer - The buffer to be sent to the sink if upsampling is not used.
305 */ 307 */
306 void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info, 308 void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info,
307 u32 session_id, std::span<s32> samples_buffer); 309 u32 session_id, std::span<s32> samples_buffer);
diff --git a/src/audio_core/renderer/command/command_generator.h b/src/audio_core/renderer/command/command_generator.h
index d80d9b0d8..b3cd7b408 100644
--- a/src/audio_core/renderer/command/command_generator.h
+++ b/src/audio_core/renderer/command/command_generator.h
@@ -197,9 +197,9 @@ public:
197 /** 197 /**
198 * Generate an I3DL2 reverb effect command. 198 * Generate an I3DL2 reverb effect command.
199 * 199 *
200 * @param buffer_offset - Base mix buffer offset to use. 200 * @param buffer_offset - Base mix buffer offset to use.
201 * @param effect_info_base - I3DL2Reverb effect info. 201 * @param effect_info - I3DL2Reverb effect info.
202 * @param node_id - Node id of the mix this command is generated for. 202 * @param node_id - Node id of the mix this command is generated for.
203 */ 203 */
204 void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, 204 void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
205 s32 node_id); 205 s32 node_id);
@@ -207,18 +207,18 @@ public:
207 /** 207 /**
208 * Generate an aux effect command. 208 * Generate an aux effect command.
209 * 209 *
210 * @param buffer_offset - Base mix buffer offset to use. 210 * @param buffer_offset - Base mix buffer offset to use.
211 * @param effect_info_base - Aux effect info. 211 * @param effect_info - Aux effect info.
212 * @param node_id - Node id of the mix this command is generated for. 212 * @param node_id - Node id of the mix this command is generated for.
213 */ 213 */
214 void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); 214 void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
215 215
216 /** 216 /**
217 * Generate a biquad filter effect command. 217 * Generate a biquad filter effect command.
218 * 218 *
219 * @param buffer_offset - Base mix buffer offset to use. 219 * @param buffer_offset - Base mix buffer offset to use.
220 * @param effect_info_base - Aux effect info. 220 * @param effect_info - Aux effect info.
221 * @param node_id - Node id of the mix this command is generated for. 221 * @param node_id - Node id of the mix this command is generated for.
222 */ 222 */
223 void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, 223 void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
224 s32 node_id); 224 s32 node_id);
@@ -226,10 +226,10 @@ public:
226 /** 226 /**
227 * Generate a light limiter effect command. 227 * Generate a light limiter effect command.
228 * 228 *
229 * @param buffer_offset - Base mix buffer offset to use. 229 * @param buffer_offset - Base mix buffer offset to use.
230 * @param effect_info_base - Limiter effect info. 230 * @param effect_info - Limiter effect info.
231 * @param node_id - Node id of the mix this command is generated for. 231 * @param node_id - Node id of the mix this command is generated for.
232 * @param effect_index - Index for the statistics state. 232 * @param effect_index - Index for the statistics state.
233 */ 233 */
234 void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, 234 void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
235 s32 node_id, u32 effect_index); 235 s32 node_id, u32 effect_index);
@@ -238,21 +238,20 @@ public:
238 * Generate a capture effect command. 238 * Generate a capture effect command.
239 * Writes a mix buffer back to game memory. 239 * Writes a mix buffer back to game memory.
240 * 240 *
241 * @param buffer_offset - Base mix buffer offset to use. 241 * @param buffer_offset - Base mix buffer offset to use.
242 * @param effect_info_base - Capture effect info. 242 * @param effect_info - Capture effect info.
243 * @param node_id - Node id of the mix this command is generated for. 243 * @param node_id - Node id of the mix this command is generated for.
244 */ 244 */
245 void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); 245 void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
246 246
247 /** 247 /**
248 * Generate a compressor effect command. 248 * Generate a compressor effect command.
249 * 249 *
250 * @param buffer_offset - Base mix buffer offset to use. 250 * @param buffer_offset - Base mix buffer offset to use.
251 * @param effect_info_base - Compressor effect info. 251 * @param effect_info - Compressor effect info.
252 * @param node_id - Node id of the mix this command is generated for. 252 * @param node_id - Node id of the mix this command is generated for.
253 */ 253 */
254 void GenerateCompressorCommand(const s16 buffer_offset, EffectInfoBase& effect_info, 254 void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
255 const s32 node_id);
256 255
257 /** 256 /**
258 * Generate all effect commands for a mix. 257 * Generate all effect commands for a mix.
@@ -318,8 +317,9 @@ public:
318 * Generate a performance command. 317 * Generate a performance command.
319 * Used to report performance metrics of the AudioRenderer back to the game. 318 * Used to report performance metrics of the AudioRenderer back to the game.
320 * 319 *
321 * @param buffer_offset - Base mix buffer offset to use. 320 * @param node_id - Node ID of the mix this command is generated for
322 * @param sink_info - Sink info to generate the commands from. 321 * @param state - Output state of the generated performance command
322 * @param entry_addresses - Addresses to be written
323 */ 323 */
324 void GeneratePerformanceCommand(s32 node_id, PerformanceState state, 324 void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
325 const PerformanceEntryAddresses& entry_addresses); 325 const PerformanceEntryAddresses& entry_addresses);
diff --git a/src/audio_core/renderer/command/effect/compressor.cpp b/src/audio_core/renderer/command/effect/compressor.cpp
index 2ebc140f1..7229618e8 100644
--- a/src/audio_core/renderer/command/effect/compressor.cpp
+++ b/src/audio_core/renderer/command/effect/compressor.cpp
@@ -11,7 +11,7 @@
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::AudioRenderer {
13 13
14static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& params, 14static void SetCompressorEffectParameter(const CompressorInfo::ParameterVersion2& params,
15 CompressorInfo::State& state) { 15 CompressorInfo::State& state) {
16 const auto ratio{1.0f / params.compressor_ratio}; 16 const auto ratio{1.0f / params.compressor_ratio};
17 auto makeup_gain{0.0f}; 17 auto makeup_gain{0.0f};
@@ -31,9 +31,9 @@ static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& para
31 state.unk_20 = c; 31 state.unk_20 = c;
32} 32}
33 33
34static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params, 34static void InitializeCompressorEffect(const CompressorInfo::ParameterVersion2& params,
35 CompressorInfo::State& state) { 35 CompressorInfo::State& state) {
36 std::memset(&state, 0, sizeof(CompressorInfo::State)); 36 state = {};
37 37
38 state.unk_00 = 0; 38 state.unk_00 = 0;
39 state.unk_04 = 1.0f; 39 state.unk_04 = 1.0f;
@@ -42,7 +42,7 @@ static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params
42 SetCompressorEffectParameter(params, state); 42 SetCompressorEffectParameter(params, state);
43} 43}
44 44
45static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params, 45static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& params,
46 CompressorInfo::State& state, bool enabled, 46 CompressorInfo::State& state, bool enabled,
47 std::vector<std::span<const s32>> input_buffers, 47 std::vector<std::span<const s32>> input_buffers,
48 std::vector<std::span<s32>> output_buffers, u32 sample_count) { 48 std::vector<std::span<s32>> output_buffers, u32 sample_count) {
@@ -103,8 +103,7 @@ static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params,
103 } else { 103 } else {
104 for (s16 channel = 0; channel < params.channel_count; channel++) { 104 for (s16 channel = 0; channel < params.channel_count; channel++) {
105 if (params.inputs[channel] != params.outputs[channel]) { 105 if (params.inputs[channel] != params.outputs[channel]) {
106 std::memcpy((char*)output_buffers[channel].data(), 106 std::memcpy(output_buffers[channel].data(), input_buffers[channel].data(),
107 (char*)input_buffers[channel].data(),
108 output_buffers[channel].size_bytes()); 107 output_buffers[channel].size_bytes());
109 } 108 }
110 } 109 }
diff --git a/src/audio_core/renderer/command/mix/mix_ramp.cpp b/src/audio_core/renderer/command/mix/mix_ramp.cpp
index ffdafa1c8..d67123cd8 100644
--- a/src/audio_core/renderer/command/mix/mix_ramp.cpp
+++ b/src/audio_core/renderer/command/mix/mix_ramp.cpp
@@ -7,17 +7,7 @@
7#include "common/logging/log.h" 7#include "common/logging/log.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::AudioRenderer {
10/** 10
11 * Mix input mix buffer into output mix buffer, with volume applied to the input.
12 *
13 * @tparam Q - Number of bits for fixed point operations.
14 * @param output - Output mix buffer.
15 * @param input - Input mix buffer.
16 * @param volume - Volume applied to the input.
17 * @param ramp - Ramp applied to volume every sample.
18 * @param sample_count - Number of samples to process.
19 * @return The final gained input sample, used for depopping.
20 */
21template <size_t Q> 11template <size_t Q>
22s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, 12s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_,
23 const f32 ramp_, const u32 sample_count) { 13 const f32 ramp_, const u32 sample_count) {
@@ -40,10 +30,8 @@ s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 vo
40 return sample.to_int(); 30 return sample.to_int();
41} 31}
42 32
43template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, const f32, const f32, 33template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, f32, f32, u32);
44 const u32); 34template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, f32, f32, u32);
45template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, const f32, const f32,
46 const u32);
47 35
48void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { 36void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
49 const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; 37 const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
diff --git a/src/audio_core/renderer/command/mix/mix_ramp.h b/src/audio_core/renderer/command/mix/mix_ramp.h
index 770f57e80..52f74a273 100644
--- a/src/audio_core/renderer/command/mix/mix_ramp.h
+++ b/src/audio_core/renderer/command/mix/mix_ramp.h
@@ -61,13 +61,13 @@ struct MixRampCommand : ICommand {
61 * @tparam Q - Number of bits for fixed point operations. 61 * @tparam Q - Number of bits for fixed point operations.
62 * @param output - Output mix buffer. 62 * @param output - Output mix buffer.
63 * @param input - Input mix buffer. 63 * @param input - Input mix buffer.
64 * @param volume - Volume applied to the input. 64 * @param volume_ - Volume applied to the input.
65 * @param ramp - Ramp applied to volume every sample. 65 * @param ramp_ - Ramp applied to volume every sample.
66 * @param sample_count - Number of samples to process. 66 * @param sample_count - Number of samples to process.
67 * @return The final gained input sample, used for depopping. 67 * @return The final gained input sample, used for depopping.
68 */ 68 */
69template <size_t Q> 69template <size_t Q>
70s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, 70s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, f32 volume_, f32 ramp_,
71 const f32 ramp_, const u32 sample_count); 71 u32 sample_count);
72 72
73} // namespace AudioCore::AudioRenderer 73} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.h b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h
index 027276e5a..3b0ce67ef 100644
--- a/src/audio_core/renderer/command/mix/mix_ramp_grouped.h
+++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h
@@ -50,9 +50,9 @@ struct MixRampGroupedCommand : ICommand {
50 std::array<s16, MaxMixBuffers> inputs; 50 std::array<s16, MaxMixBuffers> inputs;
51 /// Output mix buffer indexes for each mix buffer 51 /// Output mix buffer indexes for each mix buffer
52 std::array<s16, MaxMixBuffers> outputs; 52 std::array<s16, MaxMixBuffers> outputs;
53 /// Previous mix vloumes for each mix buffer 53 /// Previous mix volumes for each mix buffer
54 std::array<f32, MaxMixBuffers> prev_volumes; 54 std::array<f32, MaxMixBuffers> prev_volumes;
55 /// Current mix vloumes for each mix buffer 55 /// Current mix volumes for each mix buffer
56 std::array<f32, MaxMixBuffers> volumes; 56 std::array<f32, MaxMixBuffers> volumes;
57 /// Pointer to the previous sample buffer, used for depop 57 /// Pointer to the previous sample buffer, used for depop
58 CpuAddr previous_samples; 58 CpuAddr previous_samples;
diff --git a/src/audio_core/renderer/command/sink/device.cpp b/src/audio_core/renderer/command/sink/device.cpp
index 47e0c6722..e88372a75 100644
--- a/src/audio_core/renderer/command/sink/device.cpp
+++ b/src/audio_core/renderer/command/sink/device.cpp
@@ -46,6 +46,10 @@ void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
46 46
47 out_buffer.tag = reinterpret_cast<u64>(samples.data()); 47 out_buffer.tag = reinterpret_cast<u64>(samples.data());
48 stream->AppendBuffer(out_buffer, samples); 48 stream->AppendBuffer(out_buffer, samples);
49
50 if (stream->IsPaused()) {
51 stream->Start();
52 }
49} 53}
50 54
51bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { 55bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {
diff --git a/src/audio_core/renderer/effect/effect_context.h b/src/audio_core/renderer/effect/effect_context.h
index 85955bd9c..8f6d6e7d8 100644
--- a/src/audio_core/renderer/effect/effect_context.h
+++ b/src/audio_core/renderer/effect/effect_context.h
@@ -15,15 +15,15 @@ class EffectContext {
15public: 15public:
16 /** 16 /**
17 * Initialize the effect context 17 * Initialize the effect context
18 * @param effect_infos List of effect infos for this context 18 * @param effect_infos_ - List of effect infos for this context
19 * @param effect_count The number of effects in the list 19 * @param effect_count_ - The number of effects in the list
20 * @param result_states_cpu The workbuffer of result states for the CPU for this context 20 * @param result_states_cpu_ - The workbuffer of result states for the CPU for this context
21 * @param result_states_dsp The workbuffer of result states for the DSP for this context 21 * @param result_states_dsp_ - The workbuffer of result states for the DSP for this context
22 * @param state_count The number of result states 22 * @param dsp_state_count - The number of result states
23 */ 23 */
24 void Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_, 24 void Initialize(std::span<EffectInfoBase> effect_infos_, u32 effect_count_,
25 std::span<EffectResultState> result_states_cpu_, 25 std::span<EffectResultState> result_states_cpu_,
26 std::span<EffectResultState> result_states_dsp_, const size_t dsp_state_count); 26 std::span<EffectResultState> result_states_dsp_, size_t dsp_state_count);
27 27
28 /** 28 /**
29 * Get the EffectInfo for a given index 29 * Get the EffectInfo for a given index
diff --git a/src/audio_core/renderer/effect/effect_info_base.h b/src/audio_core/renderer/effect/effect_info_base.h
index 8c9583878..8525fde05 100644
--- a/src/audio_core/renderer/effect/effect_info_base.h
+++ b/src/audio_core/renderer/effect/effect_info_base.h
@@ -291,7 +291,7 @@ public:
291 * Update the info with new parameters, version 1. 291 * Update the info with new parameters, version 1.
292 * 292 *
293 * @param error_info - Used to write call result code. 293 * @param error_info - Used to write call result code.
294 * @param in_params - New parameters to update the info with. 294 * @param params - New parameters to update the info with.
295 * @param pool_mapper - Pool for mapping buffers. 295 * @param pool_mapper - Pool for mapping buffers.
296 */ 296 */
297 virtual void Update(BehaviorInfo::ErrorInfo& error_info, 297 virtual void Update(BehaviorInfo::ErrorInfo& error_info,
@@ -305,7 +305,7 @@ public:
305 * Update the info with new parameters, version 2. 305 * Update the info with new parameters, version 2.
306 * 306 *
307 * @param error_info - Used to write call result code. 307 * @param error_info - Used to write call result code.
308 * @param in_params - New parameters to update the info with. 308 * @param params - New parameters to update the info with.
309 * @param pool_mapper - Pool for mapping buffers. 309 * @param pool_mapper - Pool for mapping buffers.
310 */ 310 */
311 virtual void Update(BehaviorInfo::ErrorInfo& error_info, 311 virtual void Update(BehaviorInfo::ErrorInfo& error_info,
diff --git a/src/audio_core/renderer/memory/address_info.h b/src/audio_core/renderer/memory/address_info.h
index 4cfefea8e..bb5c930e1 100644
--- a/src/audio_core/renderer/memory/address_info.h
+++ b/src/audio_core/renderer/memory/address_info.h
@@ -19,8 +19,8 @@ public:
19 /** 19 /**
20 * Setup a new AddressInfo. 20 * Setup a new AddressInfo.
21 * 21 *
22 * @param cpu_address - The CPU address of this region. 22 * @param cpu_address_ - The CPU address of this region.
23 * @param size - The size of this region. 23 * @param size_ - The size of this region.
24 */ 24 */
25 void Setup(CpuAddr cpu_address_, u64 size_) { 25 void Setup(CpuAddr cpu_address_, u64 size_) {
26 cpu_address = cpu_address_; 26 cpu_address = cpu_address_;
@@ -42,7 +42,6 @@ public:
42 * Assign this region to a memory pool. 42 * Assign this region to a memory pool.
43 * 43 *
44 * @param memory_pool_ - Memory pool to assign. 44 * @param memory_pool_ - Memory pool to assign.
45 * @return The CpuAddr address of this region.
46 */ 45 */
47 void SetPool(MemoryPoolInfo* memory_pool_) { 46 void SetPool(MemoryPoolInfo* memory_pool_) {
48 memory_pool = memory_pool_; 47 memory_pool = memory_pool_;
diff --git a/src/audio_core/renderer/nodes/node_states.h b/src/audio_core/renderer/nodes/node_states.h
index a1e0958a2..c0fced56f 100644
--- a/src/audio_core/renderer/nodes/node_states.h
+++ b/src/audio_core/renderer/nodes/node_states.h
@@ -112,11 +112,11 @@ public:
112 /** 112 /**
113 * Initialize the node states. 113 * Initialize the node states.
114 * 114 *
115 * @param buffer - The workbuffer to use. Unused. 115 * @param buffer_ - The workbuffer to use. Unused.
116 * @param node_buffer_size - The size of the workbuffer. Unused. 116 * @param node_buffer_size - The size of the workbuffer. Unused.
117 * @param count - The number of nodes in the graph. 117 * @param count - The number of nodes in the graph.
118 */ 118 */
119 void Initialize(std::span<u8> nodes, u64 node_buffer_size, u32 count); 119 void Initialize(std::span<u8> buffer_, u64 node_buffer_size, u32 count);
120 120
121 /** 121 /**
122 * Sort the graph. Only calls DepthFirstSearch. 122 * Sort the graph. Only calls DepthFirstSearch.
diff --git a/src/audio_core/renderer/performance/performance_manager.h b/src/audio_core/renderer/performance/performance_manager.h
index b82176bef..b65caa9b6 100644
--- a/src/audio_core/renderer/performance/performance_manager.h
+++ b/src/audio_core/renderer/performance/performance_manager.h
@@ -73,7 +73,8 @@ public:
73 * Calculate the required size for the performance workbuffer. 73 * Calculate the required size for the performance workbuffer.
74 * 74 *
75 * @param behavior - Check which version is supported. 75 * @param behavior - Check which version is supported.
76 * @param params - Input parameters. 76 * @param params - Input parameters.
77 *
77 * @return Required workbuffer size. 78 * @return Required workbuffer size.
78 */ 79 */
79 static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame( 80 static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame(
@@ -104,7 +105,7 @@ public:
104 * @param workbuffer - Workbuffer to use for performance frames. 105 * @param workbuffer - Workbuffer to use for performance frames.
105 * @param workbuffer_size - Size of the workbuffer. 106 * @param workbuffer_size - Size of the workbuffer.
106 * @param params - Input parameters. 107 * @param params - Input parameters.
107 * @param behavior - Behaviour to check version and data format. 108 * @param behavior - Behaviour to check version and data format.
108 * @param memory_pool - Used to translate the workbuffer address for the DSP. 109 * @param memory_pool - Used to translate the workbuffer address for the DSP.
109 */ 110 */
110 virtual void Initialize(std::span<u8> workbuffer, u64 workbuffer_size, 111 virtual void Initialize(std::span<u8> workbuffer, u64 workbuffer_size,
@@ -160,7 +161,8 @@ public:
160 * workbuffer, to be written by the AudioRenderer. 161 * workbuffer, to be written by the AudioRenderer.
161 * 162 *
162 * @param addresses - Filled with pointers to the new detail, which should be passed 163 * @param addresses - Filled with pointers to the new detail, which should be passed
163 * to the AudioRenderer with Performance commands to be written. 164 * to the AudioRenderer with Performance commands to be written.
165 * @param detail_type - Performance detail type.
164 * @param entry_type - The type of this detail. See PerformanceEntryType 166 * @param entry_type - The type of this detail. See PerformanceEntryType
165 * @param node_id - Node id for this detail. 167 * @param node_id - Node id for this detail.
166 * @return True if a new detail was created and the offsets are valid, otherwise false. 168 * @return True if a new detail was created and the offsets are valid, otherwise false.
diff --git a/src/audio_core/renderer/system_manager.cpp b/src/audio_core/renderer/system_manager.cpp
index b326819ed..9c1331e19 100644
--- a/src/audio_core/renderer/system_manager.cpp
+++ b/src/audio_core/renderer/system_manager.cpp
@@ -15,17 +15,14 @@ MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager",
15 MP_RGB(60, 19, 97)); 15 MP_RGB(60, 19, 97));
16 16
17namespace AudioCore::AudioRenderer { 17namespace AudioCore::AudioRenderer {
18constexpr std::chrono::nanoseconds BaseRenderTime{5'000'000UL}; 18constexpr std::chrono::nanoseconds RENDER_TIME{5'000'000UL};
19constexpr std::chrono::nanoseconds RenderTimeOffset{400'000UL};
20 19
21SystemManager::SystemManager(Core::System& core_) 20SystemManager::SystemManager(Core::System& core_)
22 : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()}, 21 : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()},
23 thread_event{Core::Timing::CreateEvent( 22 thread_event{Core::Timing::CreateEvent(
24 "AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) { 23 "AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
25 return ThreadFunc2(time); 24 return ThreadFunc2(time);
26 })} { 25 })} {}
27 core.CoreTiming().RegisterPauseCallback([this](bool paused) { PauseCallback(paused); });
28}
29 26
30SystemManager::~SystemManager() { 27SystemManager::~SystemManager() {
31 Stop(); 28 Stop();
@@ -36,8 +33,8 @@ bool SystemManager::InitializeUnsafe() {
36 if (adsp.Start()) { 33 if (adsp.Start()) {
37 active = true; 34 active = true;
38 thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); }); 35 thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); });
39 core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), 36 core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), RENDER_TIME,
40 BaseRenderTime - RenderTimeOffset, thread_event); 37 thread_event);
41 } 38 }
42 } 39 }
43 40
@@ -121,42 +118,9 @@ void SystemManager::ThreadFunc() {
121} 118}
122 119
123std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) { 120std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) {
124 std::optional<std::chrono::nanoseconds> new_schedule_time{std::nullopt};
125 const auto queue_size{core.AudioCore().GetStreamQueue()};
126 switch (state) {
127 case StreamState::Filling:
128 if (queue_size >= 5) {
129 new_schedule_time = BaseRenderTime;
130 state = StreamState::Steady;
131 }
132 break;
133 case StreamState::Steady:
134 if (queue_size <= 2) {
135 new_schedule_time = BaseRenderTime - RenderTimeOffset;
136 state = StreamState::Filling;
137 } else if (queue_size > 5) {
138 new_schedule_time = BaseRenderTime + RenderTimeOffset;
139 state = StreamState::Draining;
140 }
141 break;
142 case StreamState::Draining:
143 if (queue_size <= 5) {
144 new_schedule_time = BaseRenderTime;
145 state = StreamState::Steady;
146 }
147 break;
148 }
149
150 update.store(true); 121 update.store(true);
151 update.notify_all(); 122 update.notify_all();
152 return new_schedule_time; 123 return std::nullopt;
153}
154
155void SystemManager::PauseCallback(bool paused) {
156 if (paused && core.IsPoweredOn() && core.IsShuttingDown()) {
157 update.store(true);
158 update.notify_all();
159 }
160} 124}
161 125
162} // namespace AudioCore::AudioRenderer 126} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/system_manager.h b/src/audio_core/renderer/system_manager.h
index 1291e9e0e..81457a3a1 100644
--- a/src/audio_core/renderer/system_manager.h
+++ b/src/audio_core/renderer/system_manager.h
@@ -73,13 +73,6 @@ private:
73 */ 73 */
74 std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time); 74 std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time);
75 75
76 /**
77 * Callback from core timing when pausing, used to detect shutdowns and stop ThreadFunc.
78 *
79 * @param paused - Are we pausing or resuming?
80 */
81 void PauseCallback(bool paused);
82
83 enum class StreamState { 76 enum class StreamState {
84 Filling, 77 Filling,
85 Steady, 78 Steady,
@@ -106,8 +99,6 @@ private:
106 std::shared_ptr<Core::Timing::EventType> thread_event; 99 std::shared_ptr<Core::Timing::EventType> thread_event;
107 /// Atomic for main thread to wait on 100 /// Atomic for main thread to wait on
108 std::atomic<bool> update{}; 101 std::atomic<bool> update{};
109 /// Current state of the streams
110 StreamState state{StreamState::Filling};
111}; 102};
112 103
113} // namespace AudioCore::AudioRenderer 104} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.h b/src/audio_core/renderer/upsampler/upsampler_manager.h
index 70cd42b08..83c697c0c 100644
--- a/src/audio_core/renderer/upsampler/upsampler_manager.h
+++ b/src/audio_core/renderer/upsampler/upsampler_manager.h
@@ -27,7 +27,7 @@ public:
27 /** 27 /**
28 * Free the given upsampler. 28 * Free the given upsampler.
29 * 29 *
30 * @param The upsampler to be freed. 30 * @param info The upsampler to be freed.
31 */ 31 */
32 void Free(UpsamplerInfo* info); 32 void Free(UpsamplerInfo* info);
33 33
diff --git a/src/audio_core/renderer/voice/voice_info.h b/src/audio_core/renderer/voice/voice_info.h
index 896723e0c..930180895 100644
--- a/src/audio_core/renderer/voice/voice_info.h
+++ b/src/audio_core/renderer/voice/voice_info.h
@@ -185,7 +185,8 @@ public:
185 /** 185 /**
186 * Does this voice ned an update? 186 * Does this voice ned an update?
187 * 187 *
188 * @param params - Input parametetrs to check matching. 188 * @param params - Input parameters to check matching.
189 *
189 * @return True if this voice needs an update, otherwise false. 190 * @return True if this voice needs an update, otherwise false.
190 */ 191 */
191 bool ShouldUpdateParameters(const InParameter& params) const; 192 bool ShouldUpdateParameters(const InParameter& params) const;
@@ -194,9 +195,9 @@ public:
194 * Update the parameters of this voice. 195 * Update the parameters of this voice.
195 * 196 *
196 * @param error_info - Output error code. 197 * @param error_info - Output error code.
197 * @param params - Input parametters to udpate from. 198 * @param params - Input parameters to update from.
198 * @param pool_mapper - Used to map buffers. 199 * @param pool_mapper - Used to map buffers.
199 * @param behavior - behavior to check supported features. 200 * @param behavior - behavior to check supported features.
200 */ 201 */
201 void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params, 202 void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params,
202 const PoolMapper& pool_mapper, const BehaviorInfo& behavior); 203 const PoolMapper& pool_mapper, const BehaviorInfo& behavior);
@@ -218,12 +219,12 @@ public:
218 /** 219 /**
219 * Update all wavebuffers. 220 * Update all wavebuffers.
220 * 221 *
221 * @param error_infos - Output 2D array of errors, 2 per wavebuffer. 222 * @param error_infos - Output 2D array of errors, 2 per wavebuffer.
222 * @param error_count - Number of errors provided. Unused. 223 * @param error_count - Number of errors provided. Unused.
223 * @param params - Input parametters to be used for the update. 224 * @param params - Input parameters to be used for the update.
224 * @param voice_states - The voice states for each channel in this voice to be updated. 225 * @param voice_states - The voice states for each channel in this voice to be updated.
225 * @param pool_mapper - Used to map the wavebuffers. 226 * @param pool_mapper - Used to map the wavebuffers.
226 * @param behavior - Used to check for supported features. 227 * @param behavior - Used to check for supported features.
227 */ 228 */
228 void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos, 229 void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos,
229 u32 error_count, const InParameter& params, 230 u32 error_count, const InParameter& params,
@@ -233,13 +234,13 @@ public:
233 /** 234 /**
234 * Update a wavebuffer. 235 * Update a wavebuffer.
235 * 236 *
236 * @param error_infos - Output array of errors. 237 * @param error_info - Output array of errors.
237 * @param wave_buffer - The wavebuffer to be updated. 238 * @param wave_buffer - The wavebuffer to be updated.
238 * @param wave_buffer_internal - Input parametters to be used for the update. 239 * @param wave_buffer_internal - Input parametters to be used for the update.
239 * @param sample_format - Sample format of the wavebuffer. 240 * @param sample_format - Sample format of the wavebuffer.
240 * @param valid - Is this wavebuffer valid? 241 * @param valid - Is this wavebuffer valid?
241 * @param pool_mapper - Used to map the wavebuffers. 242 * @param pool_mapper - Used to map the wavebuffers.
242 * @param behavior - Used to check for supported features. 243 * @param behavior - Used to check for supported features.
243 */ 244 */
244 void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer, 245 void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer,
245 const WaveBufferInternal& wave_buffer_internal, 246 const WaveBufferInternal& wave_buffer_internal,
@@ -276,7 +277,7 @@ public:
276 /** 277 /**
277 * Check if this voice has any mixing connections. 278 * Check if this voice has any mixing connections.
278 * 279 *
279 * @return True if this voice participes in mixing, otherwise false. 280 * @return True if this voice participates in mixing, otherwise false.
280 */ 281 */
281 bool HasAnyConnection() const; 282 bool HasAnyConnection() const;
282 283
@@ -301,7 +302,8 @@ public:
301 /** 302 /**
302 * Update this voice on command generation. 303 * Update this voice on command generation.
303 * 304 *
304 * @param voice_states - Voice states for these wavebuffers. 305 * @param voice_context - Voice context for these wavebuffers.
306 *
305 * @return True if this voice should be generated, otherwise false. 307 * @return True if this voice should be generated, otherwise false.
306 */ 308 */
307 bool UpdateForCommandGeneration(VoiceContext& voice_context); 309 bool UpdateForCommandGeneration(VoiceContext& voice_context);
diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp
index a4e28de6d..36b115ad6 100644
--- a/src/audio_core/sink/cubeb_sink.cpp
+++ b/src/audio_core/sink/cubeb_sink.cpp
@@ -1,21 +1,13 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <algorithm>
5#include <atomic>
6#include <span> 4#include <span>
5#include <vector>
7 6
8#include "audio_core/audio_core.h" 7#include "audio_core/common/common.h"
9#include "audio_core/audio_event.h"
10#include "audio_core/audio_manager.h"
11#include "audio_core/sink/cubeb_sink.h" 8#include "audio_core/sink/cubeb_sink.h"
12#include "audio_core/sink/sink_stream.h" 9#include "audio_core/sink/sink_stream.h"
13#include "common/assert.h"
14#include "common/fixed_point.h"
15#include "common/logging/log.h" 10#include "common/logging/log.h"
16#include "common/reader_writer_queue.h"
17#include "common/ring_buffer.h"
18#include "common/settings.h"
19#include "core/core.h" 11#include "core/core.h"
20 12
21#ifdef _WIN32 13#ifdef _WIN32
@@ -42,10 +34,10 @@ public:
42 * @param system_ - Core system. 34 * @param system_ - Core system.
43 * @param event - Event used only for audio renderer, signalled on buffer consume. 35 * @param event - Event used only for audio renderer, signalled on buffer consume.
44 */ 36 */
45 CubebSinkStream(cubeb* ctx_, const u32 device_channels_, const u32 system_channels_, 37 CubebSinkStream(cubeb* ctx_, u32 device_channels_, u32 system_channels_,
46 cubeb_devid output_device, cubeb_devid input_device, const std::string& name_, 38 cubeb_devid output_device, cubeb_devid input_device, const std::string& name_,
47 const StreamType type_, Core::System& system_) 39 StreamType type_, Core::System& system_)
48 : ctx{ctx_}, type{type_}, system{system_} { 40 : SinkStream(system_, type_), ctx{ctx_} {
49#ifdef _WIN32 41#ifdef _WIN32
50 CoInitializeEx(nullptr, COINIT_MULTITHREADED); 42 CoInitializeEx(nullptr, COINIT_MULTITHREADED);
51#endif 43#endif
@@ -79,12 +71,10 @@ public:
79 71
80 minimum_latency = std::max(minimum_latency, 256u); 72 minimum_latency = std::max(minimum_latency, 256u);
81 73
82 playing_buffer.consumed = true; 74 LOG_INFO(Service_Audio,
83 75 "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) "
84 LOG_DEBUG(Service_Audio, 76 "latency {}",
85 "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) " 77 name, type, params.rate, params.channels, system_channels, minimum_latency);
86 "latency {}",
87 name, type, params.rate, params.channels, system_channels, minimum_latency);
88 78
89 auto init_error{0}; 79 auto init_error{0};
90 if (type == StreamType::In) { 80 if (type == StreamType::In) {
@@ -111,6 +101,8 @@ public:
111 ~CubebSinkStream() override { 101 ~CubebSinkStream() override {
112 LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name); 102 LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name);
113 103
104 Unstall();
105
114 if (!ctx) { 106 if (!ctx) {
115 return; 107 return;
116 } 108 }
@@ -136,21 +128,14 @@ public:
136 * @param resume - Set to true if this is resuming the stream a previously-active stream. 128 * @param resume - Set to true if this is resuming the stream a previously-active stream.
137 * Default false. 129 * Default false.
138 */ 130 */
139 void Start(const bool resume = false) override { 131 void Start(bool resume = false) override {
140 if (!ctx) { 132 if (!ctx || !paused) {
141 return; 133 return;
142 } 134 }
143 135
144 if (resume && was_playing) { 136 paused = false;
145 if (cubeb_stream_start(stream_backend) != CUBEB_OK) { 137 if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
146 LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); 138 LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
147 }
148 paused = false;
149 } else if (!resume) {
150 if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
151 LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
152 }
153 paused = false;
154 } 139 }
155 } 140 }
156 141
@@ -158,204 +143,20 @@ public:
158 * Stop the sink stream. 143 * Stop the sink stream.
159 */ 144 */
160 void Stop() override { 145 void Stop() override {
161 if (!ctx) { 146 Unstall();
147
148 if (!ctx || paused) {
162 return; 149 return;
163 } 150 }
164 151
152 paused = true;
165 if (cubeb_stream_stop(stream_backend) != CUBEB_OK) { 153 if (cubeb_stream_stop(stream_backend) != CUBEB_OK) {
166 LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream"); 154 LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream");
167 } 155 }
168
169 was_playing.store(!paused);
170 paused = true;
171 }
172
173 /**
174 * Append a new buffer and its samples to a waiting queue to play.
175 *
176 * @param buffer - Audio buffer information to be queued.
177 * @param samples - The s16 samples to be queue for playback.
178 */
179 void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector<s16>& samples) override {
180 if (type == StreamType::In) {
181 queue.enqueue(buffer);
182 queued_buffers++;
183 } else {
184 constexpr s32 min{std::numeric_limits<s16>::min()};
185 constexpr s32 max{std::numeric_limits<s16>::max()};
186
187 auto yuzu_volume{Settings::Volume()};
188 auto volume{system_volume * device_volume * yuzu_volume};
189
190 if (system_channels == 6 && device_channels == 2) {
191 // We're given 6 channels, but our device only outputs 2, so downmix.
192 constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f};
193
194 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
195 read_index += system_channels, write_index += device_channels) {
196 const auto left_sample{
197 ((Common::FixedPoint<49, 15>(
198 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
199 down_mix_coeff[0] +
200 samples[read_index + static_cast<u32>(Channels::Center)] *
201 down_mix_coeff[1] +
202 samples[read_index + static_cast<u32>(Channels::LFE)] *
203 down_mix_coeff[2] +
204 samples[read_index + static_cast<u32>(Channels::BackLeft)] *
205 down_mix_coeff[3]) *
206 volume)
207 .to_int()};
208
209 const auto right_sample{
210 ((Common::FixedPoint<49, 15>(
211 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
212 down_mix_coeff[0] +
213 samples[read_index + static_cast<u32>(Channels::Center)] *
214 down_mix_coeff[1] +
215 samples[read_index + static_cast<u32>(Channels::LFE)] *
216 down_mix_coeff[2] +
217 samples[read_index + static_cast<u32>(Channels::BackRight)] *
218 down_mix_coeff[3]) *
219 volume)
220 .to_int()};
221
222 samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
223 static_cast<s16>(std::clamp(left_sample, min, max));
224 samples[write_index + static_cast<u32>(Channels::FrontRight)] =
225 static_cast<s16>(std::clamp(right_sample, min, max));
226 }
227
228 samples.resize(samples.size() / system_channels * device_channels);
229
230 } else if (system_channels == 2 && device_channels == 6) {
231 // We need moar samples! Not all games will provide 6 channel audio.
232 // TODO: Implement some upmixing here. Currently just passthrough, with other
233 // channels left as silence.
234 std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0);
235
236 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
237 read_index += system_channels, write_index += device_channels) {
238 const auto left_sample{static_cast<s16>(std::clamp(
239 static_cast<s32>(
240 static_cast<f32>(
241 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
242 volume),
243 min, max))};
244
245 new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
246
247 const auto right_sample{static_cast<s16>(std::clamp(
248 static_cast<s32>(
249 static_cast<f32>(
250 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
251 volume),
252 min, max))};
253
254 new_samples[write_index + static_cast<u32>(Channels::FrontRight)] =
255 right_sample;
256 }
257 samples = std::move(new_samples);
258
259 } else if (volume != 1.0f) {
260 for (u32 i = 0; i < samples.size(); i++) {
261 samples[i] = static_cast<s16>(std::clamp(
262 static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
263 }
264 }
265
266 samples_buffer.Push(samples);
267 queue.enqueue(buffer);
268 queued_buffers++;
269 }
270 }
271
272 /**
273 * Release a buffer. Audio In only, will fill a buffer with recorded samples.
274 *
275 * @param num_samples - Maximum number of samples to receive.
276 * @return Vector of recorded samples. May have fewer than num_samples.
277 */
278 std::vector<s16> ReleaseBuffer(const u64 num_samples) override {
279 static constexpr s32 min = std::numeric_limits<s16>::min();
280 static constexpr s32 max = std::numeric_limits<s16>::max();
281
282 auto samples{samples_buffer.Pop(num_samples)};
283
284 // TODO: Up-mix to 6 channels if the game expects it.
285 // For audio input this is unlikely to ever be the case though.
286
287 // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here.
288 // TODO: Play with this and find something that works better.
289 auto volume{system_volume * device_volume * 8};
290 for (u32 i = 0; i < samples.size(); i++) {
291 samples[i] = static_cast<s16>(
292 std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
293 }
294
295 if (samples.size() < num_samples) {
296 samples.resize(num_samples, 0);
297 }
298 return samples;
299 }
300
301 /**
302 * Check if a certain buffer has been consumed (fully played).
303 *
304 * @param tag - Unique tag of a buffer to check for.
305 * @return True if the buffer has been played, otherwise false.
306 */
307 bool IsBufferConsumed(const u64 tag) override {
308 if (released_buffer.tag == 0) {
309 if (!released_buffers.try_dequeue(released_buffer)) {
310 return false;
311 }
312 }
313
314 if (released_buffer.tag == tag) {
315 released_buffer.tag = 0;
316 return true;
317 }
318 return false;
319 }
320
321 /**
322 * Empty out the buffer queue.
323 */
324 void ClearQueue() override {
325 samples_buffer.Pop();
326 while (queue.pop()) {
327 }
328 while (released_buffers.pop()) {
329 }
330 queued_buffers = 0;
331 released_buffer = {};
332 playing_buffer = {};
333 playing_buffer.consumed = true;
334 } 156 }
335 157
336private: 158private:
337 /** 159 /**
338 * Signal events back to the audio system that a buffer was played/can be filled.
339 *
340 * @param buffer - Consumed audio buffer to be released.
341 */
342 void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) {
343 auto& manager{system.AudioCore().GetAudioManager()};
344 switch (type) {
345 case StreamType::Out:
346 released_buffers.enqueue(buffer);
347 manager.SetEvent(Event::Type::AudioOutManager, true);
348 break;
349 case StreamType::In:
350 released_buffers.enqueue(buffer);
351 manager.SetEvent(Event::Type::AudioInManager, true);
352 break;
353 case StreamType::Render:
354 break;
355 }
356 }
357
358 /**
359 * Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will 160 * Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will
360 * provide samples to be copied (audio in). 161 * provide samples to be copied (audio in).
361 * 162 *
@@ -375,106 +176,15 @@ private:
375 176
376 const std::size_t num_channels = impl->GetDeviceChannels(); 177 const std::size_t num_channels = impl->GetDeviceChannels();
377 const std::size_t frame_size = num_channels; 178 const std::size_t frame_size = num_channels;
378 const std::size_t frame_size_bytes = frame_size * sizeof(s16);
379 const std::size_t num_frames{static_cast<size_t>(num_frames_)}; 179 const std::size_t num_frames{static_cast<size_t>(num_frames_)};
380 size_t frames_written{0};
381 [[maybe_unused]] bool underrun{false};
382 180
383 if (impl->type == StreamType::In) { 181 if (impl->type == StreamType::In) {
384 // INPUT
385 std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff), 182 std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff),
386 num_frames * frame_size}; 183 num_frames * frame_size};
387 184 impl->ProcessAudioIn(input_buffer, num_frames);
388 while (frames_written < num_frames) {
389 auto& playing_buffer{impl->playing_buffer};
390
391 // If the playing buffer has been consumed or has no frames, we need a new one
392 if (playing_buffer.consumed || playing_buffer.frames == 0) {
393 if (!impl->queue.try_dequeue(impl->playing_buffer)) {
394 // If no buffer was available we've underrun, just push the samples and
395 // continue.
396 underrun = true;
397 impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
398 (num_frames - frames_written) * frame_size);
399 frames_written = num_frames;
400 continue;
401 } else {
402 // Successfully got a new buffer, mark the old one as consumed and signal.
403 impl->queued_buffers--;
404 impl->SignalEvent(impl->playing_buffer);
405 }
406 }
407
408 // Get the minimum frames available between the currently playing buffer, and the
409 // amount we have left to fill
410 size_t frames_available{
411 std::min(playing_buffer.frames - playing_buffer.frames_played,
412 num_frames - frames_written)};
413
414 impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
415 frames_available * frame_size);
416
417 frames_written += frames_available;
418 playing_buffer.frames_played += frames_available;
419
420 // If that's all the frames in the current buffer, add its samples and mark it as
421 // consumed
422 if (playing_buffer.frames_played >= playing_buffer.frames) {
423 impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
424 impl->playing_buffer.consumed = true;
425 }
426 }
427
428 std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size],
429 frame_size_bytes);
430 } else { 185 } else {
431 // OUTPUT
432 std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size}; 186 std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size};
433 187 impl->ProcessAudioOutAndRender(output_buffer, num_frames);
434 while (frames_written < num_frames) {
435 auto& playing_buffer{impl->playing_buffer};
436
437 // If the playing buffer has been consumed or has no frames, we need a new one
438 if (playing_buffer.consumed || playing_buffer.frames == 0) {
439 if (!impl->queue.try_dequeue(impl->playing_buffer)) {
440 // If no buffer was available we've underrun, fill the remaining buffer with
441 // the last written frame and continue.
442 underrun = true;
443 for (size_t i = frames_written; i < num_frames; i++) {
444 std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0],
445 frame_size_bytes);
446 }
447 frames_written = num_frames;
448 continue;
449 } else {
450 // Successfully got a new buffer, mark the old one as consumed and signal.
451 impl->queued_buffers--;
452 impl->SignalEvent(impl->playing_buffer);
453 }
454 }
455
456 // Get the minimum frames available between the currently playing buffer, and the
457 // amount we have left to fill
458 size_t frames_available{
459 std::min(playing_buffer.frames - playing_buffer.frames_played,
460 num_frames - frames_written)};
461
462 impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size],
463 frames_available * frame_size);
464
465 frames_written += frames_available;
466 playing_buffer.frames_played += frames_available;
467
468 // If that's all the frames in the current buffer, add its samples and mark it as
469 // consumed
470 if (playing_buffer.frames_played >= playing_buffer.frames) {
471 impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
472 impl->playing_buffer.consumed = true;
473 }
474 }
475
476 std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
477 frame_size_bytes);
478 } 188 }
479 189
480 return num_frames_; 190 return num_frames_;
@@ -487,32 +197,12 @@ private:
487 * @param user_data - Custom data pointer passed along, points to a CubebSinkStream. 197 * @param user_data - Custom data pointer passed along, points to a CubebSinkStream.
488 * @param state - New state of the device. 198 * @param state - New state of the device.
489 */ 199 */
490 static void StateCallback([[maybe_unused]] cubeb_stream* stream, 200 static void StateCallback(cubeb_stream*, void*, cubeb_state) {}
491 [[maybe_unused]] void* user_data,
492 [[maybe_unused]] cubeb_state state) {}
493 201
494 /// Main Cubeb context 202 /// Main Cubeb context
495 cubeb* ctx{}; 203 cubeb* ctx{};
496 /// Cubeb stream backend 204 /// Cubeb stream backend
497 cubeb_stream* stream_backend{}; 205 cubeb_stream* stream_backend{};
498 /// Name of this stream
499 std::string name{};
500 /// Type of this stream
501 StreamType type;
502 /// Core system
503 Core::System& system;
504 /// Ring buffer of the samples waiting to be played or consumed
505 Common::RingBuffer<s16, 0x10000> samples_buffer;
506 /// Audio buffers queued and waiting to play
507 Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue;
508 /// The currently-playing audio buffer
509 ::AudioCore::Sink::SinkBuffer playing_buffer{};
510 /// Audio buffers which have been played and are in queue to be released by the audio system
511 Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{};
512 /// Currently released buffer waiting to be taken by the audio system
513 ::AudioCore::Sink::SinkBuffer released_buffer{};
514 /// The last played (or received) frame of audio, used when the callback underruns
515 std::array<s16, MaxChannels> last_frame{};
516}; 206};
517 207
518CubebSink::CubebSink(std::string_view target_device_name) { 208CubebSink::CubebSink(std::string_view target_device_name) {
@@ -566,15 +256,15 @@ CubebSink::~CubebSink() {
566#endif 256#endif
567} 257}
568 258
569SinkStream* CubebSink::AcquireSinkStream(Core::System& system, const u32 system_channels, 259SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels,
570 const std::string& name, const StreamType type) { 260 const std::string& name, StreamType type) {
571 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>( 261 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>(
572 ctx, device_channels, system_channels, output_device, input_device, name, type, system)); 262 ctx, device_channels, system_channels, output_device, input_device, name, type, system));
573 263
574 return stream.get(); 264 return stream.get();
575} 265}
576 266
577void CubebSink::CloseStream(const SinkStream* stream) { 267void CubebSink::CloseStream(SinkStream* stream) {
578 for (size_t i = 0; i < sink_streams.size(); i++) { 268 for (size_t i = 0; i < sink_streams.size(); i++) {
579 if (sink_streams[i].get() == stream) { 269 if (sink_streams[i].get() == stream) {
580 sink_streams[i].reset(); 270 sink_streams[i].reset();
@@ -588,18 +278,6 @@ void CubebSink::CloseStreams() {
588 sink_streams.clear(); 278 sink_streams.clear();
589} 279}
590 280
591void CubebSink::PauseStreams() {
592 for (auto& stream : sink_streams) {
593 stream->Stop();
594 }
595}
596
597void CubebSink::UnpauseStreams() {
598 for (auto& stream : sink_streams) {
599 stream->Start(true);
600 }
601}
602
603f32 CubebSink::GetDeviceVolume() const { 281f32 CubebSink::GetDeviceVolume() const {
604 if (sink_streams.empty()) { 282 if (sink_streams.empty()) {
605 return 1.0f; 283 return 1.0f;
@@ -608,19 +286,19 @@ f32 CubebSink::GetDeviceVolume() const {
608 return sink_streams[0]->GetDeviceVolume(); 286 return sink_streams[0]->GetDeviceVolume();
609} 287}
610 288
611void CubebSink::SetDeviceVolume(const f32 volume) { 289void CubebSink::SetDeviceVolume(f32 volume) {
612 for (auto& stream : sink_streams) { 290 for (auto& stream : sink_streams) {
613 stream->SetDeviceVolume(volume); 291 stream->SetDeviceVolume(volume);
614 } 292 }
615} 293}
616 294
617void CubebSink::SetSystemVolume(const f32 volume) { 295void CubebSink::SetSystemVolume(f32 volume) {
618 for (auto& stream : sink_streams) { 296 for (auto& stream : sink_streams) {
619 stream->SetSystemVolume(volume); 297 stream->SetSystemVolume(volume);
620 } 298 }
621} 299}
622 300
623std::vector<std::string> ListCubebSinkDevices(const bool capture) { 301std::vector<std::string> ListCubebSinkDevices(bool capture) {
624 std::vector<std::string> device_list; 302 std::vector<std::string> device_list;
625 cubeb* ctx; 303 cubeb* ctx;
626 304
diff --git a/src/audio_core/sink/cubeb_sink.h b/src/audio_core/sink/cubeb_sink.h
index f0f43dfa1..4b0cb160d 100644
--- a/src/audio_core/sink/cubeb_sink.h
+++ b/src/audio_core/sink/cubeb_sink.h
@@ -34,8 +34,7 @@ public:
34 * May differ from the device's channel count. 34 * May differ from the device's channel count.
35 * @param name - Name of this stream. 35 * @param name - Name of this stream.
36 * @param type - Type of this stream, render/in/out. 36 * @param type - Type of this stream, render/in/out.
37 * @param event - Audio render only, a signal used to prevent the renderer running too 37 *
38 * fast.
39 * @return A pointer to the created SinkStream 38 * @return A pointer to the created SinkStream
40 */ 39 */
41 SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, 40 SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
@@ -46,7 +45,7 @@ public:
46 * 45 *
47 * @param stream - The stream to close. 46 * @param stream - The stream to close.
48 */ 47 */
49 void CloseStream(const SinkStream* stream) override; 48 void CloseStream(SinkStream* stream) override;
50 49
51 /** 50 /**
52 * Close all streams. 51 * Close all streams.
@@ -54,16 +53,6 @@ public:
54 void CloseStreams() override; 53 void CloseStreams() override;
55 54
56 /** 55 /**
57 * Pause all streams.
58 */
59 void PauseStreams() override;
60
61 /**
62 * Unpause all streams.
63 */
64 void UnpauseStreams() override;
65
66 /**
67 * Get the device volume. Set from calls to the IAudioDevice service. 56 * Get the device volume. Set from calls to the IAudioDevice service.
68 * 57 *
69 * @return Volume of the device. 58 * @return Volume of the device.
@@ -101,7 +90,7 @@ private:
101}; 90};
102 91
103/** 92/**
104 * Get a list of conencted devices from Cubeb. 93 * Get a list of connected devices from Cubeb.
105 * 94 *
106 * @param capture - Return input (capture) devices if true, otherwise output devices. 95 * @param capture - Return input (capture) devices if true, otherwise output devices.
107 */ 96 */
diff --git a/src/audio_core/sink/null_sink.h b/src/audio_core/sink/null_sink.h
index 47a342171..1215d3cd2 100644
--- a/src/audio_core/sink/null_sink.h
+++ b/src/audio_core/sink/null_sink.h
@@ -3,10 +3,29 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <string>
7#include <string_view>
8#include <vector>
9
6#include "audio_core/sink/sink.h" 10#include "audio_core/sink/sink.h"
7#include "audio_core/sink/sink_stream.h" 11#include "audio_core/sink/sink_stream.h"
8 12
13namespace Core {
14class System;
15} // namespace Core
16
9namespace AudioCore::Sink { 17namespace AudioCore::Sink {
18class NullSinkStreamImpl final : public SinkStream {
19public:
20 explicit NullSinkStreamImpl(Core::System& system_, StreamType type_)
21 : SinkStream{system_, type_} {}
22 ~NullSinkStreamImpl() override {}
23 void AppendBuffer(SinkBuffer&, std::vector<s16>&) override {}
24 std::vector<s16> ReleaseBuffer(u64) override {
25 return {};
26 }
27};
28
10/** 29/**
11 * A no-op sink for when no audio out is wanted. 30 * A no-op sink for when no audio out is wanted.
12 */ 31 */
@@ -15,17 +34,16 @@ public:
15 explicit NullSink(std::string_view) {} 34 explicit NullSink(std::string_view) {}
16 ~NullSink() override = default; 35 ~NullSink() override = default;
17 36
18 SinkStream* AcquireSinkStream([[maybe_unused]] Core::System& system, 37 SinkStream* AcquireSinkStream(Core::System& system, u32, const std::string&,
19 [[maybe_unused]] u32 system_channels, 38 StreamType type) override {
20 [[maybe_unused]] const std::string& name, 39 if (null_sink == nullptr) {
21 [[maybe_unused]] StreamType type) override { 40 null_sink = std::make_unique<NullSinkStreamImpl>(system, type);
22 return &null_sink_stream; 41 }
42 return null_sink.get();
23 } 43 }
24 44
25 void CloseStream([[maybe_unused]] const SinkStream* stream) override {} 45 void CloseStream(SinkStream*) override {}
26 void CloseStreams() override {} 46 void CloseStreams() override {}
27 void PauseStreams() override {}
28 void UnpauseStreams() override {}
29 f32 GetDeviceVolume() const override { 47 f32 GetDeviceVolume() const override {
30 return 1.0f; 48 return 1.0f;
31 } 49 }
@@ -33,20 +51,7 @@ public:
33 void SetSystemVolume(f32 volume) override {} 51 void SetSystemVolume(f32 volume) override {}
34 52
35private: 53private:
36 struct NullSinkStreamImpl final : SinkStream { 54 SinkStreamPtr null_sink{};
37 void Finalize() override {}
38 void Start(bool resume = false) override {}
39 void Stop() override {}
40 void AppendBuffer([[maybe_unused]] ::AudioCore::Sink::SinkBuffer& buffer,
41 [[maybe_unused]] std::vector<s16>& samples) override {}
42 std::vector<s16> ReleaseBuffer([[maybe_unused]] u64 num_samples) override {
43 return {};
44 }
45 bool IsBufferConsumed([[maybe_unused]] const u64 tag) {
46 return true;
47 }
48 void ClearQueue() override {}
49 } null_sink_stream;
50}; 55};
51 56
52} // namespace AudioCore::Sink 57} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp
index d6c9ec90d..1bd001b94 100644
--- a/src/audio_core/sink/sdl2_sink.cpp
+++ b/src/audio_core/sink/sdl2_sink.cpp
@@ -1,20 +1,13 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <algorithm> 4#include <span>
5#include <atomic> 5#include <vector>
6 6
7#include "audio_core/audio_core.h" 7#include "audio_core/common/common.h"
8#include "audio_core/audio_event.h"
9#include "audio_core/audio_manager.h"
10#include "audio_core/sink/sdl2_sink.h" 8#include "audio_core/sink/sdl2_sink.h"
11#include "audio_core/sink/sink_stream.h" 9#include "audio_core/sink/sink_stream.h"
12#include "common/assert.h"
13#include "common/fixed_point.h"
14#include "common/logging/log.h" 10#include "common/logging/log.h"
15#include "common/reader_writer_queue.h"
16#include "common/ring_buffer.h"
17#include "common/settings.h"
18#include "core/core.h" 11#include "core/core.h"
19 12
20// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307 13// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307
@@ -44,10 +37,9 @@ public:
44 * @param system_ - Core system. 37 * @param system_ - Core system.
45 * @param event - Event used only for audio renderer, signalled on buffer consume. 38 * @param event - Event used only for audio renderer, signalled on buffer consume.
46 */ 39 */
47 SDLSinkStream(u32 device_channels_, const u32 system_channels_, 40 SDLSinkStream(u32 device_channels_, u32 system_channels_, const std::string& output_device,
48 const std::string& output_device, const std::string& input_device, 41 const std::string& input_device, StreamType type_, Core::System& system_)
49 const StreamType type_, Core::System& system_) 42 : SinkStream{system_, type_} {
50 : type{type_}, system{system_} {
51 system_channels = system_channels_; 43 system_channels = system_channels_;
52 device_channels = device_channels_; 44 device_channels = device_channels_;
53 45
@@ -63,8 +55,6 @@ public:
63 spec.callback = &SDLSinkStream::DataCallback; 55 spec.callback = &SDLSinkStream::DataCallback;
64 spec.userdata = this; 56 spec.userdata = this;
65 57
66 playing_buffer.consumed = true;
67
68 std::string device_name{output_device}; 58 std::string device_name{output_device};
69 bool capture{false}; 59 bool capture{false};
70 if (type == StreamType::In) { 60 if (type == StreamType::In) {
@@ -84,31 +74,30 @@ public:
84 return; 74 return;
85 } 75 }
86 76
87 LOG_DEBUG(Service_Audio, 77 LOG_INFO(Service_Audio,
88 "Opening sdl stream {} with: rate {} channels {} (system channels {}) " 78 "Opening SDL stream {} with: rate {} channels {} (system channels {}) "
89 " samples {}", 79 " samples {}",
90 device, obtained.freq, obtained.channels, system_channels, obtained.samples); 80 device, obtained.freq, obtained.channels, system_channels, obtained.samples);
91 } 81 }
92 82
93 /** 83 /**
94 * Destroy the sink stream. 84 * Destroy the sink stream.
95 */ 85 */
96 ~SDLSinkStream() override { 86 ~SDLSinkStream() override {
97 if (device == 0) { 87 LOG_DEBUG(Service_Audio, "Destructing SDL stream {}", name);
98 return; 88 Finalize();
99 }
100
101 SDL_CloseAudioDevice(device);
102 } 89 }
103 90
104 /** 91 /**
105 * Finalize the sink stream. 92 * Finalize the sink stream.
106 */ 93 */
107 void Finalize() override { 94 void Finalize() override {
95 Unstall();
108 if (device == 0) { 96 if (device == 0) {
109 return; 97 return;
110 } 98 }
111 99
100 Stop();
112 SDL_CloseAudioDevice(device); 101 SDL_CloseAudioDevice(device);
113 } 102 }
114 103
@@ -118,217 +107,29 @@ public:
118 * @param resume - Set to true if this is resuming the stream a previously-active stream. 107 * @param resume - Set to true if this is resuming the stream a previously-active stream.
119 * Default false. 108 * Default false.
120 */ 109 */
121 void Start(const bool resume = false) override { 110 void Start(bool resume = false) override {
122 if (device == 0) { 111 if (device == 0 || !paused) {
123 return; 112 return;
124 } 113 }
125 114
126 if (resume && was_playing) { 115 paused = false;
127 SDL_PauseAudioDevice(device, 0); 116 SDL_PauseAudioDevice(device, 0);
128 paused = false;
129 } else if (!resume) {
130 SDL_PauseAudioDevice(device, 0);
131 paused = false;
132 }
133 } 117 }
134 118
135 /** 119 /**
136 * Stop the sink stream. 120 * Stop the sink stream.
137 */ 121 */
138 void Stop() { 122 void Stop() override {
139 if (device == 0) { 123 Unstall();
124 if (device == 0 || paused) {
140 return; 125 return;
141 } 126 }
142 SDL_PauseAudioDevice(device, 1);
143 paused = true; 127 paused = true;
144 } 128 SDL_PauseAudioDevice(device, 1);
145
146 /**
147 * Append a new buffer and its samples to a waiting queue to play.
148 *
149 * @param buffer - Audio buffer information to be queued.
150 * @param samples - The s16 samples to be queue for playback.
151 */
152 void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector<s16>& samples) override {
153 if (type == StreamType::In) {
154 queue.enqueue(buffer);
155 queued_buffers++;
156 } else {
157 constexpr s32 min = std::numeric_limits<s16>::min();
158 constexpr s32 max = std::numeric_limits<s16>::max();
159
160 auto yuzu_volume{Settings::Volume()};
161 auto volume{system_volume * device_volume * yuzu_volume};
162
163 if (system_channels == 6 && device_channels == 2) {
164 // We're given 6 channels, but our device only outputs 2, so downmix.
165 constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f};
166
167 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
168 read_index += system_channels, write_index += device_channels) {
169 const auto left_sample{
170 ((Common::FixedPoint<49, 15>(
171 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
172 down_mix_coeff[0] +
173 samples[read_index + static_cast<u32>(Channels::Center)] *
174 down_mix_coeff[1] +
175 samples[read_index + static_cast<u32>(Channels::LFE)] *
176 down_mix_coeff[2] +
177 samples[read_index + static_cast<u32>(Channels::BackLeft)] *
178 down_mix_coeff[3]) *
179 volume)
180 .to_int()};
181
182 const auto right_sample{
183 ((Common::FixedPoint<49, 15>(
184 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
185 down_mix_coeff[0] +
186 samples[read_index + static_cast<u32>(Channels::Center)] *
187 down_mix_coeff[1] +
188 samples[read_index + static_cast<u32>(Channels::LFE)] *
189 down_mix_coeff[2] +
190 samples[read_index + static_cast<u32>(Channels::BackRight)] *
191 down_mix_coeff[3]) *
192 volume)
193 .to_int()};
194
195 samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
196 static_cast<s16>(std::clamp(left_sample, min, max));
197 samples[write_index + static_cast<u32>(Channels::FrontRight)] =
198 static_cast<s16>(std::clamp(right_sample, min, max));
199 }
200
201 samples.resize(samples.size() / system_channels * device_channels);
202
203 } else if (system_channels == 2 && device_channels == 6) {
204 // We need moar samples! Not all games will provide 6 channel audio.
205 // TODO: Implement some upmixing here. Currently just passthrough, with other
206 // channels left as silence.
207 std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0);
208
209 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
210 read_index += system_channels, write_index += device_channels) {
211 const auto left_sample{static_cast<s16>(std::clamp(
212 static_cast<s32>(
213 static_cast<f32>(
214 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
215 volume),
216 min, max))};
217
218 new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
219
220 const auto right_sample{static_cast<s16>(std::clamp(
221 static_cast<s32>(
222 static_cast<f32>(
223 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
224 volume),
225 min, max))};
226
227 new_samples[write_index + static_cast<u32>(Channels::FrontRight)] =
228 right_sample;
229 }
230 samples = std::move(new_samples);
231
232 } else if (volume != 1.0f) {
233 for (u32 i = 0; i < samples.size(); i++) {
234 samples[i] = static_cast<s16>(std::clamp(
235 static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
236 }
237 }
238
239 samples_buffer.Push(samples);
240 queue.enqueue(buffer);
241 queued_buffers++;
242 }
243 }
244
245 /**
246 * Release a buffer. Audio In only, will fill a buffer with recorded samples.
247 *
248 * @param num_samples - Maximum number of samples to receive.
249 * @return Vector of recorded samples. May have fewer than num_samples.
250 */
251 std::vector<s16> ReleaseBuffer(const u64 num_samples) override {
252 static constexpr s32 min = std::numeric_limits<s16>::min();
253 static constexpr s32 max = std::numeric_limits<s16>::max();
254
255 auto samples{samples_buffer.Pop(num_samples)};
256
257 // TODO: Up-mix to 6 channels if the game expects it.
258 // For audio input this is unlikely to ever be the case though.
259
260 // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here.
261 // TODO: Play with this and find something that works better.
262 auto volume{system_volume * device_volume * 8};
263 for (u32 i = 0; i < samples.size(); i++) {
264 samples[i] = static_cast<s16>(
265 std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
266 }
267
268 if (samples.size() < num_samples) {
269 samples.resize(num_samples, 0);
270 }
271 return samples;
272 }
273
274 /**
275 * Check if a certain buffer has been consumed (fully played).
276 *
277 * @param tag - Unique tag of a buffer to check for.
278 * @return True if the buffer has been played, otherwise false.
279 */
280 bool IsBufferConsumed(const u64 tag) override {
281 if (released_buffer.tag == 0) {
282 if (!released_buffers.try_dequeue(released_buffer)) {
283 return false;
284 }
285 }
286
287 if (released_buffer.tag == tag) {
288 released_buffer.tag = 0;
289 return true;
290 }
291 return false;
292 }
293
294 /**
295 * Empty out the buffer queue.
296 */
297 void ClearQueue() override {
298 samples_buffer.Pop();
299 while (queue.pop()) {
300 }
301 while (released_buffers.pop()) {
302 }
303 released_buffer = {};
304 playing_buffer = {};
305 playing_buffer.consumed = true;
306 queued_buffers = 0;
307 } 129 }
308 130
309private: 131private:
310 /** 132 /**
311 * Signal events back to the audio system that a buffer was played/can be filled.
312 *
313 * @param buffer - Consumed audio buffer to be released.
314 */
315 void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) {
316 auto& manager{system.AudioCore().GetAudioManager()};
317 switch (type) {
318 case StreamType::Out:
319 released_buffers.enqueue(buffer);
320 manager.SetEvent(Event::Type::AudioOutManager, true);
321 break;
322 case StreamType::In:
323 released_buffers.enqueue(buffer);
324 manager.SetEvent(Event::Type::AudioInManager, true);
325 break;
326 case StreamType::Render:
327 break;
328 }
329 }
330
331 /**
332 * Main callback from SDL. Either expects samples from us (audio render/audio out), or will 133 * Main callback from SDL. Either expects samples from us (audio render/audio out), or will
333 * provide samples to be copied (audio in). 134 * provide samples to be copied (audio in).
334 * 135 *
@@ -345,122 +146,20 @@ private:
345 146
346 const std::size_t num_channels = impl->GetDeviceChannels(); 147 const std::size_t num_channels = impl->GetDeviceChannels();
347 const std::size_t frame_size = num_channels; 148 const std::size_t frame_size = num_channels;
348 const std::size_t frame_size_bytes = frame_size * sizeof(s16);
349 const std::size_t num_frames{len / num_channels / sizeof(s16)}; 149 const std::size_t num_frames{len / num_channels / sizeof(s16)};
350 size_t frames_written{0};
351 [[maybe_unused]] bool underrun{false};
352 150
353 if (impl->type == StreamType::In) { 151 if (impl->type == StreamType::In) {
354 std::span<s16> input_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size}; 152 std::span<const s16> input_buffer{reinterpret_cast<const s16*>(stream),
355 153 num_frames * frame_size};
356 while (frames_written < num_frames) { 154 impl->ProcessAudioIn(input_buffer, num_frames);
357 auto& playing_buffer{impl->playing_buffer};
358
359 // If the playing buffer has been consumed or has no frames, we need a new one
360 if (playing_buffer.consumed || playing_buffer.frames == 0) {
361 if (!impl->queue.try_dequeue(impl->playing_buffer)) {
362 // If no buffer was available we've underrun, just push the samples and
363 // continue.
364 underrun = true;
365 impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
366 (num_frames - frames_written) * frame_size);
367 frames_written = num_frames;
368 continue;
369 } else {
370 impl->queued_buffers--;
371 impl->SignalEvent(impl->playing_buffer);
372 }
373 }
374
375 // Get the minimum frames available between the currently playing buffer, and the
376 // amount we have left to fill
377 size_t frames_available{
378 std::min(playing_buffer.frames - playing_buffer.frames_played,
379 num_frames - frames_written)};
380
381 impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
382 frames_available * frame_size);
383
384 frames_written += frames_available;
385 playing_buffer.frames_played += frames_available;
386
387 // If that's all the frames in the current buffer, add its samples and mark it as
388 // consumed
389 if (playing_buffer.frames_played >= playing_buffer.frames) {
390 impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
391 impl->playing_buffer.consumed = true;
392 }
393 }
394
395 std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size],
396 frame_size_bytes);
397 } else { 155 } else {
398 std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size}; 156 std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size};
399 157 impl->ProcessAudioOutAndRender(output_buffer, num_frames);
400 while (frames_written < num_frames) {
401 auto& playing_buffer{impl->playing_buffer};
402
403 // If the playing buffer has been consumed or has no frames, we need a new one
404 if (playing_buffer.consumed || playing_buffer.frames == 0) {
405 if (!impl->queue.try_dequeue(impl->playing_buffer)) {
406 // If no buffer was available we've underrun, fill the remaining buffer with
407 // the last written frame and continue.
408 underrun = true;
409 for (size_t i = frames_written; i < num_frames; i++) {
410 std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0],
411 frame_size_bytes);
412 }
413 frames_written = num_frames;
414 continue;
415 } else {
416 impl->queued_buffers--;
417 impl->SignalEvent(impl->playing_buffer);
418 }
419 }
420
421 // Get the minimum frames available between the currently playing buffer, and the
422 // amount we have left to fill
423 size_t frames_available{
424 std::min(playing_buffer.frames - playing_buffer.frames_played,
425 num_frames - frames_written)};
426
427 impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size],
428 frames_available * frame_size);
429
430 frames_written += frames_available;
431 playing_buffer.frames_played += frames_available;
432
433 // If that's all the frames in the current buffer, add its samples and mark it as
434 // consumed
435 if (playing_buffer.frames_played >= playing_buffer.frames) {
436 impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
437 impl->playing_buffer.consumed = true;
438 }
439 }
440
441 std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
442 frame_size_bytes);
443 } 158 }
444 } 159 }
445 160
446 /// SDL device id of the opened input/output device 161 /// SDL device id of the opened input/output device
447 SDL_AudioDeviceID device{}; 162 SDL_AudioDeviceID device{};
448 /// Type of this stream
449 StreamType type;
450 /// Core system
451 Core::System& system;
452 /// Ring buffer of the samples waiting to be played or consumed
453 Common::RingBuffer<s16, 0x10000> samples_buffer;
454 /// Audio buffers queued and waiting to play
455 Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue;
456 /// The currently-playing audio buffer
457 ::AudioCore::Sink::SinkBuffer playing_buffer{};
458 /// Audio buffers which have been played and are in queue to be released by the audio system
459 Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{};
460 /// Currently released buffer waiting to be taken by the audio system
461 ::AudioCore::Sink::SinkBuffer released_buffer{};
462 /// The last played (or received) frame of audio, used when the callback underruns
463 std::array<s16, MaxChannels> last_frame{};
464}; 163};
465 164
466SDLSink::SDLSink(std::string_view target_device_name) { 165SDLSink::SDLSink(std::string_view target_device_name) {
@@ -482,14 +181,14 @@ SDLSink::SDLSink(std::string_view target_device_name) {
482 181
483SDLSink::~SDLSink() = default; 182SDLSink::~SDLSink() = default;
484 183
485SinkStream* SDLSink::AcquireSinkStream(Core::System& system, const u32 system_channels, 184SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels,
486 const std::string&, const StreamType type) { 185 const std::string&, StreamType type) {
487 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>( 186 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(
488 device_channels, system_channels, output_device, input_device, type, system)); 187 device_channels, system_channels, output_device, input_device, type, system));
489 return stream.get(); 188 return stream.get();
490} 189}
491 190
492void SDLSink::CloseStream(const SinkStream* stream) { 191void SDLSink::CloseStream(SinkStream* stream) {
493 for (size_t i = 0; i < sink_streams.size(); i++) { 192 for (size_t i = 0; i < sink_streams.size(); i++) {
494 if (sink_streams[i].get() == stream) { 193 if (sink_streams[i].get() == stream) {
495 sink_streams[i].reset(); 194 sink_streams[i].reset();
@@ -503,18 +202,6 @@ void SDLSink::CloseStreams() {
503 sink_streams.clear(); 202 sink_streams.clear();
504} 203}
505 204
506void SDLSink::PauseStreams() {
507 for (auto& stream : sink_streams) {
508 stream->Stop();
509 }
510}
511
512void SDLSink::UnpauseStreams() {
513 for (auto& stream : sink_streams) {
514 stream->Start();
515 }
516}
517
518f32 SDLSink::GetDeviceVolume() const { 205f32 SDLSink::GetDeviceVolume() const {
519 if (sink_streams.empty()) { 206 if (sink_streams.empty()) {
520 return 1.0f; 207 return 1.0f;
@@ -523,19 +210,19 @@ f32 SDLSink::GetDeviceVolume() const {
523 return sink_streams[0]->GetDeviceVolume(); 210 return sink_streams[0]->GetDeviceVolume();
524} 211}
525 212
526void SDLSink::SetDeviceVolume(const f32 volume) { 213void SDLSink::SetDeviceVolume(f32 volume) {
527 for (auto& stream : sink_streams) { 214 for (auto& stream : sink_streams) {
528 stream->SetDeviceVolume(volume); 215 stream->SetDeviceVolume(volume);
529 } 216 }
530} 217}
531 218
532void SDLSink::SetSystemVolume(const f32 volume) { 219void SDLSink::SetSystemVolume(f32 volume) {
533 for (auto& stream : sink_streams) { 220 for (auto& stream : sink_streams) {
534 stream->SetSystemVolume(volume); 221 stream->SetSystemVolume(volume);
535 } 222 }
536} 223}
537 224
538std::vector<std::string> ListSDLSinkDevices(const bool capture) { 225std::vector<std::string> ListSDLSinkDevices(bool capture) {
539 std::vector<std::string> device_list; 226 std::vector<std::string> device_list;
540 227
541 if (!SDL_WasInit(SDL_INIT_AUDIO)) { 228 if (!SDL_WasInit(SDL_INIT_AUDIO)) {
diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl2_sink.h
index 186bc2fa3..f01eddc1b 100644
--- a/src/audio_core/sink/sdl2_sink.h
+++ b/src/audio_core/sink/sdl2_sink.h
@@ -32,8 +32,7 @@ public:
32 * May differ from the device's channel count. 32 * May differ from the device's channel count.
33 * @param name - Name of this stream. 33 * @param name - Name of this stream.
34 * @param type - Type of this stream, render/in/out. 34 * @param type - Type of this stream, render/in/out.
35 * @param event - Audio render only, a signal used to prevent the renderer running too 35 *
36 * fast.
37 * @return A pointer to the created SinkStream 36 * @return A pointer to the created SinkStream
38 */ 37 */
39 SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, 38 SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
@@ -44,7 +43,7 @@ public:
44 * 43 *
45 * @param stream - The stream to close. 44 * @param stream - The stream to close.
46 */ 45 */
47 void CloseStream(const SinkStream* stream) override; 46 void CloseStream(SinkStream* stream) override;
48 47
49 /** 48 /**
50 * Close all streams. 49 * Close all streams.
@@ -52,16 +51,6 @@ public:
52 void CloseStreams() override; 51 void CloseStreams() override;
53 52
54 /** 53 /**
55 * Pause all streams.
56 */
57 void PauseStreams() override;
58
59 /**
60 * Unpause all streams.
61 */
62 void UnpauseStreams() override;
63
64 /**
65 * Get the device volume. Set from calls to the IAudioDevice service. 54 * Get the device volume. Set from calls to the IAudioDevice service.
66 * 55 *
67 * @return Volume of the device. 56 * @return Volume of the device.
@@ -92,7 +81,7 @@ private:
92}; 81};
93 82
94/** 83/**
95 * Get a list of conencted devices from Cubeb. 84 * Get a list of connected devices from SDL.
96 * 85 *
97 * @param capture - Return input (capture) devices if true, otherwise output devices. 86 * @param capture - Return input (capture) devices if true, otherwise output devices.
98 */ 87 */
diff --git a/src/audio_core/sink/sink.h b/src/audio_core/sink/sink.h
index 91fe455e4..f28c6d126 100644
--- a/src/audio_core/sink/sink.h
+++ b/src/audio_core/sink/sink.h
@@ -32,7 +32,7 @@ public:
32 * 32 *
33 * @param stream - The stream to close. 33 * @param stream - The stream to close.
34 */ 34 */
35 virtual void CloseStream(const SinkStream* stream) = 0; 35 virtual void CloseStream(SinkStream* stream) = 0;
36 36
37 /** 37 /**
38 * Close all streams. 38 * Close all streams.
@@ -40,16 +40,6 @@ public:
40 virtual void CloseStreams() = 0; 40 virtual void CloseStreams() = 0;
41 41
42 /** 42 /**
43 * Pause all streams.
44 */
45 virtual void PauseStreams() = 0;
46
47 /**
48 * Unpause all streams.
49 */
50 virtual void UnpauseStreams() = 0;
51
52 /**
53 * Create a new sink stream, kept within this sink, with a pointer returned for use. 43 * Create a new sink stream, kept within this sink, with a pointer returned for use.
54 * Do not free the returned pointer. When done with the stream, call CloseStream on the sink. 44 * Do not free the returned pointer. When done with the stream, call CloseStream on the sink.
55 * 45 *
@@ -58,8 +48,7 @@ public:
58 * May differ from the device's channel count. 48 * May differ from the device's channel count.
59 * @param name - Name of this stream. 49 * @param name - Name of this stream.
60 * @param type - Type of this stream, render/in/out. 50 * @param type - Type of this stream, render/in/out.
61 * @param event - Audio render only, a signal used to prevent the renderer running too 51 *
62 * fast.
63 * @return A pointer to the created SinkStream 52 * @return A pointer to the created SinkStream
64 */ 53 */
65 virtual SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, 54 virtual SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
diff --git a/src/audio_core/sink/sink_details.cpp b/src/audio_core/sink/sink_details.cpp
index 253c0fd1e..67bdab779 100644
--- a/src/audio_core/sink/sink_details.cpp
+++ b/src/audio_core/sink/sink_details.cpp
@@ -5,7 +5,7 @@
5#include <memory> 5#include <memory>
6#include <string> 6#include <string>
7#include <vector> 7#include <vector>
8#include "audio_core/sink/null_sink.h" 8
9#include "audio_core/sink/sink_details.h" 9#include "audio_core/sink/sink_details.h"
10#ifdef HAVE_CUBEB 10#ifdef HAVE_CUBEB
11#include "audio_core/sink/cubeb_sink.h" 11#include "audio_core/sink/cubeb_sink.h"
@@ -13,6 +13,7 @@
13#ifdef HAVE_SDL2 13#ifdef HAVE_SDL2
14#include "audio_core/sink/sdl2_sink.h" 14#include "audio_core/sink/sdl2_sink.h"
15#endif 15#endif
16#include "audio_core/sink/null_sink.h"
16#include "common/logging/log.h" 17#include "common/logging/log.h"
17 18
18namespace AudioCore::Sink { 19namespace AudioCore::Sink {
@@ -59,8 +60,7 @@ const SinkDetails& GetOutputSinkDetails(std::string_view sink_id) {
59 60
60 if (sink_id == "auto" || iter == std::end(sink_details)) { 61 if (sink_id == "auto" || iter == std::end(sink_details)) {
61 if (sink_id != "auto") { 62 if (sink_id != "auto") {
62 LOG_ERROR(Audio, "AudioCore::Sink::GetOutputSinkDetails given invalid sink_id {}", 63 LOG_ERROR(Audio, "Invalid sink_id {}", sink_id);
63 sink_id);
64 } 64 }
65 // Auto-select. 65 // Auto-select.
66 // sink_details is ordered in terms of desirability, with the best choice at the front. 66 // sink_details is ordered in terms of desirability, with the best choice at the front.
diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp
new file mode 100644
index 000000000..37fe725e4
--- /dev/null
+++ b/src/audio_core/sink/sink_stream.cpp
@@ -0,0 +1,279 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <array>
5#include <atomic>
6#include <memory>
7#include <span>
8#include <vector>
9
10#include "audio_core/audio_core.h"
11#include "audio_core/common/common.h"
12#include "audio_core/sink/sink_stream.h"
13#include "common/common_types.h"
14#include "common/fixed_point.h"
15#include "common/settings.h"
16#include "core/core.h"
17
18namespace AudioCore::Sink {
19
20void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
21 if (type == StreamType::In) {
22 queue.enqueue(buffer);
23 queued_buffers++;
24 return;
25 }
26
27 constexpr s32 min{std::numeric_limits<s16>::min()};
28 constexpr s32 max{std::numeric_limits<s16>::max()};
29
30 auto yuzu_volume{Settings::Volume()};
31 if (yuzu_volume > 1.0f) {
32 yuzu_volume = 0.6f + 20 * std::log10(yuzu_volume);
33 }
34 auto volume{system_volume * device_volume * yuzu_volume};
35
36 if (system_channels == 6 && device_channels == 2) {
37 // We're given 6 channels, but our device only outputs 2, so downmix.
38 constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f};
39
40 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
41 read_index += system_channels, write_index += device_channels) {
42 const auto left_sample{
43 ((Common::FixedPoint<49, 15>(
44 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
45 down_mix_coeff[0] +
46 samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] +
47 samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] +
48 samples[read_index + static_cast<u32>(Channels::BackLeft)] * down_mix_coeff[3]) *
49 volume)
50 .to_int()};
51
52 const auto right_sample{
53 ((Common::FixedPoint<49, 15>(
54 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
55 down_mix_coeff[0] +
56 samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] +
57 samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] +
58 samples[read_index + static_cast<u32>(Channels::BackRight)] * down_mix_coeff[3]) *
59 volume)
60 .to_int()};
61
62 samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
63 static_cast<s16>(std::clamp(left_sample, min, max));
64 samples[write_index + static_cast<u32>(Channels::FrontRight)] =
65 static_cast<s16>(std::clamp(right_sample, min, max));
66 }
67
68 samples.resize(samples.size() / system_channels * device_channels);
69
70 } else if (system_channels == 2 && device_channels == 6) {
71 // We need moar samples! Not all games will provide 6 channel audio.
72 // TODO: Implement some upmixing here. Currently just passthrough, with other
73 // channels left as silence.
74 std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0);
75
76 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
77 read_index += system_channels, write_index += device_channels) {
78 const auto left_sample{static_cast<s16>(std::clamp(
79 static_cast<s32>(
80 static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
81 volume),
82 min, max))};
83
84 new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
85
86 const auto right_sample{static_cast<s16>(std::clamp(
87 static_cast<s32>(
88 static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
89 volume),
90 min, max))};
91
92 new_samples[write_index + static_cast<u32>(Channels::FrontRight)] = right_sample;
93 }
94 samples = std::move(new_samples);
95
96 } else if (volume != 1.0f) {
97 for (u32 i = 0; i < samples.size(); i++) {
98 samples[i] = static_cast<s16>(
99 std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
100 }
101 }
102
103 samples_buffer.Push(samples);
104 queue.enqueue(buffer);
105 queued_buffers++;
106}
107
108std::vector<s16> SinkStream::ReleaseBuffer(u64 num_samples) {
109 constexpr s32 min = std::numeric_limits<s16>::min();
110 constexpr s32 max = std::numeric_limits<s16>::max();
111
112 auto samples{samples_buffer.Pop(num_samples)};
113
114 // TODO: Up-mix to 6 channels if the game expects it.
115 // For audio input this is unlikely to ever be the case though.
116
117 // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here.
118 // TODO: Play with this and find something that works better.
119 auto volume{system_volume * device_volume * 8};
120 for (u32 i = 0; i < samples.size(); i++) {
121 samples[i] = static_cast<s16>(
122 std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
123 }
124
125 if (samples.size() < num_samples) {
126 samples.resize(num_samples, 0);
127 }
128 return samples;
129}
130
131void SinkStream::ClearQueue() {
132 samples_buffer.Pop();
133 while (queue.pop()) {
134 }
135 queued_buffers = 0;
136 playing_buffer = {};
137 playing_buffer.consumed = true;
138}
139
140void SinkStream::ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames) {
141 const std::size_t num_channels = GetDeviceChannels();
142 const std::size_t frame_size = num_channels;
143 const std::size_t frame_size_bytes = frame_size * sizeof(s16);
144 size_t frames_written{0};
145
146 // If we're paused or going to shut down, we don't want to consume buffers as coretiming is
147 // paused and we'll desync, so just return.
148 if (system.IsPaused() || system.IsShuttingDown()) {
149 return;
150 }
151
152 if (queued_buffers > max_queue_size) {
153 Stall();
154 }
155
156 while (frames_written < num_frames) {
157 // If the playing buffer has been consumed or has no frames, we need a new one
158 if (playing_buffer.consumed || playing_buffer.frames == 0) {
159 if (!queue.try_dequeue(playing_buffer)) {
160 // If no buffer was available we've underrun, just push the samples and
161 // continue.
162 samples_buffer.Push(&input_buffer[frames_written * frame_size],
163 (num_frames - frames_written) * frame_size);
164 frames_written = num_frames;
165 continue;
166 }
167 // Successfully dequeued a new buffer.
168 queued_buffers--;
169 }
170
171 // Get the minimum frames available between the currently playing buffer, and the
172 // amount we have left to fill
173 size_t frames_available{std::min(playing_buffer.frames - playing_buffer.frames_played,
174 num_frames - frames_written)};
175
176 samples_buffer.Push(&input_buffer[frames_written * frame_size],
177 frames_available * frame_size);
178
179 frames_written += frames_available;
180 playing_buffer.frames_played += frames_available;
181
182 // If that's all the frames in the current buffer, add its samples and mark it as
183 // consumed
184 if (playing_buffer.frames_played >= playing_buffer.frames) {
185 playing_buffer.consumed = true;
186 }
187 }
188
189 std::memcpy(&last_frame[0], &input_buffer[(frames_written - 1) * frame_size], frame_size_bytes);
190
191 if (queued_buffers <= max_queue_size) {
192 Unstall();
193 }
194}
195
196void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames) {
197 const std::size_t num_channels = GetDeviceChannels();
198 const std::size_t frame_size = num_channels;
199 const std::size_t frame_size_bytes = frame_size * sizeof(s16);
200 size_t frames_written{0};
201
202 // If we're paused or going to shut down, we don't want to consume buffers as coretiming is
203 // paused and we'll desync, so just play silence.
204 if (system.IsPaused() || system.IsShuttingDown()) {
205 constexpr std::array<s16, 6> silence{};
206 for (size_t i = frames_written; i < num_frames; i++) {
207 std::memcpy(&output_buffer[i * frame_size], &silence[0], frame_size_bytes);
208 }
209 return;
210 }
211
212 // Due to many frames being queued up with nvdec (5 frames or so?), a lot of buffers also get
213 // queued up (30+) but not all at once, which causes constant stalling here, so just let the
214 // video play out without attempting to stall.
215 // Can hopefully remove this later with a more complete NVDEC implementation.
216 const auto nvdec_active{system.AudioCore().IsNVDECActive()};
217 if (!nvdec_active && queued_buffers > max_queue_size) {
218 Stall();
219 }
220
221 while (frames_written < num_frames) {
222 // If the playing buffer has been consumed or has no frames, we need a new one
223 if (playing_buffer.consumed || playing_buffer.frames == 0) {
224 if (!queue.try_dequeue(playing_buffer)) {
225 // If no buffer was available we've underrun, fill the remaining buffer with
226 // the last written frame and continue.
227 for (size_t i = frames_written; i < num_frames; i++) {
228 std::memcpy(&output_buffer[i * frame_size], &last_frame[0], frame_size_bytes);
229 }
230 frames_written = num_frames;
231 continue;
232 }
233 // Successfully dequeued a new buffer.
234 queued_buffers--;
235 }
236
237 // Get the minimum frames available between the currently playing buffer, and the
238 // amount we have left to fill
239 size_t frames_available{std::min(playing_buffer.frames - playing_buffer.frames_played,
240 num_frames - frames_written)};
241
242 samples_buffer.Pop(&output_buffer[frames_written * frame_size],
243 frames_available * frame_size);
244
245 frames_written += frames_available;
246 playing_buffer.frames_played += frames_available;
247
248 // If that's all the frames in the current buffer, add its samples and mark it as
249 // consumed
250 if (playing_buffer.frames_played >= playing_buffer.frames) {
251 playing_buffer.consumed = true;
252 }
253 }
254
255 std::memcpy(&last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
256 frame_size_bytes);
257
258 if (stalled && queued_buffers <= max_queue_size) {
259 Unstall();
260 }
261}
262
263void SinkStream::Stall() {
264 if (stalled) {
265 return;
266 }
267 stalled = true;
268 system.StallProcesses();
269}
270
271void SinkStream::Unstall() {
272 if (!stalled) {
273 return;
274 }
275 system.UnstallProcesses();
276 stalled = false;
277}
278
279} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sink_stream.h b/src/audio_core/sink/sink_stream.h
index 17ed6593f..9366ebbd3 100644
--- a/src/audio_core/sink/sink_stream.h
+++ b/src/audio_core/sink/sink_stream.h
@@ -3,12 +3,20 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <array>
6#include <atomic> 7#include <atomic>
7#include <memory> 8#include <memory>
9#include <span>
8#include <vector> 10#include <vector>
9 11
10#include "audio_core/common/common.h" 12#include "audio_core/common/common.h"
11#include "common/common_types.h" 13#include "common/common_types.h"
14#include "common/reader_writer_queue.h"
15#include "common/ring_buffer.h"
16
17namespace Core {
18class System;
19} // namespace Core
12 20
13namespace AudioCore::Sink { 21namespace AudioCore::Sink {
14 22
@@ -34,20 +42,24 @@ struct SinkBuffer {
34 * You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer 42 * You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer
35 * has been consumed. 43 * has been consumed.
36 * 44 *
37 * Since these are a FIFO queue, always check IsBufferConsumed in the same order you appended the 45 * Since these are a FIFO queue, IsBufferConsumed must be checked in the same order buffers were
38 * buffers, skipping a buffer will result in all following buffers to never release. 46 * appended, skipping a buffer will result in the queue getting stuck, and all following buffers to
47 * never release.
39 * 48 *
40 * If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this 49 * If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this
41 * is what games do), or call ClearQueue to flush all of the buffers without a full restart. 50 * is what games do), or call ClearQueue to flush all of the buffers without a full restart.
42 */ 51 */
43class SinkStream { 52class SinkStream {
44public: 53public:
45 virtual ~SinkStream() = default; 54 explicit SinkStream(Core::System& system_, StreamType type_) : system{system_}, type{type_} {}
55 virtual ~SinkStream() {
56 Unstall();
57 }
46 58
47 /** 59 /**
48 * Finalize the sink stream. 60 * Finalize the sink stream.
49 */ 61 */
50 virtual void Finalize() = 0; 62 virtual void Finalize() {}
51 63
52 /** 64 /**
53 * Start the sink stream. 65 * Start the sink stream.
@@ -55,48 +67,19 @@ public:
55 * @param resume - Set to true if this is resuming the stream a previously-active stream. 67 * @param resume - Set to true if this is resuming the stream a previously-active stream.
56 * Default false. 68 * Default false.
57 */ 69 */
58 virtual void Start(bool resume = false) = 0; 70 virtual void Start(bool resume = false) {}
59 71
60 /** 72 /**
61 * Stop the sink stream. 73 * Stop the sink stream.
62 */ 74 */
63 virtual void Stop() = 0; 75 virtual void Stop() {}
64
65 /**
66 * Append a new buffer and its samples to a waiting queue to play.
67 *
68 * @param buffer - Audio buffer information to be queued.
69 * @param samples - The s16 samples to be queue for playback.
70 */
71 virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) = 0;
72
73 /**
74 * Release a buffer. Audio In only, will fill a buffer with recorded samples.
75 *
76 * @param num_samples - Maximum number of samples to receive.
77 * @return Vector of recorded samples. May have fewer than num_samples.
78 */
79 virtual std::vector<s16> ReleaseBuffer(u64 num_samples) = 0;
80
81 /**
82 * Check if a certain buffer has been consumed (fully played).
83 *
84 * @param tag - Unique tag of a buffer to check for.
85 * @return True if the buffer has been played, otherwise false.
86 */
87 virtual bool IsBufferConsumed(u64 tag) = 0;
88
89 /**
90 * Empty out the buffer queue.
91 */
92 virtual void ClearQueue() = 0;
93 76
94 /** 77 /**
95 * Check if the stream is paused. 78 * Check if the stream is paused.
96 * 79 *
97 * @return True if paused, otherwise false. 80 * @return True if paused, otherwise false.
98 */ 81 */
99 bool IsPaused() { 82 bool IsPaused() const {
100 return paused; 83 return paused;
101 } 84 }
102 85
@@ -128,34 +111,6 @@ public:
128 } 111 }
129 112
130 /** 113 /**
131 * Get the total number of samples played by this stream.
132 *
133 * @return Number of samples played.
134 */
135 u64 GetPlayedSampleCount() const {
136 return played_sample_count;
137 }
138
139 /**
140 * Set the number of samples played.
141 * This is started and stopped on system start/stop.
142 *
143 * @param played_sample_count_ - Number of samples to set.
144 */
145 void SetPlayedSampleCount(u64 played_sample_count_) {
146 played_sample_count = played_sample_count_;
147 }
148
149 /**
150 * Add to the played sample count.
151 *
152 * @param num_samples - Number of samples to add.
153 */
154 void AddPlayedSampleCount(u64 num_samples) {
155 played_sample_count += num_samples;
156 }
157
158 /**
159 * Get the system volume. 114 * Get the system volume.
160 * 115 *
161 * @return The current system volume. 116 * @return The current system volume.
@@ -200,23 +155,93 @@ public:
200 return queued_buffers.load(); 155 return queued_buffers.load();
201 } 156 }
202 157
158 /**
159 * Set the maximum buffer queue size.
160 */
161 void SetRingSize(u32 ring_size) {
162 max_queue_size = ring_size;
163 }
164
165 /**
166 * Append a new buffer and its samples to a waiting queue to play.
167 *
168 * @param buffer - Audio buffer information to be queued.
169 * @param samples - The s16 samples to be queue for playback.
170 */
171 virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples);
172
173 /**
174 * Release a buffer. Audio In only, will fill a buffer with recorded samples.
175 *
176 * @param num_samples - Maximum number of samples to receive.
177 * @return Vector of recorded samples. May have fewer than num_samples.
178 */
179 virtual std::vector<s16> ReleaseBuffer(u64 num_samples);
180
181 /**
182 * Empty out the buffer queue.
183 */
184 void ClearQueue();
185
186 /**
187 * Callback for AudioIn.
188 *
189 * @param input_buffer - Input buffer to be filled with samples.
190 * @param num_frames - Number of frames to be filled.
191 */
192 void ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames);
193
194 /**
195 * Callback for AudioOut and AudioRenderer.
196 *
197 * @param output_buffer - Output buffer to be filled with samples.
198 * @param num_frames - Number of frames to be filled.
199 */
200 void ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames);
201
202 /**
203 * Stall core processes if the audio thread falls too far behind.
204 */
205 void Stall();
206
207 /**
208 * Unstall core processes.
209 */
210 void Unstall();
211
203protected: 212protected:
204 /// Number of buffers waiting to be played 213 /// Core system
205 std::atomic<u32> queued_buffers{}; 214 Core::System& system;
206 /// Total samples played by this stream 215 /// Type of this stream
207 std::atomic<u64> played_sample_count{}; 216 StreamType type;
208 /// Set by the audio render/in/out system which uses this stream 217 /// Set by the audio render/in/out system which uses this stream
209 f32 system_volume{1.0f};
210 /// Set via IAudioDevice service calls
211 f32 device_volume{1.0f};
212 /// Set by the audio render/in/out systen which uses this stream
213 u32 system_channels{2}; 218 u32 system_channels{2};
214 /// Channels supported by hardware 219 /// Channels supported by hardware
215 u32 device_channels{2}; 220 u32 device_channels{2};
216 /// Is this stream currently paused? 221 /// Is this stream currently paused?
217 std::atomic<bool> paused{true}; 222 std::atomic<bool> paused{true};
218 /// Was this stream previously playing? 223 /// Name of this stream
219 std::atomic<bool> was_playing{false}; 224 std::string name{};
225
226private:
227 /// Ring buffer of the samples waiting to be played or consumed
228 Common::RingBuffer<s16, 0x10000> samples_buffer;
229 /// Audio buffers queued and waiting to play
230 Common::ReaderWriterQueue<SinkBuffer> queue;
231 /// The currently-playing audio buffer
232 SinkBuffer playing_buffer{};
233 /// The last played (or received) frame of audio, used when the callback underruns
234 std::array<s16, MaxChannels> last_frame{};
235 /// Number of buffers waiting to be played
236 std::atomic<u32> queued_buffers{};
237 /// The ring size for audio out buffers (usually 4, rarely 2 or 8)
238 u32 max_queue_size{};
239 /// Set by the audio render/in/out system which uses this stream
240 f32 system_volume{1.0f};
241 /// Set via IAudioDevice service calls
242 f32 device_volume{1.0f};
243 /// True if coretiming has been stalled
244 bool stalled{false};
220}; 245};
221 246
222using SinkStreamPtr = std::unique_ptr<SinkStream>; 247using SinkStreamPtr = std::unique_ptr<SinkStream>;
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index a6dc31b53..b1e0ba6cc 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -124,6 +124,7 @@ add_library(common STATIC
124 settings.h 124 settings.h
125 settings_input.cpp 125 settings_input.cpp
126 settings_input.h 126 settings_input.h
127 socket_types.h
127 spin_lock.cpp 128 spin_lock.cpp
128 spin_lock.h 129 spin_lock.h
129 stream.cpp 130 stream.cpp
@@ -165,6 +166,7 @@ if(ARCHITECTURE_x86_64)
165 x64/xbyak_abi.h 166 x64/xbyak_abi.h
166 x64/xbyak_util.h 167 x64/xbyak_util.h
167 ) 168 )
169 target_link_libraries(common PRIVATE xbyak)
168endif() 170endif()
169 171
170if (MSVC) 172if (MSVC)
@@ -188,7 +190,7 @@ endif()
188create_target_directory_groups(common) 190create_target_directory_groups(common)
189 191
190target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile Threads::Threads) 192target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile Threads::Threads)
191target_link_libraries(common PRIVATE lz4::lz4 xbyak) 193target_link_libraries(common PRIVATE lz4::lz4)
192if (TARGET zstd::zstd) 194if (TARGET zstd::zstd)
193 target_link_libraries(common PRIVATE zstd::zstd) 195 target_link_libraries(common PRIVATE zstd::zstd)
194else() 196else()
diff --git a/src/common/announce_multiplayer_room.h b/src/common/announce_multiplayer_room.h
index 0ad9da2be..4a3100fa4 100644
--- a/src/common/announce_multiplayer_room.h
+++ b/src/common/announce_multiplayer_room.h
@@ -8,15 +8,15 @@
8#include <string> 8#include <string>
9#include <vector> 9#include <vector>
10#include "common/common_types.h" 10#include "common/common_types.h"
11#include "common/socket_types.h"
11#include "web_service/web_result.h" 12#include "web_service/web_result.h"
12 13
13namespace AnnounceMultiplayerRoom { 14namespace AnnounceMultiplayerRoom {
14 15
15using MacAddress = std::array<u8, 6>;
16
17struct GameInfo { 16struct GameInfo {
18 std::string name{""}; 17 std::string name{""};
19 u64 id{0}; 18 u64 id{0};
19 std::string version{""};
20}; 20};
21 21
22struct Member { 22struct Member {
@@ -24,7 +24,7 @@ struct Member {
24 std::string nickname; 24 std::string nickname;
25 std::string display_name; 25 std::string display_name;
26 std::string avatar_url; 26 std::string avatar_url;
27 MacAddress mac_address; 27 Network::IPv4Address fake_ip;
28 GameInfo game; 28 GameInfo game;
29}; 29};
30 30
@@ -75,10 +75,7 @@ public:
75 const bool has_password, const GameInfo& preferred_game) = 0; 75 const bool has_password, const GameInfo& preferred_game) = 0;
76 /** 76 /**
77 * Adds a player information to the data that gets announced 77 * Adds a player information to the data that gets announced
78 * @param nickname The nickname of the player 78 * @param member The player to add
79 * @param mac_address The MAC Address of the player
80 * @param game_id The title id of the game the player plays
81 * @param game_name The name of the game the player plays
82 */ 79 */
83 virtual void AddPlayer(const Member& member) = 0; 80 virtual void AddPlayer(const Member& member) = 0;
84 81
diff --git a/src/common/input.h b/src/common/input.h
index 213aa2384..825b0d650 100644
--- a/src/common/input.h
+++ b/src/common/input.h
@@ -102,6 +102,8 @@ struct AnalogProperties {
102 float offset{}; 102 float offset{};
103 // Invert direction of the sensor data 103 // Invert direction of the sensor data
104 bool inverted{}; 104 bool inverted{};
105 // Press once to activate, press again to release
106 bool toggle{};
105}; 107};
106 108
107// Single analog sensor data 109// Single analog sensor data
@@ -115,8 +117,11 @@ struct AnalogStatus {
115struct ButtonStatus { 117struct ButtonStatus {
116 Common::UUID uuid{}; 118 Common::UUID uuid{};
117 bool value{}; 119 bool value{};
120 // Invert value of the button
118 bool inverted{}; 121 bool inverted{};
122 // Press once to activate, press again to release
119 bool toggle{}; 123 bool toggle{};
124 // Internal lock for the toggle status
120 bool locked{}; 125 bool locked{};
121}; 126};
122 127
diff --git a/src/common/microprofile.h b/src/common/microprofile.h
index 91d14d5e1..56ef0a2dc 100644
--- a/src/common/microprofile.h
+++ b/src/common/microprofile.h
@@ -22,12 +22,3 @@ typedef void* HANDLE;
22#include <microprofile.h> 22#include <microprofile.h>
23 23
24#define MP_RGB(r, g, b) ((r) << 16 | (g) << 8 | (b) << 0) 24#define MP_RGB(r, g, b) ((r) << 16 | (g) << 8 | (b) << 0)
25
26// On OS X, some Mach header included by MicroProfile defines these as macros, conflicting with
27// identifiers we use.
28#ifdef PAGE_SIZE
29#undef PAGE_SIZE
30#endif
31#ifdef PAGE_MASK
32#undef PAGE_MASK
33#endif
diff --git a/src/common/parent_of_member.h b/src/common/parent_of_member.h
index 70b1c5624..8e03f17d8 100644
--- a/src/common/parent_of_member.h
+++ b/src/common/parent_of_member.h
@@ -11,7 +11,7 @@ namespace Common {
11namespace detail { 11namespace detail {
12template <typename T, size_t Size, size_t Align> 12template <typename T, size_t Size, size_t Align>
13struct TypedStorageImpl { 13struct TypedStorageImpl {
14 std::aligned_storage_t<Size, Align> storage_; 14 alignas(Align) u8 storage_[Size];
15}; 15};
16} // namespace detail 16} // namespace detail
17 17
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 1c7b6dfae..0a560ebb7 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -105,7 +105,7 @@ float Volume() {
105 if (values.audio_muted) { 105 if (values.audio_muted) {
106 return 0.0f; 106 return 0.0f;
107 } 107 }
108 return values.volume.GetValue() / 100.0f; 108 return values.volume.GetValue() / static_cast<f32>(values.volume.GetDefault());
109} 109}
110 110
111void UpdateRescalingInfo() { 111void UpdateRescalingInfo() {
@@ -195,6 +195,7 @@ void RestoreGlobalState(bool is_powered_on) {
195 values.shader_backend.SetGlobal(true); 195 values.shader_backend.SetGlobal(true);
196 values.use_asynchronous_shaders.SetGlobal(true); 196 values.use_asynchronous_shaders.SetGlobal(true);
197 values.use_fast_gpu_time.SetGlobal(true); 197 values.use_fast_gpu_time.SetGlobal(true);
198 values.use_pessimistic_flushes.SetGlobal(true);
198 values.bg_red.SetGlobal(true); 199 values.bg_red.SetGlobal(true);
199 values.bg_green.SetGlobal(true); 200 values.bg_green.SetGlobal(true);
200 values.bg_blue.SetGlobal(true); 201 values.bg_blue.SetGlobal(true);
diff --git a/src/common/settings.h b/src/common/settings.h
index 1079cf8cb..13651de57 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -374,7 +374,7 @@ struct Values {
374 Setting<std::string> audio_output_device_id{"auto", "output_device"}; 374 Setting<std::string> audio_output_device_id{"auto", "output_device"};
375 Setting<std::string> audio_input_device_id{"auto", "input_device"}; 375 Setting<std::string> audio_input_device_id{"auto", "input_device"};
376 Setting<bool> audio_muted{false, "audio_muted"}; 376 Setting<bool> audio_muted{false, "audio_muted"};
377 SwitchableSetting<u8, true> volume{100, 0, 100, "volume"}; 377 SwitchableSetting<u8, true> volume{100, 0, 200, "volume"};
378 Setting<bool> dump_audio_commands{false, "dump_audio_commands"}; 378 Setting<bool> dump_audio_commands{false, "dump_audio_commands"};
379 379
380 // Core 380 // Core
@@ -446,6 +446,7 @@ struct Values {
446 ShaderBackend::SPIRV, "shader_backend"}; 446 ShaderBackend::SPIRV, "shader_backend"};
447 SwitchableSetting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"}; 447 SwitchableSetting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"};
448 SwitchableSetting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"}; 448 SwitchableSetting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"};
449 SwitchableSetting<bool> use_pessimistic_flushes{false, "use_pessimistic_flushes"};
449 450
450 SwitchableSetting<u8> bg_red{0, "bg_red"}; 451 SwitchableSetting<u8> bg_red{0, "bg_red"};
451 SwitchableSetting<u8> bg_green{0, "bg_green"}; 452 SwitchableSetting<u8> bg_green{0, "bg_green"};
diff --git a/src/common/socket_types.h b/src/common/socket_types.h
new file mode 100644
index 000000000..0a801a443
--- /dev/null
+++ b/src/common/socket_types.h
@@ -0,0 +1,51 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/common_types.h"
7
8namespace Network {
9
10/// Address families
11enum class Domain : u8 {
12 INET, ///< Address family for IPv4
13};
14
15/// Socket types
16enum class Type {
17 STREAM,
18 DGRAM,
19 RAW,
20 SEQPACKET,
21};
22
23/// Protocol values for sockets
24enum class Protocol : u8 {
25 ICMP,
26 TCP,
27 UDP,
28};
29
30/// Shutdown mode
31enum class ShutdownHow {
32 RD,
33 WR,
34 RDWR,
35};
36
37/// Array of IPv4 address
38using IPv4Address = std::array<u8, 4>;
39
40/// Cross-platform sockaddr structure
41struct SockAddrIn {
42 Domain family;
43 IPv4Address ip;
44 u16 portno;
45};
46
47constexpr u32 FLAG_MSG_PEEK = 0x2;
48constexpr u32 FLAG_MSG_DONTWAIT = 0x80;
49constexpr u32 FLAG_O_NONBLOCK = 0x800;
50
51} // namespace Network
diff --git a/src/common/uint128.h b/src/common/uint128.h
index f890ffec2..f450a6db9 100644
--- a/src/common/uint128.h
+++ b/src/common/uint128.h
@@ -12,7 +12,6 @@
12#pragma intrinsic(_udiv128) 12#pragma intrinsic(_udiv128)
13#else 13#else
14#include <cstring> 14#include <cstring>
15#include <x86intrin.h>
16#endif 15#endif
17 16
18#include "common/common_types.h" 17#include "common/common_types.h"
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 40b1ea4a2..806e7ff6c 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -2,12 +2,8 @@
2# SPDX-License-Identifier: GPL-2.0-or-later 2# SPDX-License-Identifier: GPL-2.0-or-later
3 3
4add_library(core STATIC 4add_library(core STATIC
5 announce_multiplayer_session.cpp
6 announce_multiplayer_session.h
7 arm/arm_interface.h 5 arm/arm_interface.h
8 arm/arm_interface.cpp 6 arm/arm_interface.cpp
9 arm/cpu_interrupt_handler.cpp
10 arm/cpu_interrupt_handler.h
11 arm/dynarmic/arm_dynarmic_32.cpp 7 arm/dynarmic/arm_dynarmic_32.cpp
12 arm/dynarmic/arm_dynarmic_32.h 8 arm/dynarmic/arm_dynarmic_32.h
13 arm/dynarmic/arm_dynarmic_64.cpp 9 arm/dynarmic/arm_dynarmic_64.cpp
@@ -504,9 +500,10 @@ add_library(core STATIC
504 hle/service/jit/jit.h 500 hle/service/jit/jit.h
505 hle/service/lbl/lbl.cpp 501 hle/service/lbl/lbl.cpp
506 hle/service/lbl/lbl.h 502 hle/service/lbl/lbl.h
507 hle/service/ldn/errors.h 503 hle/service/ldn/ldn_results.h
508 hle/service/ldn/ldn.cpp 504 hle/service/ldn/ldn.cpp
509 hle/service/ldn/ldn.h 505 hle/service/ldn/ldn.h
506 hle/service/ldn/ldn_types.h
510 hle/service/ldr/ldr.cpp 507 hle/service/ldr/ldr.cpp
511 hle/service/ldr/ldr.h 508 hle/service/ldr/ldr.h
512 hle/service/lm/lm.cpp 509 hle/service/lm/lm.cpp
@@ -541,14 +538,14 @@ add_library(core STATIC
541 hle/service/npns/npns.cpp 538 hle/service/npns/npns.cpp
542 hle/service/npns/npns.h 539 hle/service/npns/npns.h
543 hle/service/ns/errors.h 540 hle/service/ns/errors.h
541 hle/service/ns/iplatform_service_manager.cpp
542 hle/service/ns/iplatform_service_manager.h
544 hle/service/ns/language.cpp 543 hle/service/ns/language.cpp
545 hle/service/ns/language.h 544 hle/service/ns/language.h
546 hle/service/ns/ns.cpp 545 hle/service/ns/ns.cpp
547 hle/service/ns/ns.h 546 hle/service/ns/ns.h
548 hle/service/ns/pdm_qry.cpp 547 hle/service/ns/pdm_qry.cpp
549 hle/service/ns/pdm_qry.h 548 hle/service/ns/pdm_qry.h
550 hle/service/ns/pl_u.cpp
551 hle/service/ns/pl_u.h
552 hle/service/nvdrv/devices/nvdevice.h 549 hle/service/nvdrv/devices/nvdevice.h
553 hle/service/nvdrv/devices/nvdisp_disp0.cpp 550 hle/service/nvdrv/devices/nvdisp_disp0.cpp
554 hle/service/nvdrv/devices/nvdisp_disp0.h 551 hle/service/nvdrv/devices/nvdisp_disp0.h
@@ -725,10 +722,10 @@ add_library(core STATIC
725 internal_network/network_interface.cpp 722 internal_network/network_interface.cpp
726 internal_network/network_interface.h 723 internal_network/network_interface.h
727 internal_network/sockets.h 724 internal_network/sockets.h
725 internal_network/socket_proxy.cpp
726 internal_network/socket_proxy.h
728 loader/deconstructed_rom_directory.cpp 727 loader/deconstructed_rom_directory.cpp
729 loader/deconstructed_rom_directory.h 728 loader/deconstructed_rom_directory.h
730 loader/elf.cpp
731 loader/elf.h
732 loader/kip.cpp 729 loader/kip.cpp
733 loader/kip.h 730 loader/kip.h
734 loader/loader.cpp 731 loader/loader.cpp
@@ -787,7 +784,7 @@ endif()
787create_target_directory_groups(core) 784create_target_directory_groups(core)
788 785
789target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core) 786target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core)
790target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::Opus) 787target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus)
791if (MINGW) 788if (MINGW)
792 target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) 789 target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})
793endif() 790endif()
diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h
index 73f259525..7d62d030e 100644
--- a/src/core/arm/arm_interface.h
+++ b/src/core/arm/arm_interface.h
@@ -27,7 +27,6 @@ namespace Core {
27class System; 27class System;
28class CPUInterruptHandler; 28class CPUInterruptHandler;
29 29
30using CPUInterrupts = std::array<CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>;
31using WatchpointArray = std::array<Kernel::DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS>; 30using WatchpointArray = std::array<Kernel::DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS>;
32 31
33/// Generic ARMv8 CPU interface 32/// Generic ARMv8 CPU interface
@@ -36,10 +35,8 @@ public:
36 YUZU_NON_COPYABLE(ARM_Interface); 35 YUZU_NON_COPYABLE(ARM_Interface);
37 YUZU_NON_MOVEABLE(ARM_Interface); 36 YUZU_NON_MOVEABLE(ARM_Interface);
38 37
39 explicit ARM_Interface(System& system_, CPUInterrupts& interrupt_handlers_, 38 explicit ARM_Interface(System& system_, bool uses_wall_clock_)
40 bool uses_wall_clock_) 39 : system{system_}, uses_wall_clock{uses_wall_clock_} {}
41 : system{system_}, interrupt_handlers{interrupt_handlers_}, uses_wall_clock{
42 uses_wall_clock_} {}
43 virtual ~ARM_Interface() = default; 40 virtual ~ARM_Interface() = default;
44 41
45 struct ThreadContext32 { 42 struct ThreadContext32 {
@@ -181,6 +178,9 @@ public:
181 /// Signal an interrupt and ask the core to halt as soon as possible. 178 /// Signal an interrupt and ask the core to halt as soon as possible.
182 virtual void SignalInterrupt() = 0; 179 virtual void SignalInterrupt() = 0;
183 180
181 /// Clear a previous interrupt.
182 virtual void ClearInterrupt() = 0;
183
184 struct BacktraceEntry { 184 struct BacktraceEntry {
185 std::string module; 185 std::string module;
186 u64 address; 186 u64 address;
@@ -208,7 +208,6 @@ public:
208protected: 208protected:
209 /// System context that this ARM interface is running under. 209 /// System context that this ARM interface is running under.
210 System& system; 210 System& system;
211 CPUInterrupts& interrupt_handlers;
212 const WatchpointArray* watchpoints; 211 const WatchpointArray* watchpoints;
213 bool uses_wall_clock; 212 bool uses_wall_clock;
214 213
diff --git a/src/core/arm/cpu_interrupt_handler.cpp b/src/core/arm/cpu_interrupt_handler.cpp
deleted file mode 100644
index 77b6194d7..000000000
--- a/src/core/arm/cpu_interrupt_handler.cpp
+++ /dev/null
@@ -1,24 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/thread.h"
5#include "core/arm/cpu_interrupt_handler.h"
6
7namespace Core {
8
9CPUInterruptHandler::CPUInterruptHandler() : interrupt_event{std::make_unique<Common::Event>()} {}
10
11CPUInterruptHandler::~CPUInterruptHandler() = default;
12
13void CPUInterruptHandler::SetInterrupt(bool is_interrupted_) {
14 if (is_interrupted_) {
15 interrupt_event->Set();
16 }
17 is_interrupted = is_interrupted_;
18}
19
20void CPUInterruptHandler::AwaitInterrupt() {
21 interrupt_event->Wait();
22}
23
24} // namespace Core
diff --git a/src/core/arm/cpu_interrupt_handler.h b/src/core/arm/cpu_interrupt_handler.h
deleted file mode 100644
index 286e12e53..000000000
--- a/src/core/arm/cpu_interrupt_handler.h
+++ /dev/null
@@ -1,39 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <atomic>
7#include <memory>
8
9namespace Common {
10class Event;
11}
12
13namespace Core {
14
15class CPUInterruptHandler {
16public:
17 CPUInterruptHandler();
18 ~CPUInterruptHandler();
19
20 CPUInterruptHandler(const CPUInterruptHandler&) = delete;
21 CPUInterruptHandler& operator=(const CPUInterruptHandler&) = delete;
22
23 CPUInterruptHandler(CPUInterruptHandler&&) = delete;
24 CPUInterruptHandler& operator=(CPUInterruptHandler&&) = delete;
25
26 bool IsInterrupted() const {
27 return is_interrupted;
28 }
29
30 void SetInterrupt(bool is_interrupted);
31
32 void AwaitInterrupt();
33
34private:
35 std::unique_ptr<Common::Event> interrupt_event;
36 std::atomic_bool is_interrupted{false};
37};
38
39} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
index b8d2ce224..d1e70f19d 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
@@ -11,7 +11,6 @@
11#include "common/logging/log.h" 11#include "common/logging/log.h"
12#include "common/page_table.h" 12#include "common/page_table.h"
13#include "common/settings.h" 13#include "common/settings.h"
14#include "core/arm/cpu_interrupt_handler.h"
15#include "core/arm/dynarmic/arm_dynarmic_32.h" 14#include "core/arm/dynarmic/arm_dynarmic_32.h"
16#include "core/arm/dynarmic/arm_dynarmic_cp15.h" 15#include "core/arm/dynarmic/arm_dynarmic_cp15.h"
17#include "core/arm/dynarmic/arm_exclusive_monitor.h" 16#include "core/arm/dynarmic/arm_exclusive_monitor.h"
@@ -125,7 +124,9 @@ public:
125 } 124 }
126 125
127 void AddTicks(u64 ticks) override { 126 void AddTicks(u64 ticks) override {
128 ASSERT_MSG(!parent.uses_wall_clock, "This should never happen - dynarmic ticking disabled"); 127 if (parent.uses_wall_clock) {
128 return;
129 }
129 130
130 // Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a 131 // Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
131 // rough approximation of the amount of executed ticks in the system, it may be thrown off 132 // rough approximation of the amount of executed ticks in the system, it may be thrown off
@@ -142,7 +143,12 @@ public:
142 } 143 }
143 144
144 u64 GetTicksRemaining() override { 145 u64 GetTicksRemaining() override {
145 ASSERT_MSG(!parent.uses_wall_clock, "This should never happen - dynarmic ticking disabled"); 146 if (parent.uses_wall_clock) {
147 if (!IsInterrupted()) {
148 return minimum_run_cycles;
149 }
150 return 0U;
151 }
146 152
147 return std::max<s64>(parent.system.CoreTiming().GetDowncount(), 0); 153 return std::max<s64>(parent.system.CoreTiming().GetDowncount(), 0);
148 } 154 }
@@ -168,11 +174,15 @@ public:
168 parent.jit.load()->HaltExecution(hr); 174 parent.jit.load()->HaltExecution(hr);
169 } 175 }
170 176
177 bool IsInterrupted() {
178 return parent.system.Kernel().PhysicalCore(parent.core_index).IsInterrupted();
179 }
180
171 ARM_Dynarmic_32& parent; 181 ARM_Dynarmic_32& parent;
172 Core::Memory::Memory& memory; 182 Core::Memory::Memory& memory;
173 std::size_t num_interpreted_instructions{}; 183 std::size_t num_interpreted_instructions{};
174 bool debugger_enabled{}; 184 bool debugger_enabled{};
175 static constexpr u64 minimum_run_cycles = 1000U; 185 static constexpr u64 minimum_run_cycles = 10000U;
176}; 186};
177 187
178std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable* page_table) const { 188std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable* page_table) const {
@@ -180,19 +190,21 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
180 config.callbacks = cb.get(); 190 config.callbacks = cb.get();
181 config.coprocessors[15] = cp15; 191 config.coprocessors[15] = cp15;
182 config.define_unpredictable_behaviour = true; 192 config.define_unpredictable_behaviour = true;
183 static constexpr std::size_t PAGE_BITS = 12; 193 static constexpr std::size_t YUZU_PAGEBITS = 12;
184 static constexpr std::size_t NUM_PAGE_TABLE_ENTRIES = 1 << (32 - PAGE_BITS); 194 static constexpr std::size_t NUM_PAGE_TABLE_ENTRIES = 1 << (32 - YUZU_PAGEBITS);
185 if (page_table) { 195 if (page_table) {
186 config.page_table = reinterpret_cast<std::array<std::uint8_t*, NUM_PAGE_TABLE_ENTRIES>*>( 196 config.page_table = reinterpret_cast<std::array<std::uint8_t*, NUM_PAGE_TABLE_ENTRIES>*>(
187 page_table->pointers.data()); 197 page_table->pointers.data());
198 config.absolute_offset_page_table = true;
199 config.page_table_pointer_mask_bits = Common::PageTable::ATTRIBUTE_BITS;
200 config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128;
201 config.only_detect_misalignment_via_page_table_on_page_boundary = true;
202
188 config.fastmem_pointer = page_table->fastmem_arena; 203 config.fastmem_pointer = page_table->fastmem_arena;
204
205 config.fastmem_exclusive_access = config.fastmem_pointer != nullptr;
206 config.recompile_on_exclusive_fastmem_failure = true;
189 } 207 }
190 config.absolute_offset_page_table = true;
191 config.page_table_pointer_mask_bits = Common::PageTable::ATTRIBUTE_BITS;
192 config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128;
193 config.only_detect_misalignment_via_page_table_on_page_boundary = true;
194 config.fastmem_exclusive_access = true;
195 config.recompile_on_exclusive_fastmem_failure = true;
196 208
197 // Multi-process state 209 // Multi-process state
198 config.processor_id = core_index; 210 config.processor_id = core_index;
@@ -200,7 +212,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
200 212
201 // Timing 213 // Timing
202 config.wall_clock_cntpct = uses_wall_clock; 214 config.wall_clock_cntpct = uses_wall_clock;
203 config.enable_cycle_counting = !uses_wall_clock; 215 config.enable_cycle_counting = true;
204 216
205 // Code cache size 217 // Code cache size
206 config.code_cache_size = 512_MiB; 218 config.code_cache_size = 512_MiB;
@@ -244,6 +256,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
244 } 256 }
245 if (!Settings::values.cpuopt_fastmem) { 257 if (!Settings::values.cpuopt_fastmem) {
246 config.fastmem_pointer = nullptr; 258 config.fastmem_pointer = nullptr;
259 config.fastmem_exclusive_access = false;
247 } 260 }
248 if (!Settings::values.cpuopt_fastmem_exclusives) { 261 if (!Settings::values.cpuopt_fastmem_exclusives) {
249 config.fastmem_exclusive_access = false; 262 config.fastmem_exclusive_access = false;
@@ -311,11 +324,9 @@ void ARM_Dynarmic_32::RewindBreakpointInstruction() {
311 LoadContext(breakpoint_context); 324 LoadContext(breakpoint_context);
312} 325}
313 326
314ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_, 327ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, bool uses_wall_clock_,
315 bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_, 328 ExclusiveMonitor& exclusive_monitor_, std::size_t core_index_)
316 std::size_t core_index_) 329 : ARM_Interface{system_, uses_wall_clock_}, cb(std::make_unique<DynarmicCallbacks32>(*this)),
317 : ARM_Interface{system_, interrupt_handlers_, uses_wall_clock_},
318 cb(std::make_unique<DynarmicCallbacks32>(*this)),
319 cp15(std::make_shared<DynarmicCP15>(*this)), core_index{core_index_}, 330 cp15(std::make_shared<DynarmicCP15>(*this)), core_index{core_index_},
320 exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor_)}, 331 exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor_)},
321 null_jit{MakeJit(nullptr)}, jit{null_jit.get()} {} 332 null_jit{MakeJit(nullptr)}, jit{null_jit.get()} {}
@@ -394,6 +405,10 @@ void ARM_Dynarmic_32::SignalInterrupt() {
394 jit.load()->HaltExecution(break_loop); 405 jit.load()->HaltExecution(break_loop);
395} 406}
396 407
408void ARM_Dynarmic_32::ClearInterrupt() {
409 jit.load()->ClearHalt(break_loop);
410}
411
397void ARM_Dynarmic_32::ClearInstructionCache() { 412void ARM_Dynarmic_32::ClearInstructionCache() {
398 jit.load()->ClearCache(); 413 jit.load()->ClearCache();
399} 414}
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.h b/src/core/arm/dynarmic/arm_dynarmic_32.h
index 346e9abf8..d24ba2289 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.h
@@ -28,8 +28,8 @@ class System;
28 28
29class ARM_Dynarmic_32 final : public ARM_Interface { 29class ARM_Dynarmic_32 final : public ARM_Interface {
30public: 30public:
31 ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_, bool uses_wall_clock_, 31 ARM_Dynarmic_32(System& system_, bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_,
32 ExclusiveMonitor& exclusive_monitor_, std::size_t core_index_); 32 std::size_t core_index_);
33 ~ARM_Dynarmic_32() override; 33 ~ARM_Dynarmic_32() override;
34 34
35 void SetPC(u64 pc) override; 35 void SetPC(u64 pc) override;
@@ -56,6 +56,7 @@ public:
56 void LoadContext(const ThreadContext64& ctx) override {} 56 void LoadContext(const ThreadContext64& ctx) override {}
57 57
58 void SignalInterrupt() override; 58 void SignalInterrupt() override;
59 void ClearInterrupt() override;
59 void ClearExclusiveState() override; 60 void ClearExclusiveState() override;
60 61
61 void ClearInstructionCache() override; 62 void ClearInstructionCache() override;
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
index 1a4d37cbc..1d46f6d40 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
@@ -10,7 +10,6 @@
10#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "common/page_table.h" 11#include "common/page_table.h"
12#include "common/settings.h" 12#include "common/settings.h"
13#include "core/arm/cpu_interrupt_handler.h"
14#include "core/arm/dynarmic/arm_dynarmic_64.h" 13#include "core/arm/dynarmic/arm_dynarmic_64.h"
15#include "core/arm/dynarmic/arm_exclusive_monitor.h" 14#include "core/arm/dynarmic/arm_exclusive_monitor.h"
16#include "core/core.h" 15#include "core/core.h"
@@ -166,7 +165,9 @@ public:
166 } 165 }
167 166
168 void AddTicks(u64 ticks) override { 167 void AddTicks(u64 ticks) override {
169 ASSERT_MSG(!parent.uses_wall_clock, "This should never happen - dynarmic ticking disabled"); 168 if (parent.uses_wall_clock) {
169 return;
170 }
170 171
171 // Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a 172 // Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
172 // rough approximation of the amount of executed ticks in the system, it may be thrown off 173 // rough approximation of the amount of executed ticks in the system, it may be thrown off
@@ -181,7 +182,12 @@ public:
181 } 182 }
182 183
183 u64 GetTicksRemaining() override { 184 u64 GetTicksRemaining() override {
184 ASSERT_MSG(!parent.uses_wall_clock, "This should never happen - dynarmic ticking disabled"); 185 if (parent.uses_wall_clock) {
186 if (!IsInterrupted()) {
187 return minimum_run_cycles;
188 }
189 return 0U;
190 }
185 191
186 return std::max<s64>(parent.system.CoreTiming().GetDowncount(), 0); 192 return std::max<s64>(parent.system.CoreTiming().GetDowncount(), 0);
187 } 193 }
@@ -211,12 +217,16 @@ public:
211 parent.jit.load()->HaltExecution(hr); 217 parent.jit.load()->HaltExecution(hr);
212 } 218 }
213 219
220 bool IsInterrupted() {
221 return parent.system.Kernel().PhysicalCore(parent.core_index).IsInterrupted();
222 }
223
214 ARM_Dynarmic_64& parent; 224 ARM_Dynarmic_64& parent;
215 Core::Memory::Memory& memory; 225 Core::Memory::Memory& memory;
216 u64 tpidrro_el0 = 0; 226 u64 tpidrro_el0 = 0;
217 u64 tpidr_el0 = 0; 227 u64 tpidr_el0 = 0;
218 bool debugger_enabled{}; 228 bool debugger_enabled{};
219 static constexpr u64 minimum_run_cycles = 1000U; 229 static constexpr u64 minimum_run_cycles = 10000U;
220}; 230};
221 231
222std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* page_table, 232std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* page_table,
@@ -240,7 +250,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
240 config.fastmem_address_space_bits = address_space_bits; 250 config.fastmem_address_space_bits = address_space_bits;
241 config.silently_mirror_fastmem = false; 251 config.silently_mirror_fastmem = false;
242 252
243 config.fastmem_exclusive_access = true; 253 config.fastmem_exclusive_access = config.fastmem_pointer != nullptr;
244 config.recompile_on_exclusive_fastmem_failure = true; 254 config.recompile_on_exclusive_fastmem_failure = true;
245 } 255 }
246 256
@@ -260,7 +270,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
260 270
261 // Timing 271 // Timing
262 config.wall_clock_cntpct = uses_wall_clock; 272 config.wall_clock_cntpct = uses_wall_clock;
263 config.enable_cycle_counting = !uses_wall_clock; 273 config.enable_cycle_counting = true;
264 274
265 // Code cache size 275 // Code cache size
266 config.code_cache_size = 512_MiB; 276 config.code_cache_size = 512_MiB;
@@ -304,6 +314,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
304 } 314 }
305 if (!Settings::values.cpuopt_fastmem) { 315 if (!Settings::values.cpuopt_fastmem) {
306 config.fastmem_pointer = nullptr; 316 config.fastmem_pointer = nullptr;
317 config.fastmem_exclusive_access = false;
307 } 318 }
308 if (!Settings::values.cpuopt_fastmem_exclusives) { 319 if (!Settings::values.cpuopt_fastmem_exclusives) {
309 config.fastmem_exclusive_access = false; 320 config.fastmem_exclusive_access = false;
@@ -371,10 +382,9 @@ void ARM_Dynarmic_64::RewindBreakpointInstruction() {
371 LoadContext(breakpoint_context); 382 LoadContext(breakpoint_context);
372} 383}
373 384
374ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_, 385ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, bool uses_wall_clock_,
375 bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_, 386 ExclusiveMonitor& exclusive_monitor_, std::size_t core_index_)
376 std::size_t core_index_) 387 : ARM_Interface{system_, uses_wall_clock_},
377 : ARM_Interface{system_, interrupt_handlers_, uses_wall_clock_},
378 cb(std::make_unique<DynarmicCallbacks64>(*this)), core_index{core_index_}, 388 cb(std::make_unique<DynarmicCallbacks64>(*this)), core_index{core_index_},
379 exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor_)}, 389 exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor_)},
380 null_jit{MakeJit(nullptr, 48)}, jit{null_jit.get()} {} 390 null_jit{MakeJit(nullptr, 48)}, jit{null_jit.get()} {}
@@ -461,6 +471,10 @@ void ARM_Dynarmic_64::SignalInterrupt() {
461 jit.load()->HaltExecution(break_loop); 471 jit.load()->HaltExecution(break_loop);
462} 472}
463 473
474void ARM_Dynarmic_64::ClearInterrupt() {
475 jit.load()->ClearHalt(break_loop);
476}
477
464void ARM_Dynarmic_64::ClearInstructionCache() { 478void ARM_Dynarmic_64::ClearInstructionCache() {
465 jit.load()->ClearCache(); 479 jit.load()->ClearCache();
466} 480}
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.h b/src/core/arm/dynarmic/arm_dynarmic_64.h
index c77a83ad7..ed1a5eb96 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.h
@@ -20,14 +20,13 @@ class Memory;
20namespace Core { 20namespace Core {
21 21
22class DynarmicCallbacks64; 22class DynarmicCallbacks64;
23class CPUInterruptHandler;
24class DynarmicExclusiveMonitor; 23class DynarmicExclusiveMonitor;
25class System; 24class System;
26 25
27class ARM_Dynarmic_64 final : public ARM_Interface { 26class ARM_Dynarmic_64 final : public ARM_Interface {
28public: 27public:
29 ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_, bool uses_wall_clock_, 28 ARM_Dynarmic_64(System& system_, bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_,
30 ExclusiveMonitor& exclusive_monitor_, std::size_t core_index_); 29 std::size_t core_index_);
31 ~ARM_Dynarmic_64() override; 30 ~ARM_Dynarmic_64() override;
32 31
33 void SetPC(u64 pc) override; 32 void SetPC(u64 pc) override;
@@ -50,6 +49,7 @@ public:
50 void LoadContext(const ThreadContext64& ctx) override; 49 void LoadContext(const ThreadContext64& ctx) override;
51 50
52 void SignalInterrupt() override; 51 void SignalInterrupt() override;
52 void ClearInterrupt() override;
53 void ClearExclusiveState() override; 53 void ClearExclusiveState() override;
54 54
55 void ClearInstructionCache() override; 55 void ClearInstructionCache() override;
diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
index e9123c13d..200efe4db 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
@@ -8,6 +8,10 @@
8#include "core/core.h" 8#include "core/core.h"
9#include "core/core_timing.h" 9#include "core/core_timing.h"
10 10
11#ifdef _MSC_VER
12#include <intrin.h>
13#endif
14
11using Callback = Dynarmic::A32::Coprocessor::Callback; 15using Callback = Dynarmic::A32::Coprocessor::Callback;
12using CallbackOrAccessOneWord = Dynarmic::A32::Coprocessor::CallbackOrAccessOneWord; 16using CallbackOrAccessOneWord = Dynarmic::A32::Coprocessor::CallbackOrAccessOneWord;
13using CallbackOrAccessTwoWords = Dynarmic::A32::Coprocessor::CallbackOrAccessTwoWords; 17using CallbackOrAccessTwoWords = Dynarmic::A32::Coprocessor::CallbackOrAccessTwoWords;
@@ -47,12 +51,31 @@ CallbackOrAccessOneWord DynarmicCP15::CompileSendOneWord(bool two, unsigned opc1
47 switch (opc2) { 51 switch (opc2) {
48 case 4: 52 case 4:
49 // CP15_DATA_SYNC_BARRIER 53 // CP15_DATA_SYNC_BARRIER
50 // This is a dummy write, we ignore the value written here. 54 return Callback{
51 return &dummy_value; 55 [](Dynarmic::A32::Jit*, void*, std::uint32_t, std::uint32_t) -> std::uint64_t {
56#ifdef _MSC_VER
57 _mm_mfence();
58 _mm_lfence();
59#else
60 asm volatile("mfence\n\tlfence\n\t" : : : "memory");
61#endif
62 return 0;
63 },
64 std::nullopt,
65 };
52 case 5: 66 case 5:
53 // CP15_DATA_MEMORY_BARRIER 67 // CP15_DATA_MEMORY_BARRIER
54 // This is a dummy write, we ignore the value written here. 68 return Callback{
55 return &dummy_value; 69 [](Dynarmic::A32::Jit*, void*, std::uint32_t, std::uint32_t) -> std::uint64_t {
70#ifdef _MSC_VER
71 _mm_mfence();
72#else
73 asm volatile("mfence\n\t" : : : "memory");
74#endif
75 return 0;
76 },
77 std::nullopt,
78 };
56 } 79 }
57 } 80 }
58 81
diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.h b/src/core/arm/dynarmic/arm_dynarmic_cp15.h
index 5b2a51636..d90b3e568 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_cp15.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.h
@@ -35,6 +35,8 @@ public:
35 ARM_Dynarmic_32& parent; 35 ARM_Dynarmic_32& parent;
36 u32 uprw = 0; 36 u32 uprw = 0;
37 u32 uro = 0; 37 u32 uro = 0;
38
39 friend class ARM_Dynarmic_32;
38}; 40};
39 41
40} // namespace Core 42} // namespace Core
diff --git a/src/core/core.cpp b/src/core/core.cpp
index ea32a4a8d..121092868 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -141,8 +141,6 @@ struct System::Impl {
141 core_timing.SyncPause(false); 141 core_timing.SyncPause(false);
142 is_paused = false; 142 is_paused = false;
143 143
144 audio_core->PauseSinks(false);
145
146 return status; 144 return status;
147 } 145 }
148 146
@@ -150,8 +148,6 @@ struct System::Impl {
150 std::unique_lock<std::mutex> lk(suspend_guard); 148 std::unique_lock<std::mutex> lk(suspend_guard);
151 status = SystemResultStatus::Success; 149 status = SystemResultStatus::Success;
152 150
153 audio_core->PauseSinks(true);
154
155 core_timing.SyncPause(true); 151 core_timing.SyncPause(true);
156 kernel.Suspend(true); 152 kernel.Suspend(true);
157 is_paused = true; 153 is_paused = true;
@@ -319,10 +315,19 @@ struct System::Impl {
319 if (app_loader->ReadTitle(name) != Loader::ResultStatus::Success) { 315 if (app_loader->ReadTitle(name) != Loader::ResultStatus::Success) {
320 LOG_ERROR(Core, "Failed to read title for ROM (Error {})", load_result); 316 LOG_ERROR(Core, "Failed to read title for ROM (Error {})", load_result);
321 } 317 }
318
319 std::string title_version;
320 const FileSys::PatchManager pm(program_id, system.GetFileSystemController(),
321 system.GetContentProvider());
322 const auto metadata = pm.GetControlMetadata();
323 if (metadata.first != nullptr) {
324 title_version = metadata.first->GetVersionString();
325 }
322 if (auto room_member = room_network.GetRoomMember().lock()) { 326 if (auto room_member = room_network.GetRoomMember().lock()) {
323 Network::GameInfo game_info; 327 Network::GameInfo game_info;
324 game_info.name = name; 328 game_info.name = name;
325 game_info.id = program_id; 329 game_info.id = program_id;
330 game_info.version = title_version;
326 room_member->SendGameInfo(game_info); 331 room_member->SendGameInfo(game_info);
327 } 332 }
328 333
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index 2dbb99c8b..5375a5d59 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -73,7 +73,6 @@ void CoreTiming::Shutdown() {
73 if (timer_thread) { 73 if (timer_thread) {
74 timer_thread->join(); 74 timer_thread->join();
75 } 75 }
76 pause_callbacks.clear();
77 ClearPendingEvents(); 76 ClearPendingEvents();
78 timer_thread.reset(); 77 timer_thread.reset();
79 has_started = false; 78 has_started = false;
@@ -86,10 +85,6 @@ void CoreTiming::Pause(bool is_paused) {
86 if (!is_paused) { 85 if (!is_paused) {
87 pause_end_time = GetGlobalTimeNs().count(); 86 pause_end_time = GetGlobalTimeNs().count();
88 } 87 }
89
90 for (auto& cb : pause_callbacks) {
91 cb(is_paused);
92 }
93} 88}
94 89
95void CoreTiming::SyncPause(bool is_paused) { 90void CoreTiming::SyncPause(bool is_paused) {
@@ -110,10 +105,6 @@ void CoreTiming::SyncPause(bool is_paused) {
110 if (!is_paused) { 105 if (!is_paused) {
111 pause_end_time = GetGlobalTimeNs().count(); 106 pause_end_time = GetGlobalTimeNs().count();
112 } 107 }
113
114 for (auto& cb : pause_callbacks) {
115 cb(is_paused);
116 }
117} 108}
118 109
119bool CoreTiming::IsRunning() const { 110bool CoreTiming::IsRunning() const {
@@ -219,11 +210,6 @@ void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
219 } 210 }
220} 211}
221 212
222void CoreTiming::RegisterPauseCallback(PauseCallback&& callback) {
223 std::scoped_lock lock{basic_lock};
224 pause_callbacks.emplace_back(std::move(callback));
225}
226
227std::optional<s64> CoreTiming::Advance() { 213std::optional<s64> CoreTiming::Advance() {
228 std::scoped_lock lock{advance_lock, basic_lock}; 214 std::scoped_lock lock{advance_lock, basic_lock};
229 global_timer = GetGlobalTimeNs().count(); 215 global_timer = GetGlobalTimeNs().count();
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index 6aa3ae923..3259397b2 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -22,7 +22,6 @@ namespace Core::Timing {
22/// A callback that may be scheduled for a particular core timing event. 22/// A callback that may be scheduled for a particular core timing event.
23using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>( 23using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>(
24 std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>; 24 std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>;
25using PauseCallback = std::function<void(bool paused)>;
26 25
27/// Contains the characteristics of a particular event. 26/// Contains the characteristics of a particular event.
28struct EventType { 27struct EventType {
@@ -134,9 +133,6 @@ public:
134 /// Checks for events manually and returns time in nanoseconds for next event, threadsafe. 133 /// Checks for events manually and returns time in nanoseconds for next event, threadsafe.
135 std::optional<s64> Advance(); 134 std::optional<s64> Advance();
136 135
137 /// Register a callback function to be called when coretiming pauses.
138 void RegisterPauseCallback(PauseCallback&& callback);
139
140private: 136private:
141 struct Event; 137 struct Event;
142 138
@@ -176,8 +172,6 @@ private:
176 /// Cycle timing 172 /// Cycle timing
177 u64 ticks{}; 173 u64 ticks{};
178 s64 downcount{}; 174 s64 downcount{};
179
180 std::vector<PauseCallback> pause_callbacks{};
181}; 175};
182 176
183/// Creates a core timing event with the given name and callback. 177/// Creates a core timing event with the given name and callback.
diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp
index ac64d2f9d..e42bdd17d 100644
--- a/src/core/debugger/debugger.cpp
+++ b/src/core/debugger/debugger.cpp
@@ -15,6 +15,7 @@
15#include "core/debugger/debugger_interface.h" 15#include "core/debugger/debugger_interface.h"
16#include "core/debugger/gdbstub.h" 16#include "core/debugger/gdbstub.h"
17#include "core/hle/kernel/global_scheduler_context.h" 17#include "core/hle/kernel/global_scheduler_context.h"
18#include "core/hle/kernel/k_scheduler.h"
18 19
19template <typename Readable, typename Buffer, typename Callback> 20template <typename Readable, typename Buffer, typename Callback>
20static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) { 21static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) {
@@ -230,13 +231,12 @@ private:
230 } 231 }
231 232
232 void PauseEmulation() { 233 void PauseEmulation() {
234 Kernel::KScopedSchedulerLock sl{system.Kernel()};
235
233 // Put all threads to sleep on next scheduler round. 236 // Put all threads to sleep on next scheduler round.
234 for (auto* thread : ThreadList()) { 237 for (auto* thread : ThreadList()) {
235 thread->RequestSuspend(Kernel::SuspendType::Debug); 238 thread->RequestSuspend(Kernel::SuspendType::Debug);
236 } 239 }
237
238 // Signal an interrupt so that scheduler will fire.
239 system.Kernel().InterruptAllPhysicalCores();
240 } 240 }
241 241
242 void ResumeEmulation(Kernel::KThread* except = nullptr) { 242 void ResumeEmulation(Kernel::KThread* except = nullptr) {
@@ -253,7 +253,8 @@ private:
253 253
254 template <typename Callback> 254 template <typename Callback>
255 void MarkResumed(Callback&& cb) { 255 void MarkResumed(Callback&& cb) {
256 std::scoped_lock lk{connection_lock}; 256 Kernel::KScopedSchedulerLock sl{system.Kernel()};
257 std::scoped_lock cl{connection_lock};
257 stopped = false; 258 stopped = false;
258 cb(); 259 cb();
259 } 260 }
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp
index 4b35ca82f..5aab428bb 100644
--- a/src/core/file_sys/ips_layer.cpp
+++ b/src/core/file_sys/ips_layer.cpp
@@ -217,9 +217,7 @@ void IPSwitchCompiler::Parse() {
217 break; 217 break;
218 } else if (StartsWith(line, "@nsobid-")) { 218 } else if (StartsWith(line, "@nsobid-")) {
219 // NSO Build ID Specifier 219 // NSO Build ID Specifier
220 auto raw_build_id = line.substr(8); 220 const auto raw_build_id = fmt::format("{:0<64}", line.substr(8));
221 if (raw_build_id.size() != 0x40)
222 raw_build_id.resize(0x40, '0');
223 nso_build_id = Common::HexStringToArray<0x20>(raw_build_id); 221 nso_build_id = Common::HexStringToArray<0x20>(raw_build_id);
224 } else if (StartsWith(line, "#")) { 222 } else if (StartsWith(line, "#")) {
225 // Mandatory Comment 223 // Mandatory Comment
@@ -287,7 +285,8 @@ void IPSwitchCompiler::Parse() {
287 std::copy(value.begin(), value.end(), std::back_inserter(replace)); 285 std::copy(value.begin(), value.end(), std::back_inserter(replace));
288 } else { 286 } else {
289 // hex replacement 287 // hex replacement
290 const auto value = patch_line.substr(9); 288 const auto value =
289 patch_line.substr(9, patch_line.find_first_of(" /\r\n", 9) - 9);
291 replace = Common::HexStringToVector(value, is_little_endian); 290 replace = Common::HexStringToVector(value, is_little_endian);
292 } 291 }
293 292
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index bd525b26c..4c80e13a9 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -191,6 +191,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
191std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs, 191std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs,
192 const std::string& build_id) const { 192 const std::string& build_id) const {
193 const auto& disabled = Settings::values.disabled_addons[title_id]; 193 const auto& disabled = Settings::values.disabled_addons[title_id];
194 const auto nso_build_id = fmt::format("{:0<64}", build_id);
194 195
195 std::vector<VirtualFile> out; 196 std::vector<VirtualFile> out;
196 out.reserve(patch_dirs.size()); 197 out.reserve(patch_dirs.size());
@@ -203,21 +204,18 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
203 for (const auto& file : exefs_dir->GetFiles()) { 204 for (const auto& file : exefs_dir->GetFiles()) {
204 if (file->GetExtension() == "ips") { 205 if (file->GetExtension() == "ips") {
205 auto name = file->GetName(); 206 auto name = file->GetName();
206 const auto p1 = name.substr(0, name.find('.'));
207 const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1);
208 207
209 if (build_id == this_build_id) 208 const auto this_build_id =
209 fmt::format("{:0<64}", name.substr(0, name.find('.')));
210 if (nso_build_id == this_build_id)
210 out.push_back(file); 211 out.push_back(file);
211 } else if (file->GetExtension() == "pchtxt") { 212 } else if (file->GetExtension() == "pchtxt") {
212 IPSwitchCompiler compiler{file}; 213 IPSwitchCompiler compiler{file};
213 if (!compiler.IsValid()) 214 if (!compiler.IsValid())
214 continue; 215 continue;
215 216
216 auto this_build_id = Common::HexToString(compiler.GetBuildID()); 217 const auto this_build_id = Common::HexToString(compiler.GetBuildID());
217 this_build_id = 218 if (nso_build_id == this_build_id)
218 this_build_id.substr(0, this_build_id.find_last_not_of('0') + 1);
219
220 if (build_id == this_build_id)
221 out.push_back(file); 219 out.push_back(file);
222 } 220 }
223 } 221 }
diff --git a/src/core/file_sys/system_archive/shared_font.cpp b/src/core/file_sys/system_archive/shared_font.cpp
index f841988ff..3210583f0 100644
--- a/src/core/file_sys/system_archive/shared_font.cpp
+++ b/src/core/file_sys/system_archive/shared_font.cpp
@@ -9,7 +9,7 @@
9#include "core/file_sys/system_archive/data/font_standard.h" 9#include "core/file_sys/system_archive/data/font_standard.h"
10#include "core/file_sys/system_archive/shared_font.h" 10#include "core/file_sys/system_archive/shared_font.h"
11#include "core/file_sys/vfs_vector.h" 11#include "core/file_sys/vfs_vector.h"
12#include "core/hle/service/ns/pl_u.h" 12#include "core/hle/service/ns/iplatform_service_manager.h"
13 13
14namespace FileSys::SystemArchive { 14namespace FileSys::SystemArchive {
15 15
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 8c3895937..01c43be93 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -1,6 +1,7 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "common/thread.h"
4#include "core/hid/emulated_controller.h" 5#include "core/hid/emulated_controller.h"
5#include "core/hid/input_converter.h" 6#include "core/hid/input_converter.h"
6 7
@@ -84,23 +85,26 @@ void EmulatedController::ReloadFromSettings() {
84 motion_params[index] = Common::ParamPackage(player.motions[index]); 85 motion_params[index] = Common::ParamPackage(player.motions[index]);
85 } 86 }
86 87
88 controller.colors_state.fullkey = {
89 .body = GetNpadColor(player.body_color_left),
90 .button = GetNpadColor(player.button_color_left),
91 };
87 controller.colors_state.left = { 92 controller.colors_state.left = {
88 .body = player.body_color_left, 93 .body = GetNpadColor(player.body_color_left),
89 .button = player.button_color_left, 94 .button = GetNpadColor(player.button_color_left),
90 }; 95 };
91 96 controller.colors_state.left = {
92 controller.colors_state.right = { 97 .body = GetNpadColor(player.body_color_right),
93 .body = player.body_color_right, 98 .button = GetNpadColor(player.button_color_right),
94 .button = player.button_color_right,
95 }; 99 };
96 100
97 controller.colors_state.fullkey = controller.colors_state.left;
98
99 // Other or debug controller should always be a pro controller 101 // Other or debug controller should always be a pro controller
100 if (npad_id_type != NpadIdType::Other) { 102 if (npad_id_type != NpadIdType::Other) {
101 SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type)); 103 SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type));
104 original_npad_type = npad_type;
102 } else { 105 } else {
103 SetNpadStyleIndex(NpadStyleIndex::ProController); 106 SetNpadStyleIndex(NpadStyleIndex::ProController);
107 original_npad_type = npad_type;
104 } 108 }
105 109
106 if (player.connected) { 110 if (player.connected) {
@@ -352,6 +356,7 @@ void EmulatedController::DisableConfiguration() {
352 Disconnect(); 356 Disconnect();
353 } 357 }
354 SetNpadStyleIndex(tmp_npad_type); 358 SetNpadStyleIndex(tmp_npad_type);
359 original_npad_type = tmp_npad_type;
355 } 360 }
356 361
357 // Apply temporary connected status to the real controller 362 // Apply temporary connected status to the real controller
@@ -557,6 +562,16 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
557 return; 562 return;
558 } 563 }
559 564
565 // GC controllers have triggers not buttons
566 if (npad_type == NpadStyleIndex::GameCube) {
567 if (index == Settings::NativeButton::ZR) {
568 return;
569 }
570 if (index == Settings::NativeButton::ZL) {
571 return;
572 }
573 }
574
560 switch (index) { 575 switch (index) {
561 case Settings::NativeButton::A: 576 case Settings::NativeButton::A:
562 controller.npad_button_state.a.Assign(current_status.value); 577 controller.npad_button_state.a.Assign(current_status.value);
@@ -733,6 +748,11 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
733 return; 748 return;
734 } 749 }
735 750
751 // Only GC controllers have analog triggers
752 if (npad_type != NpadStyleIndex::GameCube) {
753 return;
754 }
755
736 const auto& trigger = controller.trigger_values[index]; 756 const auto& trigger = controller.trigger_values[index];
737 757
738 switch (index) { 758 switch (index) {
@@ -949,6 +969,9 @@ bool EmulatedController::TestVibration(std::size_t device_index) {
949 // Send a slight vibration to test for rumble support 969 // Send a slight vibration to test for rumble support
950 output_devices[device_index]->SetVibration(test_vibration); 970 output_devices[device_index]->SetVibration(test_vibration);
951 971
972 // Wait for about 15ms to ensure the controller is ready for the stop command
973 std::this_thread::sleep_for(std::chrono::milliseconds(15));
974
952 // Stop any vibration and return the result 975 // Stop any vibration and return the result
953 return output_devices[device_index]->SetVibration(zero_vibration) == 976 return output_devices[device_index]->SetVibration(zero_vibration) ==
954 Common::Input::VibrationError::None; 977 Common::Input::VibrationError::None;
@@ -999,13 +1022,27 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
999 if (!is_connected) { 1022 if (!is_connected) {
1000 return; 1023 return;
1001 } 1024 }
1025
1026 // Attempt to reconnect with the original type
1027 if (npad_type != original_npad_type) {
1028 Disconnect();
1029 const auto current_npad_type = npad_type;
1030 SetNpadStyleIndex(original_npad_type);
1031 if (IsControllerSupported()) {
1032 Connect();
1033 return;
1034 }
1035 SetNpadStyleIndex(current_npad_type);
1036 Connect();
1037 }
1038
1002 if (IsControllerSupported()) { 1039 if (IsControllerSupported()) {
1003 return; 1040 return;
1004 } 1041 }
1005 1042
1006 Disconnect(); 1043 Disconnect();
1007 1044
1008 // Fallback fullkey controllers to Pro controllers 1045 // Fallback Fullkey controllers to Pro controllers
1009 if (IsControllerFullkey() && supported_style_tag.fullkey) { 1046 if (IsControllerFullkey() && supported_style_tag.fullkey) {
1010 LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type); 1047 LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type);
1011 SetNpadStyleIndex(NpadStyleIndex::ProController); 1048 SetNpadStyleIndex(NpadStyleIndex::ProController);
@@ -1013,6 +1050,22 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
1013 return; 1050 return;
1014 } 1051 }
1015 1052
1053 // Fallback Dual joycon controllers to Pro controllers
1054 if (npad_type == NpadStyleIndex::JoyconDual && supported_style_tag.fullkey) {
1055 LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type);
1056 SetNpadStyleIndex(NpadStyleIndex::ProController);
1057 Connect();
1058 return;
1059 }
1060
1061 // Fallback Pro controllers to Dual joycon
1062 if (npad_type == NpadStyleIndex::ProController && supported_style_tag.joycon_dual) {
1063 LOG_WARNING(Service_HID, "Reconnecting controller type {} as Dual Joycons", npad_type);
1064 SetNpadStyleIndex(NpadStyleIndex::JoyconDual);
1065 Connect();
1066 return;
1067 }
1068
1016 LOG_ERROR(Service_HID, "Controller type {} is not supported. Disconnecting controller", 1069 LOG_ERROR(Service_HID, "Controller type {} is not supported. Disconnecting controller",
1017 npad_type); 1070 npad_type);
1018} 1071}
@@ -1310,6 +1363,15 @@ const CameraState& EmulatedController::GetCamera() const {
1310 return controller.camera_state; 1363 return controller.camera_state;
1311} 1364}
1312 1365
1366NpadColor EmulatedController::GetNpadColor(u32 color) {
1367 return {
1368 .r = static_cast<u8>((color >> 16) & 0xFF),
1369 .g = static_cast<u8>((color >> 8) & 0xFF),
1370 .b = static_cast<u8>(color & 0xFF),
1371 .a = 0xff,
1372 };
1373}
1374
1313void EmulatedController::TriggerOnChange(ControllerTriggerType type, bool is_npad_service_update) { 1375void EmulatedController::TriggerOnChange(ControllerTriggerType type, bool is_npad_service_update) {
1314 std::scoped_lock lock{callback_mutex}; 1376 std::scoped_lock lock{callback_mutex};
1315 for (const auto& poller_pair : callback_list) { 1377 for (const auto& poller_pair : callback_list) {
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index 823c1700c..c3aa8f9d3 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -425,6 +425,13 @@ private:
425 void SetCamera(const Common::Input::CallbackStatus& callback); 425 void SetCamera(const Common::Input::CallbackStatus& callback);
426 426
427 /** 427 /**
428 * Converts a color format from bgra to rgba
429 * @param color in bgra format
430 * @return NpadColor in rgba format
431 */
432 NpadColor GetNpadColor(u32 color);
433
434 /**
428 * Triggers a callback that something has changed on the controller status 435 * Triggers a callback that something has changed on the controller status
429 * @param type Input type of the event to trigger 436 * @param type Input type of the event to trigger
430 * @param is_service_update indicates if this event should only be sent to HID services 437 * @param is_service_update indicates if this event should only be sent to HID services
@@ -433,6 +440,7 @@ private:
433 440
434 const NpadIdType npad_id_type; 441 const NpadIdType npad_id_type;
435 NpadStyleIndex npad_type{NpadStyleIndex::None}; 442 NpadStyleIndex npad_type{NpadStyleIndex::None};
443 NpadStyleIndex original_npad_type{NpadStyleIndex::None};
436 NpadStyleTag supported_style_tag{NpadStyleSet::All}; 444 NpadStyleTag supported_style_tag{NpadStyleSet::All};
437 bool is_connected{false}; 445 bool is_connected{false};
438 bool is_configuring{false}; 446 bool is_configuring{false};
diff --git a/src/core/hid/hid_types.h b/src/core/hid/hid_types.h
index e49223016..e3b1cfbc6 100644
--- a/src/core/hid/hid_types.h
+++ b/src/core/hid/hid_types.h
@@ -327,10 +327,18 @@ struct TouchState {
327}; 327};
328static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size"); 328static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
329 329
330struct NpadColor {
331 u8 r{};
332 u8 g{};
333 u8 b{};
334 u8 a{};
335};
336static_assert(sizeof(NpadColor) == 4, "NpadColor is an invalid size");
337
330// This is nn::hid::NpadControllerColor 338// This is nn::hid::NpadControllerColor
331struct NpadControllerColor { 339struct NpadControllerColor {
332 u32 body{}; 340 NpadColor body{};
333 u32 button{}; 341 NpadColor button{};
334}; 342};
335static_assert(sizeof(NpadControllerColor) == 8, "NpadControllerColor is an invalid size"); 343static_assert(sizeof(NpadControllerColor) == 8, "NpadControllerColor is an invalid size");
336 344
diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp
index 68d143a01..52fb69e9c 100644
--- a/src/core/hid/input_converter.cpp
+++ b/src/core/hid/input_converter.cpp
@@ -52,6 +52,9 @@ Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatu
52 Common::Input::ButtonStatus status{}; 52 Common::Input::ButtonStatus status{};
53 switch (callback.type) { 53 switch (callback.type) {
54 case Common::Input::InputType::Analog: 54 case Common::Input::InputType::Analog:
55 status.value = TransformToTrigger(callback).pressed.value;
56 status.toggle = callback.analog_status.properties.toggle;
57 break;
55 case Common::Input::InputType::Trigger: 58 case Common::Input::InputType::Trigger:
56 status.value = TransformToTrigger(callback).pressed.value; 59 status.value = TransformToTrigger(callback).pressed.value;
57 break; 60 break;
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index f4072e1c3..ce7fa8275 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -17,7 +17,6 @@
17#include "common/thread.h" 17#include "common/thread.h"
18#include "common/thread_worker.h" 18#include "common/thread_worker.h"
19#include "core/arm/arm_interface.h" 19#include "core/arm/arm_interface.h"
20#include "core/arm/cpu_interrupt_handler.h"
21#include "core/arm/exclusive_monitor.h" 20#include "core/arm/exclusive_monitor.h"
22#include "core/core.h" 21#include "core/core.h"
23#include "core/core_timing.h" 22#include "core/core_timing.h"
@@ -82,7 +81,7 @@ struct KernelCore::Impl {
82 81
83 void InitializeCores() { 82 void InitializeCores() {
84 for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { 83 for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
85 cores[core_id].Initialize((*current_process).Is64BitProcess()); 84 cores[core_id]->Initialize((*current_process).Is64BitProcess());
86 system.Memory().SetCurrentPageTable(*current_process, core_id); 85 system.Memory().SetCurrentPageTable(*current_process, core_id);
87 } 86 }
88 } 87 }
@@ -100,7 +99,9 @@ struct KernelCore::Impl {
100 next_user_process_id = KProcess::ProcessIDMin; 99 next_user_process_id = KProcess::ProcessIDMin;
101 next_thread_id = 1; 100 next_thread_id = 1;
102 101
103 cores.clear(); 102 for (auto& core : cores) {
103 core = nullptr;
104 }
104 105
105 global_handle_table->Finalize(); 106 global_handle_table->Finalize();
106 global_handle_table.reset(); 107 global_handle_table.reset();
@@ -199,7 +200,7 @@ struct KernelCore::Impl {
199 const s32 core{static_cast<s32>(i)}; 200 const s32 core{static_cast<s32>(i)};
200 201
201 schedulers[i] = std::make_unique<Kernel::KScheduler>(system.Kernel()); 202 schedulers[i] = std::make_unique<Kernel::KScheduler>(system.Kernel());
202 cores.emplace_back(i, system, *schedulers[i], interrupts); 203 cores[i] = std::make_unique<Kernel::PhysicalCore>(i, system, *schedulers[i]);
203 204
204 auto* main_thread{Kernel::KThread::Create(system.Kernel())}; 205 auto* main_thread{Kernel::KThread::Create(system.Kernel())};
205 main_thread->SetName(fmt::format("MainThread:{}", core)); 206 main_thread->SetName(fmt::format("MainThread:{}", core));
@@ -761,7 +762,7 @@ struct KernelCore::Impl {
761 std::unordered_set<KAutoObject*> registered_in_use_objects; 762 std::unordered_set<KAutoObject*> registered_in_use_objects;
762 763
763 std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor; 764 std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor;
764 std::vector<Kernel::PhysicalCore> cores; 765 std::array<std::unique_ptr<Kernel::PhysicalCore>, Core::Hardware::NUM_CPU_CORES> cores;
765 766
766 // Next host thead ID to use, 0-3 IDs represent core threads, >3 represent others 767 // Next host thead ID to use, 0-3 IDs represent core threads, >3 represent others
767 std::atomic<u32> next_host_thread_id{Core::Hardware::NUM_CPU_CORES}; 768 std::atomic<u32> next_host_thread_id{Core::Hardware::NUM_CPU_CORES};
@@ -785,7 +786,6 @@ struct KernelCore::Impl {
785 Common::ThreadWorker service_threads_manager; 786 Common::ThreadWorker service_threads_manager;
786 787
787 std::array<KThread*, Core::Hardware::NUM_CPU_CORES> shutdown_threads; 788 std::array<KThread*, Core::Hardware::NUM_CPU_CORES> shutdown_threads;
788 std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{};
789 std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{}; 789 std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
790 790
791 bool is_multicore{}; 791 bool is_multicore{};
@@ -874,11 +874,11 @@ const Kernel::KScheduler& KernelCore::Scheduler(std::size_t id) const {
874} 874}
875 875
876Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) { 876Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) {
877 return impl->cores[id]; 877 return *impl->cores[id];
878} 878}
879 879
880const Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) const { 880const Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) const {
881 return impl->cores[id]; 881 return *impl->cores[id];
882} 882}
883 883
884size_t KernelCore::CurrentPhysicalCoreIndex() const { 884size_t KernelCore::CurrentPhysicalCoreIndex() const {
@@ -890,11 +890,11 @@ size_t KernelCore::CurrentPhysicalCoreIndex() const {
890} 890}
891 891
892Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() { 892Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() {
893 return impl->cores[CurrentPhysicalCoreIndex()]; 893 return *impl->cores[CurrentPhysicalCoreIndex()];
894} 894}
895 895
896const Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() const { 896const Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() const {
897 return impl->cores[CurrentPhysicalCoreIndex()]; 897 return *impl->cores[CurrentPhysicalCoreIndex()];
898} 898}
899 899
900Kernel::KScheduler* KernelCore::CurrentScheduler() { 900Kernel::KScheduler* KernelCore::CurrentScheduler() {
@@ -906,15 +906,6 @@ Kernel::KScheduler* KernelCore::CurrentScheduler() {
906 return impl->schedulers[core_id].get(); 906 return impl->schedulers[core_id].get();
907} 907}
908 908
909std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& KernelCore::Interrupts() {
910 return impl->interrupts;
911}
912
913const std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& KernelCore::Interrupts()
914 const {
915 return impl->interrupts;
916}
917
918Kernel::TimeManager& KernelCore::TimeManager() { 909Kernel::TimeManager& KernelCore::TimeManager() {
919 return impl->time_manager; 910 return impl->time_manager;
920} 911}
@@ -939,24 +930,18 @@ const KAutoObjectWithListContainer& KernelCore::ObjectListContainer() const {
939 return *impl->global_object_list_container; 930 return *impl->global_object_list_container;
940} 931}
941 932
942void KernelCore::InterruptAllPhysicalCores() {
943 for (auto& physical_core : impl->cores) {
944 physical_core.Interrupt();
945 }
946}
947
948void KernelCore::InvalidateAllInstructionCaches() { 933void KernelCore::InvalidateAllInstructionCaches() {
949 for (auto& physical_core : impl->cores) { 934 for (auto& physical_core : impl->cores) {
950 physical_core.ArmInterface().ClearInstructionCache(); 935 physical_core->ArmInterface().ClearInstructionCache();
951 } 936 }
952} 937}
953 938
954void KernelCore::InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size) { 939void KernelCore::InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size) {
955 for (auto& physical_core : impl->cores) { 940 for (auto& physical_core : impl->cores) {
956 if (!physical_core.IsInitialized()) { 941 if (!physical_core->IsInitialized()) {
957 continue; 942 continue;
958 } 943 }
959 physical_core.ArmInterface().InvalidateCacheRange(addr, size); 944 physical_core->ArmInterface().InvalidateCacheRange(addr, size);
960 } 945 }
961} 946}
962 947
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 6c7cf6af2..bcf016a97 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -9,14 +9,12 @@
9#include <string> 9#include <string>
10#include <unordered_map> 10#include <unordered_map>
11#include <vector> 11#include <vector>
12#include "core/arm/cpu_interrupt_handler.h"
13#include "core/hardware_properties.h" 12#include "core/hardware_properties.h"
14#include "core/hle/kernel/k_auto_object.h" 13#include "core/hle/kernel/k_auto_object.h"
15#include "core/hle/kernel/k_slab_heap.h" 14#include "core/hle/kernel/k_slab_heap.h"
16#include "core/hle/kernel/svc_common.h" 15#include "core/hle/kernel/svc_common.h"
17 16
18namespace Core { 17namespace Core {
19class CPUInterruptHandler;
20class ExclusiveMonitor; 18class ExclusiveMonitor;
21class System; 19class System;
22} // namespace Core 20} // namespace Core
@@ -183,12 +181,6 @@ public:
183 181
184 const KAutoObjectWithListContainer& ObjectListContainer() const; 182 const KAutoObjectWithListContainer& ObjectListContainer() const;
185 183
186 std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& Interrupts();
187
188 const std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& Interrupts() const;
189
190 void InterruptAllPhysicalCores();
191
192 void InvalidateAllInstructionCaches(); 184 void InvalidateAllInstructionCaches();
193 185
194 void InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size); 186 void InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size);
diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp
index 6e7dacf97..d4375962f 100644
--- a/src/core/hle/kernel/physical_core.cpp
+++ b/src/core/hle/kernel/physical_core.cpp
@@ -1,7 +1,6 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "core/arm/cpu_interrupt_handler.h"
5#include "core/arm/dynarmic/arm_dynarmic_32.h" 4#include "core/arm/dynarmic/arm_dynarmic_32.h"
6#include "core/arm/dynarmic/arm_dynarmic_64.h" 5#include "core/arm/dynarmic/arm_dynarmic_64.h"
7#include "core/core.h" 6#include "core/core.h"
@@ -11,16 +10,14 @@
11 10
12namespace Kernel { 11namespace Kernel {
13 12
14PhysicalCore::PhysicalCore(std::size_t core_index_, Core::System& system_, KScheduler& scheduler_, 13PhysicalCore::PhysicalCore(std::size_t core_index_, Core::System& system_, KScheduler& scheduler_)
15 Core::CPUInterrupts& interrupts_) 14 : core_index{core_index_}, system{system_}, scheduler{scheduler_} {
16 : core_index{core_index_}, system{system_}, scheduler{scheduler_},
17 interrupts{interrupts_}, guard{std::make_unique<std::mutex>()} {
18#ifdef ARCHITECTURE_x86_64 15#ifdef ARCHITECTURE_x86_64
19 // TODO(bunnei): Initialization relies on a core being available. We may later replace this with 16 // TODO(bunnei): Initialization relies on a core being available. We may later replace this with
20 // a 32-bit instance of Dynarmic. This should be abstracted out to a CPU manager. 17 // a 32-bit instance of Dynarmic. This should be abstracted out to a CPU manager.
21 auto& kernel = system.Kernel(); 18 auto& kernel = system.Kernel();
22 arm_interface = std::make_unique<Core::ARM_Dynarmic_64>( 19 arm_interface = std::make_unique<Core::ARM_Dynarmic_64>(
23 system, interrupts, kernel.IsMulticore(), kernel.GetExclusiveMonitor(), core_index); 20 system, kernel.IsMulticore(), kernel.GetExclusiveMonitor(), core_index);
24#else 21#else
25#error Platform not supported yet. 22#error Platform not supported yet.
26#endif 23#endif
@@ -34,7 +31,7 @@ void PhysicalCore::Initialize([[maybe_unused]] bool is_64_bit) {
34 if (!is_64_bit) { 31 if (!is_64_bit) {
35 // We already initialized a 64-bit core, replace with a 32-bit one. 32 // We already initialized a 64-bit core, replace with a 32-bit one.
36 arm_interface = std::make_unique<Core::ARM_Dynarmic_32>( 33 arm_interface = std::make_unique<Core::ARM_Dynarmic_32>(
37 system, interrupts, kernel.IsMulticore(), kernel.GetExclusiveMonitor(), core_index); 34 system, kernel.IsMulticore(), kernel.GetExclusiveMonitor(), core_index);
38 } 35 }
39#else 36#else
40#error Platform not supported yet. 37#error Platform not supported yet.
@@ -47,24 +44,26 @@ void PhysicalCore::Run() {
47} 44}
48 45
49void PhysicalCore::Idle() { 46void PhysicalCore::Idle() {
50 interrupts[core_index].AwaitInterrupt(); 47 std::unique_lock lk{guard};
48 on_interrupt.wait(lk, [this] { return is_interrupted; });
51} 49}
52 50
53bool PhysicalCore::IsInterrupted() const { 51bool PhysicalCore::IsInterrupted() const {
54 return interrupts[core_index].IsInterrupted(); 52 return is_interrupted;
55} 53}
56 54
57void PhysicalCore::Interrupt() { 55void PhysicalCore::Interrupt() {
58 guard->lock(); 56 std::unique_lock lk{guard};
59 interrupts[core_index].SetInterrupt(true); 57 is_interrupted = true;
60 arm_interface->SignalInterrupt(); 58 arm_interface->SignalInterrupt();
61 guard->unlock(); 59 on_interrupt.notify_all();
62} 60}
63 61
64void PhysicalCore::ClearInterrupt() { 62void PhysicalCore::ClearInterrupt() {
65 guard->lock(); 63 std::unique_lock lk{guard};
66 interrupts[core_index].SetInterrupt(false); 64 is_interrupted = false;
67 guard->unlock(); 65 arm_interface->ClearInterrupt();
66 on_interrupt.notify_all();
68} 67}
69 68
70} // namespace Kernel 69} // namespace Kernel
diff --git a/src/core/hle/kernel/physical_core.h b/src/core/hle/kernel/physical_core.h
index 898d1e5db..2fc8d4be2 100644
--- a/src/core/hle/kernel/physical_core.h
+++ b/src/core/hle/kernel/physical_core.h
@@ -14,7 +14,6 @@ class KScheduler;
14} // namespace Kernel 14} // namespace Kernel
15 15
16namespace Core { 16namespace Core {
17class CPUInterruptHandler;
18class ExclusiveMonitor; 17class ExclusiveMonitor;
19class System; 18class System;
20} // namespace Core 19} // namespace Core
@@ -23,15 +22,11 @@ namespace Kernel {
23 22
24class PhysicalCore { 23class PhysicalCore {
25public: 24public:
26 PhysicalCore(std::size_t core_index_, Core::System& system_, KScheduler& scheduler_, 25 PhysicalCore(std::size_t core_index_, Core::System& system_, KScheduler& scheduler_);
27 Core::CPUInterrupts& interrupts_);
28 ~PhysicalCore(); 26 ~PhysicalCore();
29 27
30 PhysicalCore(const PhysicalCore&) = delete; 28 YUZU_NON_COPYABLE(PhysicalCore);
31 PhysicalCore& operator=(const PhysicalCore&) = delete; 29 YUZU_NON_MOVEABLE(PhysicalCore);
32
33 PhysicalCore(PhysicalCore&&) = default;
34 PhysicalCore& operator=(PhysicalCore&&) = delete;
35 30
36 /// Initialize the core for the specified parameters. 31 /// Initialize the core for the specified parameters.
37 void Initialize(bool is_64_bit); 32 void Initialize(bool is_64_bit);
@@ -86,9 +81,11 @@ private:
86 const std::size_t core_index; 81 const std::size_t core_index;
87 Core::System& system; 82 Core::System& system;
88 Kernel::KScheduler& scheduler; 83 Kernel::KScheduler& scheduler;
89 Core::CPUInterrupts& interrupts; 84
90 std::unique_ptr<std::mutex> guard; 85 std::mutex guard;
86 std::condition_variable on_interrupt;
91 std::unique_ptr<Core::ARM_Interface> arm_interface; 87 std::unique_ptr<Core::ARM_Interface> arm_interface;
88 bool is_interrupted;
92}; 89};
93 90
94} // namespace Kernel 91} // namespace Kernel
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 4de44cd06..47a1b829b 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -117,6 +117,7 @@ union Result {
117 BitField<0, 9, ErrorModule> module; 117 BitField<0, 9, ErrorModule> module;
118 BitField<9, 13, u32> description; 118 BitField<9, 13, u32> description;
119 119
120 Result() = default;
120 constexpr explicit Result(u32 raw_) : raw(raw_) {} 121 constexpr explicit Result(u32 raw_) : raw(raw_) {}
121 122
122 constexpr Result(ErrorModule module_, u32 description_) 123 constexpr Result(ErrorModule module_, u32 description_)
@@ -130,6 +131,7 @@ union Result {
130 return !IsSuccess(); 131 return !IsSuccess();
131 } 132 }
132}; 133};
134static_assert(std::is_trivial_v<Result>);
133 135
134[[nodiscard]] constexpr bool operator==(const Result& a, const Result& b) { 136[[nodiscard]] constexpr bool operator==(const Result& a, const Result& b) {
135 return a.raw == b.raw; 137 return a.raw == b.raw;
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index def105832..bb838e285 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -534,7 +534,7 @@ public:
534 534
535private: 535private:
536 void CheckAvailability(Kernel::HLERequestContext& ctx) { 536 void CheckAvailability(Kernel::HLERequestContext& ctx) {
537 LOG_WARNING(Service_ACC, "(STUBBED) called"); 537 LOG_DEBUG(Service_ACC, "(STUBBED) called");
538 IPC::ResponseBuilder rb{ctx, 3}; 538 IPC::ResponseBuilder rb{ctx, 3};
539 rb.Push(ResultSuccess); 539 rb.Push(ResultSuccess);
540 rb.Push(false); // TODO: Check when this is supposed to return true and when not 540 rb.Push(false); // TODO: Check when this is supposed to return true and when not
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 118f226e4..6fb7e198e 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -754,7 +754,7 @@ void ICommonStateGetter::ReceiveMessage(Kernel::HLERequestContext& ctx) {
754} 754}
755 755
756void ICommonStateGetter::GetCurrentFocusState(Kernel::HLERequestContext& ctx) { 756void ICommonStateGetter::GetCurrentFocusState(Kernel::HLERequestContext& ctx) {
757 LOG_WARNING(Service_AM, "(STUBBED) called"); 757 LOG_DEBUG(Service_AM, "(STUBBED) called");
758 758
759 IPC::ResponseBuilder rb{ctx, 3}; 759 IPC::ResponseBuilder rb{ctx, 3};
760 rb.Push(ResultSuccess); 760 rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/am/applets/applet_web_browser.cpp b/src/core/hle/service/am/applets/applet_web_browser.cpp
index 4b804b78c..14aa6f69e 100644
--- a/src/core/hle/service/am/applets/applet_web_browser.cpp
+++ b/src/core/hle/service/am/applets/applet_web_browser.cpp
@@ -21,7 +21,7 @@
21#include "core/hle/service/am/am.h" 21#include "core/hle/service/am/am.h"
22#include "core/hle/service/am/applets/applet_web_browser.h" 22#include "core/hle/service/am/applets/applet_web_browser.h"
23#include "core/hle/service/filesystem/filesystem.h" 23#include "core/hle/service/filesystem/filesystem.h"
24#include "core/hle/service/ns/pl_u.h" 24#include "core/hle/service/ns/iplatform_service_manager.h"
25#include "core/loader/loader.h" 25#include "core/loader/loader.h"
26 26
27namespace Service::AM::Applets { 27namespace Service::AM::Applets {
diff --git a/src/core/hle/service/apm/apm_controller.cpp b/src/core/hle/service/apm/apm_controller.cpp
index 4e710491b..d6de84066 100644
--- a/src/core/hle/service/apm/apm_controller.cpp
+++ b/src/core/hle/service/apm/apm_controller.cpp
@@ -80,7 +80,7 @@ PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(Performa
80} 80}
81 81
82void Controller::SetClockSpeed(u32 mhz) { 82void Controller::SetClockSpeed(u32 mhz) {
83 LOG_INFO(Service_APM, "called, mhz={:08X}", mhz); 83 LOG_DEBUG(Service_APM, "called, mhz={:08X}", mhz);
84 // TODO(DarkLordZach): Actually signal core_timing to change clock speed. 84 // TODO(DarkLordZach): Actually signal core_timing to change clock speed.
85 // TODO(Rodrigo): Remove [[maybe_unused]] when core_timing is used. 85 // TODO(Rodrigo): Remove [[maybe_unused]] when core_timing is used.
86} 86}
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index a44dd842a..49c092301 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -246,9 +246,8 @@ void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) {
246 const auto write_count = 246 const auto write_count =
247 static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName)); 247 static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName));
248 std::vector<AudioDevice::AudioDeviceName> device_names{}; 248 std::vector<AudioDevice::AudioDeviceName> device_names{};
249 std::string print_names{};
250 if (write_count > 0) { 249 if (write_count > 0) {
251 device_names.push_back(AudioDevice::AudioDeviceName("DeviceOut")); 250 device_names.emplace_back("DeviceOut");
252 LOG_DEBUG(Service_Audio, "called. \nName=DeviceOut"); 251 LOG_DEBUG(Service_Audio, "called. \nName=DeviceOut");
253 } else { 252 } else {
254 LOG_DEBUG(Service_Audio, "called. Empty buffer passed in."); 253 LOG_DEBUG(Service_Audio, "called. Empty buffer passed in.");
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index 381a66ba5..6fb07c37d 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -50,7 +50,7 @@ public:
50 {7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"}, 50 {7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"},
51 {8, &IAudioRenderer::SetRenderingTimeLimit, "SetRenderingTimeLimit"}, 51 {8, &IAudioRenderer::SetRenderingTimeLimit, "SetRenderingTimeLimit"},
52 {9, &IAudioRenderer::GetRenderingTimeLimit, "GetRenderingTimeLimit"}, 52 {9, &IAudioRenderer::GetRenderingTimeLimit, "GetRenderingTimeLimit"},
53 {10, nullptr, "RequestUpdateAuto"}, 53 {10, &IAudioRenderer::RequestUpdate, "RequestUpdateAuto"},
54 {11, nullptr, "ExecuteAudioRendererRendering"}, 54 {11, nullptr, "ExecuteAudioRendererRendering"},
55 }; 55 };
56 // clang-format on 56 // clang-format on
@@ -113,15 +113,30 @@ private:
113 113
114 // These buffers are written manually to avoid an issue with WriteBuffer throwing errors for 114 // These buffers are written manually to avoid an issue with WriteBuffer throwing errors for
115 // checking size 0. Performance size is 0 for most games. 115 // checking size 0. Performance size is 0 for most games.
116 const auto buffers{ctx.BufferDescriptorB()}; 116
117 std::vector<u8> output(buffers[0].Size(), 0); 117 std::vector<u8> output{};
118 std::vector<u8> performance(buffers[1].Size(), 0); 118 std::vector<u8> performance{};
119 auto is_buffer_b{ctx.BufferDescriptorB()[0].Size() != 0};
120 if (is_buffer_b) {
121 const auto buffersB{ctx.BufferDescriptorB()};
122 output.resize(buffersB[0].Size(), 0);
123 performance.resize(buffersB[1].Size(), 0);
124 } else {
125 const auto buffersC{ctx.BufferDescriptorC()};
126 output.resize(buffersC[0].Size(), 0);
127 performance.resize(buffersC[1].Size(), 0);
128 }
119 129
120 auto result = impl->RequestUpdate(input, performance, output); 130 auto result = impl->RequestUpdate(input, performance, output);
121 131
122 if (result.IsSuccess()) { 132 if (result.IsSuccess()) {
123 ctx.WriteBufferB(output.data(), output.size(), 0); 133 if (is_buffer_b) {
124 ctx.WriteBufferB(performance.data(), performance.size(), 1); 134 ctx.WriteBufferB(output.data(), output.size(), 0);
135 ctx.WriteBufferB(performance.data(), performance.size(), 1);
136 } else {
137 ctx.WriteBufferC(output.data(), output.size(), 0);
138 ctx.WriteBufferC(performance.data(), performance.size(), 1);
139 }
125 } else { 140 } else {
126 LOG_ERROR(Service_Audio, "RequestUpdate failed error 0x{:02X}!", result.description); 141 LOG_ERROR(Service_Audio, "RequestUpdate failed error 0x{:02X}!", result.description);
127 } 142 }
@@ -237,7 +252,7 @@ private:
237 252
238 std::vector<AudioDevice::AudioDeviceName> out_names{}; 253 std::vector<AudioDevice::AudioDeviceName> out_names{};
239 254
240 u32 out_count = impl->ListAudioDeviceName(out_names, in_count); 255 const u32 out_count = impl->ListAudioDeviceName(out_names, in_count);
241 256
242 std::string out{}; 257 std::string out{};
243 for (u32 i = 0; i < out_count; i++) { 258 for (u32 i = 0; i < out_count; i++) {
@@ -350,7 +365,7 @@ private:
350 365
351 std::vector<AudioDevice::AudioDeviceName> out_names{}; 366 std::vector<AudioDevice::AudioDeviceName> out_names{};
352 367
353 u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count); 368 const u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count);
354 369
355 std::string out{}; 370 std::string out{};
356 for (u32 i = 0; i < out_count; i++) { 371 for (u32 i = 0; i < out_count; i++) {
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index fae6e5aff..e23eae36a 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -246,7 +246,8 @@ static void BuildEntryIndex(std::vector<FileSys::Entry>& entries, const std::vec
246 entries.reserve(entries.size() + new_data.size()); 246 entries.reserve(entries.size() + new_data.size());
247 247
248 for (const auto& new_entry : new_data) { 248 for (const auto& new_entry : new_data) {
249 entries.emplace_back(new_entry->GetName(), type, new_entry->GetSize()); 249 entries.emplace_back(new_entry->GetName(), type,
250 type == FileSys::EntryType::Directory ? 0 : new_entry->GetSize());
250 } 251 }
251} 252}
252 253
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 3c28dee76..cb29004e8 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -163,28 +163,51 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
163 } 163 }
164 LOG_DEBUG(Service_HID, "Npad connected {}", npad_id); 164 LOG_DEBUG(Service_HID, "Npad connected {}", npad_id);
165 const auto controller_type = controller.device->GetNpadStyleIndex(); 165 const auto controller_type = controller.device->GetNpadStyleIndex();
166 const auto& body_colors = controller.device->GetColors();
167 const auto& battery_level = controller.device->GetBattery();
166 auto* shared_memory = controller.shared_memory; 168 auto* shared_memory = controller.shared_memory;
167 if (controller_type == Core::HID::NpadStyleIndex::None) { 169 if (controller_type == Core::HID::NpadStyleIndex::None) {
168 controller.styleset_changed_event->GetWritableEvent().Signal(); 170 controller.styleset_changed_event->GetWritableEvent().Signal();
169 return; 171 return;
170 } 172 }
173
174 // Reset memory values
171 shared_memory->style_tag.raw = Core::HID::NpadStyleSet::None; 175 shared_memory->style_tag.raw = Core::HID::NpadStyleSet::None;
172 shared_memory->device_type.raw = 0; 176 shared_memory->device_type.raw = 0;
173 shared_memory->system_properties.raw = 0; 177 shared_memory->system_properties.raw = 0;
178 shared_memory->joycon_color.attribute = ColorAttribute::NoController;
179 shared_memory->joycon_color.attribute = ColorAttribute::NoController;
180 shared_memory->fullkey_color = {};
181 shared_memory->joycon_color.left = {};
182 shared_memory->joycon_color.right = {};
183 shared_memory->battery_level_dual = {};
184 shared_memory->battery_level_left = {};
185 shared_memory->battery_level_right = {};
186
174 switch (controller_type) { 187 switch (controller_type) {
175 case Core::HID::NpadStyleIndex::None: 188 case Core::HID::NpadStyleIndex::None:
176 ASSERT(false); 189 ASSERT(false);
177 break; 190 break;
178 case Core::HID::NpadStyleIndex::ProController: 191 case Core::HID::NpadStyleIndex::ProController:
192 shared_memory->fullkey_color.attribute = ColorAttribute::Ok;
193 shared_memory->fullkey_color.fullkey = body_colors.fullkey;
194 shared_memory->battery_level_dual = battery_level.dual.battery_level;
179 shared_memory->style_tag.fullkey.Assign(1); 195 shared_memory->style_tag.fullkey.Assign(1);
180 shared_memory->device_type.fullkey.Assign(1); 196 shared_memory->device_type.fullkey.Assign(1);
181 shared_memory->system_properties.is_vertical.Assign(1); 197 shared_memory->system_properties.is_vertical.Assign(1);
182 shared_memory->system_properties.use_plus.Assign(1); 198 shared_memory->system_properties.use_plus.Assign(1);
183 shared_memory->system_properties.use_minus.Assign(1); 199 shared_memory->system_properties.use_minus.Assign(1);
200 shared_memory->system_properties.is_charging_joy_dual.Assign(
201 battery_level.dual.is_charging);
184 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::SwitchProController; 202 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::SwitchProController;
185 shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1); 203 shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1);
186 break; 204 break;
187 case Core::HID::NpadStyleIndex::Handheld: 205 case Core::HID::NpadStyleIndex::Handheld:
206 shared_memory->fullkey_color.attribute = ColorAttribute::Ok;
207 shared_memory->joycon_color.attribute = ColorAttribute::Ok;
208 shared_memory->fullkey_color.fullkey = body_colors.fullkey;
209 shared_memory->joycon_color.left = body_colors.left;
210 shared_memory->joycon_color.right = body_colors.right;
188 shared_memory->style_tag.handheld.Assign(1); 211 shared_memory->style_tag.handheld.Assign(1);
189 shared_memory->device_type.handheld_left.Assign(1); 212 shared_memory->device_type.handheld_left.Assign(1);
190 shared_memory->device_type.handheld_right.Assign(1); 213 shared_memory->device_type.handheld_right.Assign(1);
@@ -192,47 +215,86 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
192 shared_memory->system_properties.use_plus.Assign(1); 215 shared_memory->system_properties.use_plus.Assign(1);
193 shared_memory->system_properties.use_minus.Assign(1); 216 shared_memory->system_properties.use_minus.Assign(1);
194 shared_memory->system_properties.use_directional_buttons.Assign(1); 217 shared_memory->system_properties.use_directional_buttons.Assign(1);
218 shared_memory->system_properties.is_charging_joy_dual.Assign(
219 battery_level.left.is_charging);
220 shared_memory->system_properties.is_charging_joy_left.Assign(
221 battery_level.left.is_charging);
222 shared_memory->system_properties.is_charging_joy_right.Assign(
223 battery_level.right.is_charging);
195 shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; 224 shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual;
196 shared_memory->applet_nfc_xcd.applet_footer.type = 225 shared_memory->applet_nfc_xcd.applet_footer.type =
197 AppletFooterUiType::HandheldJoyConLeftJoyConRight; 226 AppletFooterUiType::HandheldJoyConLeftJoyConRight;
198 shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1); 227 shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1);
199 break; 228 break;
200 case Core::HID::NpadStyleIndex::JoyconDual: 229 case Core::HID::NpadStyleIndex::JoyconDual:
230 shared_memory->fullkey_color.attribute = ColorAttribute::Ok;
231 shared_memory->joycon_color.attribute = ColorAttribute::Ok;
201 shared_memory->style_tag.joycon_dual.Assign(1); 232 shared_memory->style_tag.joycon_dual.Assign(1);
202 if (controller.is_dual_left_connected) { 233 if (controller.is_dual_left_connected) {
234 shared_memory->joycon_color.left = body_colors.left;
235 shared_memory->battery_level_left = battery_level.left.battery_level;
203 shared_memory->device_type.joycon_left.Assign(1); 236 shared_memory->device_type.joycon_left.Assign(1);
204 shared_memory->system_properties.use_minus.Assign(1); 237 shared_memory->system_properties.use_minus.Assign(1);
238 shared_memory->system_properties.is_charging_joy_left.Assign(
239 battery_level.left.is_charging);
205 shared_memory->sixaxis_dual_left_properties.is_newly_assigned.Assign(1); 240 shared_memory->sixaxis_dual_left_properties.is_newly_assigned.Assign(1);
206 } 241 }
207 if (controller.is_dual_right_connected) { 242 if (controller.is_dual_right_connected) {
243 shared_memory->joycon_color.right = body_colors.right;
244 shared_memory->battery_level_right = battery_level.right.battery_level;
208 shared_memory->device_type.joycon_right.Assign(1); 245 shared_memory->device_type.joycon_right.Assign(1);
209 shared_memory->system_properties.use_plus.Assign(1); 246 shared_memory->system_properties.use_plus.Assign(1);
247 shared_memory->system_properties.is_charging_joy_right.Assign(
248 battery_level.right.is_charging);
210 shared_memory->sixaxis_dual_right_properties.is_newly_assigned.Assign(1); 249 shared_memory->sixaxis_dual_right_properties.is_newly_assigned.Assign(1);
211 } 250 }
212 shared_memory->system_properties.use_directional_buttons.Assign(1); 251 shared_memory->system_properties.use_directional_buttons.Assign(1);
213 shared_memory->system_properties.is_vertical.Assign(1); 252 shared_memory->system_properties.is_vertical.Assign(1);
214 shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; 253 shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual;
254
215 if (controller.is_dual_left_connected && controller.is_dual_right_connected) { 255 if (controller.is_dual_left_connected && controller.is_dual_right_connected) {
216 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDual; 256 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDual;
257 shared_memory->fullkey_color.fullkey = body_colors.left;
258 shared_memory->battery_level_dual = battery_level.left.battery_level;
259 shared_memory->system_properties.is_charging_joy_dual.Assign(
260 battery_level.left.is_charging);
217 } else if (controller.is_dual_left_connected) { 261 } else if (controller.is_dual_left_connected) {
218 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualLeftOnly; 262 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualLeftOnly;
263 shared_memory->fullkey_color.fullkey = body_colors.left;
264 shared_memory->battery_level_dual = battery_level.left.battery_level;
265 shared_memory->system_properties.is_charging_joy_dual.Assign(
266 battery_level.left.is_charging);
219 } else { 267 } else {
220 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualRightOnly; 268 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualRightOnly;
269 shared_memory->fullkey_color.fullkey = body_colors.right;
270 shared_memory->battery_level_dual = battery_level.right.battery_level;
271 shared_memory->system_properties.is_charging_joy_dual.Assign(
272 battery_level.right.is_charging);
221 } 273 }
222 break; 274 break;
223 case Core::HID::NpadStyleIndex::JoyconLeft: 275 case Core::HID::NpadStyleIndex::JoyconLeft:
276 shared_memory->joycon_color.attribute = ColorAttribute::Ok;
277 shared_memory->joycon_color.left = body_colors.left;
278 shared_memory->battery_level_dual = battery_level.left.battery_level;
224 shared_memory->style_tag.joycon_left.Assign(1); 279 shared_memory->style_tag.joycon_left.Assign(1);
225 shared_memory->device_type.joycon_left.Assign(1); 280 shared_memory->device_type.joycon_left.Assign(1);
226 shared_memory->system_properties.is_horizontal.Assign(1); 281 shared_memory->system_properties.is_horizontal.Assign(1);
227 shared_memory->system_properties.use_minus.Assign(1); 282 shared_memory->system_properties.use_minus.Assign(1);
283 shared_memory->system_properties.is_charging_joy_left.Assign(
284 battery_level.left.is_charging);
228 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyLeftHorizontal; 285 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyLeftHorizontal;
229 shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1); 286 shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1);
230 break; 287 break;
231 case Core::HID::NpadStyleIndex::JoyconRight: 288 case Core::HID::NpadStyleIndex::JoyconRight:
289 shared_memory->joycon_color.attribute = ColorAttribute::Ok;
290 shared_memory->joycon_color.right = body_colors.right;
291 shared_memory->battery_level_right = battery_level.right.battery_level;
232 shared_memory->style_tag.joycon_right.Assign(1); 292 shared_memory->style_tag.joycon_right.Assign(1);
233 shared_memory->device_type.joycon_right.Assign(1); 293 shared_memory->device_type.joycon_right.Assign(1);
234 shared_memory->system_properties.is_horizontal.Assign(1); 294 shared_memory->system_properties.is_horizontal.Assign(1);
235 shared_memory->system_properties.use_plus.Assign(1); 295 shared_memory->system_properties.use_plus.Assign(1);
296 shared_memory->system_properties.is_charging_joy_right.Assign(
297 battery_level.right.is_charging);
236 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyRightHorizontal; 298 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyRightHorizontal;
237 shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1); 299 shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1);
238 break; 300 break;
@@ -269,21 +331,6 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
269 break; 331 break;
270 } 332 }
271 333
272 const auto& body_colors = controller.device->GetColors();
273
274 shared_memory->fullkey_color.attribute = ColorAttribute::Ok;
275 shared_memory->fullkey_color.fullkey = body_colors.fullkey;
276
277 shared_memory->joycon_color.attribute = ColorAttribute::Ok;
278 shared_memory->joycon_color.left = body_colors.left;
279 shared_memory->joycon_color.right = body_colors.right;
280
281 // TODO: Investigate when we should report all batery types
282 const auto& battery_level = controller.device->GetBattery();
283 shared_memory->battery_level_dual = battery_level.dual.battery_level;
284 shared_memory->battery_level_left = battery_level.left.battery_level;
285 shared_memory->battery_level_right = battery_level.right.battery_level;
286
287 controller.is_connected = true; 334 controller.is_connected = true;
288 controller.device->Connect(); 335 controller.device->Connect();
289 SignalStyleSetChangedEvent(npad_id); 336 SignalStyleSetChangedEvent(npad_id);
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 5ecbddf94..3d3457160 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -819,12 +819,12 @@ void Hid::EnableSixAxisSensorUnalteredPassthrough(Kernel::HLERequestContext& ctx
819 const auto result = controller.EnableSixAxisSensorUnalteredPassthrough( 819 const auto result = controller.EnableSixAxisSensorUnalteredPassthrough(
820 parameters.sixaxis_handle, parameters.enabled); 820 parameters.sixaxis_handle, parameters.enabled);
821 821
822 LOG_WARNING(Service_HID, 822 LOG_DEBUG(Service_HID,
823 "(STUBBED) called, enabled={}, npad_type={}, npad_id={}, device_index={}, " 823 "(STUBBED) called, enabled={}, npad_type={}, npad_id={}, device_index={}, "
824 "applet_resource_user_id={}", 824 "applet_resource_user_id={}",
825 parameters.enabled, parameters.sixaxis_handle.npad_type, 825 parameters.enabled, parameters.sixaxis_handle.npad_type,
826 parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index, 826 parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index,
827 parameters.applet_resource_user_id); 827 parameters.applet_resource_user_id);
828 828
829 IPC::ResponseBuilder rb{ctx, 2}; 829 IPC::ResponseBuilder rb{ctx, 2};
830 rb.Push(result); 830 rb.Push(result);
@@ -846,7 +846,7 @@ void Hid::IsSixAxisSensorUnalteredPassthroughEnabled(Kernel::HLERequestContext&
846 const auto result = controller.IsSixAxisSensorUnalteredPassthroughEnabled( 846 const auto result = controller.IsSixAxisSensorUnalteredPassthroughEnabled(
847 parameters.sixaxis_handle, is_unaltered_sisxaxis_enabled); 847 parameters.sixaxis_handle, is_unaltered_sisxaxis_enabled);
848 848
849 LOG_WARNING( 849 LOG_DEBUG(
850 Service_HID, 850 Service_HID,
851 "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", 851 "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
852 parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, 852 parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
@@ -2146,12 +2146,18 @@ public:
2146 {324, nullptr, "GetUniquePadButtonSet"}, 2146 {324, nullptr, "GetUniquePadButtonSet"},
2147 {325, nullptr, "GetUniquePadColor"}, 2147 {325, nullptr, "GetUniquePadColor"},
2148 {326, nullptr, "GetUniquePadAppletDetailedUiType"}, 2148 {326, nullptr, "GetUniquePadAppletDetailedUiType"},
2149 {327, nullptr, "GetAbstractedPadIdDataFromNpad"},
2150 {328, nullptr, "AttachAbstractedPadToNpad"},
2151 {329, nullptr, "DetachAbstractedPadAll"},
2152 {330, nullptr, "CheckAbstractedPadConnection"},
2149 {500, nullptr, "SetAppletResourceUserId"}, 2153 {500, nullptr, "SetAppletResourceUserId"},
2150 {501, nullptr, "RegisterAppletResourceUserId"}, 2154 {501, nullptr, "RegisterAppletResourceUserId"},
2151 {502, nullptr, "UnregisterAppletResourceUserId"}, 2155 {502, nullptr, "UnregisterAppletResourceUserId"},
2152 {503, nullptr, "EnableAppletToGetInput"}, 2156 {503, nullptr, "EnableAppletToGetInput"},
2153 {504, nullptr, "SetAruidValidForVibration"}, 2157 {504, nullptr, "SetAruidValidForVibration"},
2154 {505, nullptr, "EnableAppletToGetSixAxisSensor"}, 2158 {505, nullptr, "EnableAppletToGetSixAxisSensor"},
2159 {506, nullptr, "EnableAppletToGetPadInput"},
2160 {507, nullptr, "EnableAppletToGetTouchScreen"},
2155 {510, nullptr, "SetVibrationMasterVolume"}, 2161 {510, nullptr, "SetVibrationMasterVolume"},
2156 {511, nullptr, "GetVibrationMasterVolume"}, 2162 {511, nullptr, "GetVibrationMasterVolume"},
2157 {512, nullptr, "BeginPermitVibrationSession"}, 2163 {512, nullptr, "BeginPermitVibrationSession"},
diff --git a/src/core/hle/service/ldn/errors.h b/src/core/hle/service/ldn/errors.h
deleted file mode 100644
index 972a74806..000000000
--- a/src/core/hle/service/ldn/errors.h
+++ /dev/null
@@ -1,12 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/hle/result.h"
7
8namespace Service::LDN {
9
10constexpr Result ERROR_DISABLED{ErrorModule::LDN, 22};
11
12} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp
index 125d4dc4c..c11daff54 100644
--- a/src/core/hle/service/ldn/ldn.cpp
+++ b/src/core/hle/service/ldn/ldn.cpp
@@ -3,11 +3,15 @@
3 3
4#include <memory> 4#include <memory>
5 5
6#include "core/hle/ipc_helpers.h" 6#include "core/core.h"
7#include "core/hle/result.h"
8#include "core/hle/service/ldn/errors.h"
9#include "core/hle/service/ldn/ldn.h" 7#include "core/hle/service/ldn/ldn.h"
10#include "core/hle/service/sm/sm.h" 8#include "core/hle/service/ldn/ldn_results.h"
9#include "core/hle/service/ldn/ldn_types.h"
10#include "core/internal_network/network.h"
11#include "core/internal_network/network_interface.h"
12
13// This is defined by synchapi.h and conflicts with ServiceContext::CreateEvent
14#undef CreateEvent
11 15
12namespace Service::LDN { 16namespace Service::LDN {
13 17
@@ -100,74 +104,418 @@ class IUserLocalCommunicationService final
100 : public ServiceFramework<IUserLocalCommunicationService> { 104 : public ServiceFramework<IUserLocalCommunicationService> {
101public: 105public:
102 explicit IUserLocalCommunicationService(Core::System& system_) 106 explicit IUserLocalCommunicationService(Core::System& system_)
103 : ServiceFramework{system_, "IUserLocalCommunicationService"} { 107 : ServiceFramework{system_, "IUserLocalCommunicationService", ServiceThreadType::CreateNew},
108 service_context{system, "IUserLocalCommunicationService"}, room_network{
109 system_.GetRoomNetwork()} {
104 // clang-format off 110 // clang-format off
105 static const FunctionInfo functions[] = { 111 static const FunctionInfo functions[] = {
106 {0, &IUserLocalCommunicationService::GetState, "GetState"}, 112 {0, &IUserLocalCommunicationService::GetState, "GetState"},
107 {1, nullptr, "GetNetworkInfo"}, 113 {1, &IUserLocalCommunicationService::GetNetworkInfo, "GetNetworkInfo"},
108 {2, nullptr, "GetIpv4Address"}, 114 {2, nullptr, "GetIpv4Address"},
109 {3, nullptr, "GetDisconnectReason"}, 115 {3, &IUserLocalCommunicationService::GetDisconnectReason, "GetDisconnectReason"},
110 {4, nullptr, "GetSecurityParameter"}, 116 {4, &IUserLocalCommunicationService::GetSecurityParameter, "GetSecurityParameter"},
111 {5, nullptr, "GetNetworkConfig"}, 117 {5, &IUserLocalCommunicationService::GetNetworkConfig, "GetNetworkConfig"},
112 {100, nullptr, "AttachStateChangeEvent"}, 118 {100, &IUserLocalCommunicationService::AttachStateChangeEvent, "AttachStateChangeEvent"},
113 {101, nullptr, "GetNetworkInfoLatestUpdate"}, 119 {101, &IUserLocalCommunicationService::GetNetworkInfoLatestUpdate, "GetNetworkInfoLatestUpdate"},
114 {102, nullptr, "Scan"}, 120 {102, &IUserLocalCommunicationService::Scan, "Scan"},
115 {103, nullptr, "ScanPrivate"}, 121 {103, &IUserLocalCommunicationService::ScanPrivate, "ScanPrivate"},
116 {104, nullptr, "SetWirelessControllerRestriction"}, 122 {104, nullptr, "SetWirelessControllerRestriction"},
117 {200, nullptr, "OpenAccessPoint"}, 123 {200, &IUserLocalCommunicationService::OpenAccessPoint, "OpenAccessPoint"},
118 {201, nullptr, "CloseAccessPoint"}, 124 {201, &IUserLocalCommunicationService::CloseAccessPoint, "CloseAccessPoint"},
119 {202, nullptr, "CreateNetwork"}, 125 {202, &IUserLocalCommunicationService::CreateNetwork, "CreateNetwork"},
120 {203, nullptr, "CreateNetworkPrivate"}, 126 {203, &IUserLocalCommunicationService::CreateNetworkPrivate, "CreateNetworkPrivate"},
121 {204, nullptr, "DestroyNetwork"}, 127 {204, &IUserLocalCommunicationService::DestroyNetwork, "DestroyNetwork"},
122 {205, nullptr, "Reject"}, 128 {205, nullptr, "Reject"},
123 {206, nullptr, "SetAdvertiseData"}, 129 {206, &IUserLocalCommunicationService::SetAdvertiseData, "SetAdvertiseData"},
124 {207, nullptr, "SetStationAcceptPolicy"}, 130 {207, &IUserLocalCommunicationService::SetStationAcceptPolicy, "SetStationAcceptPolicy"},
125 {208, nullptr, "AddAcceptFilterEntry"}, 131 {208, &IUserLocalCommunicationService::AddAcceptFilterEntry, "AddAcceptFilterEntry"},
126 {209, nullptr, "ClearAcceptFilter"}, 132 {209, nullptr, "ClearAcceptFilter"},
127 {300, nullptr, "OpenStation"}, 133 {300, &IUserLocalCommunicationService::OpenStation, "OpenStation"},
128 {301, nullptr, "CloseStation"}, 134 {301, &IUserLocalCommunicationService::CloseStation, "CloseStation"},
129 {302, nullptr, "Connect"}, 135 {302, &IUserLocalCommunicationService::Connect, "Connect"},
130 {303, nullptr, "ConnectPrivate"}, 136 {303, nullptr, "ConnectPrivate"},
131 {304, nullptr, "Disconnect"}, 137 {304, &IUserLocalCommunicationService::Disconnect, "Disconnect"},
132 {400, nullptr, "Initialize"}, 138 {400, &IUserLocalCommunicationService::Initialize, "Initialize"},
133 {401, nullptr, "Finalize"}, 139 {401, &IUserLocalCommunicationService::Finalize, "Finalize"},
134 {402, &IUserLocalCommunicationService::Initialize2, "Initialize2"}, // 7.0.0+ 140 {402, &IUserLocalCommunicationService::Initialize2, "Initialize2"},
135 }; 141 };
136 // clang-format on 142 // clang-format on
137 143
138 RegisterHandlers(functions); 144 RegisterHandlers(functions);
145
146 state_change_event =
147 service_context.CreateEvent("IUserLocalCommunicationService:StateChangeEvent");
148 }
149
150 ~IUserLocalCommunicationService() {
151 service_context.CloseEvent(state_change_event);
152 }
153
154 void OnEventFired() {
155 state_change_event->GetWritableEvent().Signal();
139 } 156 }
140 157
141 void GetState(Kernel::HLERequestContext& ctx) { 158 void GetState(Kernel::HLERequestContext& ctx) {
159 State state = State::Error;
160 LOG_WARNING(Service_LDN, "(STUBBED) called, state = {}", state);
161
162 IPC::ResponseBuilder rb{ctx, 3};
163 rb.Push(ResultSuccess);
164 rb.PushEnum(state);
165 }
166
167 void GetNetworkInfo(Kernel::HLERequestContext& ctx) {
168 const auto write_buffer_size = ctx.GetWriteBufferSize();
169
170 if (write_buffer_size != sizeof(NetworkInfo)) {
171 LOG_ERROR(Service_LDN, "Invalid buffer size {}", write_buffer_size);
172 IPC::ResponseBuilder rb{ctx, 2};
173 rb.Push(ResultBadInput);
174 return;
175 }
176
177 NetworkInfo network_info{};
178 const auto rc = ResultSuccess;
179 if (rc.IsError()) {
180 LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
181 IPC::ResponseBuilder rb{ctx, 2};
182 rb.Push(rc);
183 return;
184 }
185
186 LOG_WARNING(Service_LDN, "(STUBBED) called, ssid='{}', nodes={}",
187 network_info.common.ssid.GetStringValue(), network_info.ldn.node_count);
188
189 ctx.WriteBuffer<NetworkInfo>(network_info);
190 IPC::ResponseBuilder rb{ctx, 2};
191 rb.Push(rc);
192 }
193
194 void GetDisconnectReason(Kernel::HLERequestContext& ctx) {
195 const auto disconnect_reason = DisconnectReason::None;
196
197 LOG_WARNING(Service_LDN, "(STUBBED) called, disconnect_reason={}", disconnect_reason);
198
199 IPC::ResponseBuilder rb{ctx, 3};
200 rb.Push(ResultSuccess);
201 rb.PushEnum(disconnect_reason);
202 }
203
204 void GetSecurityParameter(Kernel::HLERequestContext& ctx) {
205 SecurityParameter security_parameter{};
206 NetworkInfo info{};
207 const Result rc = ResultSuccess;
208
209 if (rc.IsError()) {
210 LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
211 IPC::ResponseBuilder rb{ctx, 2};
212 rb.Push(rc);
213 return;
214 }
215
216 security_parameter.session_id = info.network_id.session_id;
217 std::memcpy(security_parameter.data.data(), info.ldn.security_parameter.data(),
218 sizeof(SecurityParameter::data));
219
142 LOG_WARNING(Service_LDN, "(STUBBED) called"); 220 LOG_WARNING(Service_LDN, "(STUBBED) called");
143 221
222 IPC::ResponseBuilder rb{ctx, 10};
223 rb.Push(rc);
224 rb.PushRaw<SecurityParameter>(security_parameter);
225 }
226
227 void GetNetworkConfig(Kernel::HLERequestContext& ctx) {
228 NetworkConfig config{};
229 NetworkInfo info{};
230 const Result rc = ResultSuccess;
231
232 if (rc.IsError()) {
233 LOG_ERROR(Service_LDN, "NetworkConfig is not valid {}", rc.raw);
234 IPC::ResponseBuilder rb{ctx, 2};
235 rb.Push(rc);
236 return;
237 }
238
239 config.intent_id = info.network_id.intent_id;
240 config.channel = info.common.channel;
241 config.node_count_max = info.ldn.node_count_max;
242 config.local_communication_version = info.ldn.nodes[0].local_communication_version;
243
244 LOG_WARNING(Service_LDN,
245 "(STUBBED) called, intent_id={}/{}, channel={}, node_count_max={}, "
246 "local_communication_version={}",
247 config.intent_id.local_communication_id, config.intent_id.scene_id,
248 config.channel, config.node_count_max, config.local_communication_version);
249
250 IPC::ResponseBuilder rb{ctx, 10};
251 rb.Push(rc);
252 rb.PushRaw<NetworkConfig>(config);
253 }
254
255 void AttachStateChangeEvent(Kernel::HLERequestContext& ctx) {
256 LOG_INFO(Service_LDN, "called");
257
258 IPC::ResponseBuilder rb{ctx, 2, 1};
259 rb.Push(ResultSuccess);
260 rb.PushCopyObjects(state_change_event->GetReadableEvent());
261 }
262
263 void GetNetworkInfoLatestUpdate(Kernel::HLERequestContext& ctx) {
264 const std::size_t network_buffer_size = ctx.GetWriteBufferSize(0);
265 const std::size_t node_buffer_count = ctx.GetWriteBufferSize(1) / sizeof(NodeLatestUpdate);
266
267 if (node_buffer_count == 0 || network_buffer_size != sizeof(NetworkInfo)) {
268 LOG_ERROR(Service_LDN, "Invalid buffer size {}, {}", network_buffer_size,
269 node_buffer_count);
270 IPC::ResponseBuilder rb{ctx, 2};
271 rb.Push(ResultBadInput);
272 return;
273 }
274
275 NetworkInfo info;
276 std::vector<NodeLatestUpdate> latest_update(node_buffer_count);
277
278 const auto rc = ResultSuccess;
279 if (rc.IsError()) {
280 LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
281 IPC::ResponseBuilder rb{ctx, 2};
282 rb.Push(rc);
283 return;
284 }
285
286 LOG_WARNING(Service_LDN, "(STUBBED) called, ssid='{}', nodes={}",
287 info.common.ssid.GetStringValue(), info.ldn.node_count);
288
289 ctx.WriteBuffer(info, 0);
290 ctx.WriteBuffer(latest_update, 1);
291
292 IPC::ResponseBuilder rb{ctx, 2};
293 rb.Push(ResultSuccess);
294 }
295
296 void Scan(Kernel::HLERequestContext& ctx) {
297 ScanImpl(ctx);
298 }
299
300 void ScanPrivate(Kernel::HLERequestContext& ctx) {
301 ScanImpl(ctx, true);
302 }
303
304 void ScanImpl(Kernel::HLERequestContext& ctx, bool is_private = false) {
305 IPC::RequestParser rp{ctx};
306 const auto channel{rp.PopEnum<WifiChannel>()};
307 const auto scan_filter{rp.PopRaw<ScanFilter>()};
308
309 const std::size_t network_info_size = ctx.GetWriteBufferSize() / sizeof(NetworkInfo);
310
311 if (network_info_size == 0) {
312 LOG_ERROR(Service_LDN, "Invalid buffer size {}", network_info_size);
313 IPC::ResponseBuilder rb{ctx, 2};
314 rb.Push(ResultBadInput);
315 return;
316 }
317
318 u16 count = 0;
319 std::vector<NetworkInfo> network_infos(network_info_size);
320
321 LOG_WARNING(Service_LDN,
322 "(STUBBED) called, channel={}, filter_scan_flag={}, filter_network_type={}",
323 channel, scan_filter.flag, scan_filter.network_type);
324
325 ctx.WriteBuffer(network_infos);
326
144 IPC::ResponseBuilder rb{ctx, 3}; 327 IPC::ResponseBuilder rb{ctx, 3};
328 rb.Push(ResultSuccess);
329 rb.Push<u32>(count);
330 }
331
332 void OpenAccessPoint(Kernel::HLERequestContext& ctx) {
333 LOG_WARNING(Service_LDN, "(STUBBED) called");
334
335 IPC::ResponseBuilder rb{ctx, 2};
336 rb.Push(ResultSuccess);
337 }
338
339 void CloseAccessPoint(Kernel::HLERequestContext& ctx) {
340 LOG_WARNING(Service_LDN, "(STUBBED) called");
341
342 IPC::ResponseBuilder rb{ctx, 2};
343 rb.Push(ResultSuccess);
344 }
345
346 void CreateNetwork(Kernel::HLERequestContext& ctx) {
347 IPC::RequestParser rp{ctx};
348 struct Parameters {
349 SecurityConfig security_config;
350 UserConfig user_config;
351 INSERT_PADDING_WORDS_NOINIT(1);
352 NetworkConfig network_config;
353 };
354 static_assert(sizeof(Parameters) == 0x98, "Parameters has incorrect size.");
355
356 const auto parameters{rp.PopRaw<Parameters>()};
357
358 LOG_WARNING(Service_LDN,
359 "(STUBBED) called, passphrase_size={}, security_mode={}, "
360 "local_communication_version={}",
361 parameters.security_config.passphrase_size,
362 parameters.security_config.security_mode,
363 parameters.network_config.local_communication_version);
364
365 IPC::ResponseBuilder rb{ctx, 2};
366 rb.Push(ResultSuccess);
367 }
368
369 void CreateNetworkPrivate(Kernel::HLERequestContext& ctx) {
370 IPC::RequestParser rp{ctx};
371 struct Parameters {
372 SecurityConfig security_config;
373 SecurityParameter security_parameter;
374 UserConfig user_config;
375 NetworkConfig network_config;
376 };
377 static_assert(sizeof(Parameters) == 0xB8, "Parameters has incorrect size.");
378
379 const auto parameters{rp.PopRaw<Parameters>()};
380
381 LOG_WARNING(Service_LDN,
382 "(STUBBED) called, passphrase_size={}, security_mode={}, "
383 "local_communication_version={}",
384 parameters.security_config.passphrase_size,
385 parameters.security_config.security_mode,
386 parameters.network_config.local_communication_version);
387
388 IPC::ResponseBuilder rb{ctx, 2};
389 rb.Push(ResultSuccess);
390 }
391
392 void DestroyNetwork(Kernel::HLERequestContext& ctx) {
393 LOG_WARNING(Service_LDN, "(STUBBED) called");
394
395 IPC::ResponseBuilder rb{ctx, 2};
396 rb.Push(ResultSuccess);
397 }
398
399 void SetAdvertiseData(Kernel::HLERequestContext& ctx) {
400 std::vector<u8> read_buffer = ctx.ReadBuffer();
401
402 LOG_WARNING(Service_LDN, "(STUBBED) called, size {}", read_buffer.size());
403
404 IPC::ResponseBuilder rb{ctx, 2};
405 rb.Push(ResultSuccess);
406 }
407
408 void SetStationAcceptPolicy(Kernel::HLERequestContext& ctx) {
409 LOG_WARNING(Service_LDN, "(STUBBED) called");
410
411 IPC::ResponseBuilder rb{ctx, 2};
412 rb.Push(ResultSuccess);
413 }
145 414
146 // Indicate a network error, as we do not actually emulate LDN 415 void AddAcceptFilterEntry(Kernel::HLERequestContext& ctx) {
147 rb.Push(static_cast<u32>(State::Error)); 416 LOG_WARNING(Service_LDN, "(STUBBED) called");
148 417
418 IPC::ResponseBuilder rb{ctx, 2};
419 rb.Push(ResultSuccess);
420 }
421
422 void OpenStation(Kernel::HLERequestContext& ctx) {
423 LOG_WARNING(Service_LDN, "(STUBBED) called");
424
425 IPC::ResponseBuilder rb{ctx, 2};
426 rb.Push(ResultSuccess);
427 }
428
429 void CloseStation(Kernel::HLERequestContext& ctx) {
430 LOG_WARNING(Service_LDN, "(STUBBED) called");
431
432 IPC::ResponseBuilder rb{ctx, 2};
433 rb.Push(ResultSuccess);
434 }
435
436 void Connect(Kernel::HLERequestContext& ctx) {
437 IPC::RequestParser rp{ctx};
438 struct Parameters {
439 SecurityConfig security_config;
440 UserConfig user_config;
441 u32 local_communication_version;
442 u32 option;
443 };
444 static_assert(sizeof(Parameters) == 0x7C, "Parameters has incorrect size.");
445
446 const auto parameters{rp.PopRaw<Parameters>()};
447
448 LOG_WARNING(Service_LDN,
449 "(STUBBED) called, passphrase_size={}, security_mode={}, "
450 "local_communication_version={}",
451 parameters.security_config.passphrase_size,
452 parameters.security_config.security_mode,
453 parameters.local_communication_version);
454
455 const std::vector<u8> read_buffer = ctx.ReadBuffer();
456 NetworkInfo network_info{};
457
458 if (read_buffer.size() != sizeof(NetworkInfo)) {
459 LOG_ERROR(Frontend, "NetworkInfo doesn't match read_buffer size!");
460 IPC::ResponseBuilder rb{ctx, 2};
461 rb.Push(ResultBadInput);
462 return;
463 }
464
465 std::memcpy(&network_info, read_buffer.data(), read_buffer.size());
466
467 IPC::ResponseBuilder rb{ctx, 2};
468 rb.Push(ResultSuccess);
469 }
470
471 void Disconnect(Kernel::HLERequestContext& ctx) {
472 LOG_WARNING(Service_LDN, "(STUBBED) called");
473
474 IPC::ResponseBuilder rb{ctx, 2};
475 rb.Push(ResultSuccess);
476 }
477 void Initialize(Kernel::HLERequestContext& ctx) {
478 LOG_WARNING(Service_LDN, "(STUBBED) called");
479
480 const auto rc = InitializeImpl(ctx);
481
482 IPC::ResponseBuilder rb{ctx, 2};
483 rb.Push(rc);
484 }
485
486 void Finalize(Kernel::HLERequestContext& ctx) {
487 LOG_WARNING(Service_LDN, "(STUBBED) called");
488
489 is_initialized = false;
490
491 IPC::ResponseBuilder rb{ctx, 2};
149 rb.Push(ResultSuccess); 492 rb.Push(ResultSuccess);
150 } 493 }
151 494
152 void Initialize2(Kernel::HLERequestContext& ctx) { 495 void Initialize2(Kernel::HLERequestContext& ctx) {
153 LOG_DEBUG(Service_LDN, "called"); 496 LOG_WARNING(Service_LDN, "(STUBBED) called");
154 497
155 is_initialized = true; 498 const auto rc = InitializeImpl(ctx);
156 499
157 IPC::ResponseBuilder rb{ctx, 2}; 500 IPC::ResponseBuilder rb{ctx, 2};
158 rb.Push(ERROR_DISABLED); 501 rb.Push(rc);
502 }
503
504 Result InitializeImpl(Kernel::HLERequestContext& ctx) {
505 const auto network_interface = Network::GetSelectedNetworkInterface();
506 if (!network_interface) {
507 LOG_ERROR(Service_LDN, "No network interface is set");
508 return ResultAirplaneModeEnabled;
509 }
510
511 is_initialized = true;
512 // TODO (flTobi): Change this to ResultSuccess when LDN is fully implemented
513 return ResultAirplaneModeEnabled;
159 } 514 }
160 515
161private: 516 KernelHelpers::ServiceContext service_context;
162 enum class State { 517 Kernel::KEvent* state_change_event;
163 None, 518 Network::RoomNetwork& room_network;
164 Initialized,
165 AccessPointOpened,
166 AccessPointCreated,
167 StationOpened,
168 StationConnected,
169 Error,
170 };
171 519
172 bool is_initialized{}; 520 bool is_initialized{};
173}; 521};
@@ -273,7 +621,7 @@ public:
273 LOG_WARNING(Service_LDN, "(STUBBED) called"); 621 LOG_WARNING(Service_LDN, "(STUBBED) called");
274 622
275 IPC::ResponseBuilder rb{ctx, 2}; 623 IPC::ResponseBuilder rb{ctx, 2};
276 rb.Push(ERROR_DISABLED); 624 rb.Push(ResultDisabled);
277 } 625 }
278}; 626};
279 627
diff --git a/src/core/hle/service/ldn/ldn.h b/src/core/hle/service/ldn/ldn.h
index a0031ac71..6afe2ea6f 100644
--- a/src/core/hle/service/ldn/ldn.h
+++ b/src/core/hle/service/ldn/ldn.h
@@ -3,6 +3,12 @@
3 3
4#pragma once 4#pragma once
5 5
6#include "core/hle/ipc_helpers.h"
7#include "core/hle/kernel/k_event.h"
8#include "core/hle/result.h"
9#include "core/hle/service/kernel_helpers.h"
10#include "core/hle/service/sm/sm.h"
11
6namespace Core { 12namespace Core {
7class System; 13class System;
8} 14}
diff --git a/src/core/hle/service/ldn/ldn_results.h b/src/core/hle/service/ldn/ldn_results.h
new file mode 100644
index 000000000..f340bda42
--- /dev/null
+++ b/src/core/hle/service/ldn/ldn_results.h
@@ -0,0 +1,27 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "core/hle/result.h"
7
8namespace Service::LDN {
9
10constexpr Result ResultAdvertiseDataTooLarge{ErrorModule::LDN, 10};
11constexpr Result ResultAuthenticationFailed{ErrorModule::LDN, 20};
12constexpr Result ResultDisabled{ErrorModule::LDN, 22};
13constexpr Result ResultAirplaneModeEnabled{ErrorModule::LDN, 23};
14constexpr Result ResultInvalidNodeCount{ErrorModule::LDN, 30};
15constexpr Result ResultConnectionFailed{ErrorModule::LDN, 31};
16constexpr Result ResultBadState{ErrorModule::LDN, 32};
17constexpr Result ResultNoIpAddress{ErrorModule::LDN, 33};
18constexpr Result ResultInvalidBufferCount{ErrorModule::LDN, 50};
19constexpr Result ResultAccessPointConnectionFailed{ErrorModule::LDN, 65};
20constexpr Result ResultAuthenticationTimeout{ErrorModule::LDN, 66};
21constexpr Result ResultMaximumNodeCount{ErrorModule::LDN, 67};
22constexpr Result ResultBadInput{ErrorModule::LDN, 96};
23constexpr Result ResultLocalCommunicationIdNotFound{ErrorModule::LDN, 97};
24constexpr Result ResultLocalCommunicationVersionTooLow{ErrorModule::LDN, 113};
25constexpr Result ResultLocalCommunicationVersionTooHigh{ErrorModule::LDN, 114};
26
27} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/ldn_types.h b/src/core/hle/service/ldn/ldn_types.h
new file mode 100644
index 000000000..6231e936d
--- /dev/null
+++ b/src/core/hle/service/ldn/ldn_types.h
@@ -0,0 +1,292 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include <fmt/format.h>
7
8#include "common/common_funcs.h"
9#include "common/common_types.h"
10#include "network/network.h"
11
12namespace Service::LDN {
13
14constexpr size_t SsidLengthMax = 32;
15constexpr size_t AdvertiseDataSizeMax = 384;
16constexpr size_t UserNameBytesMax = 32;
17constexpr int NodeCountMax = 8;
18constexpr int StationCountMax = NodeCountMax - 1;
19constexpr size_t PassphraseLengthMax = 64;
20
21enum class SecurityMode : u16 {
22 All,
23 Retail,
24 Debug,
25};
26
27enum class NodeStateChange : u8 {
28 None,
29 Connect,
30 Disconnect,
31 DisconnectAndConnect,
32};
33
34enum class ScanFilterFlag : u32 {
35 None = 0,
36 LocalCommunicationId = 1 << 0,
37 SessionId = 1 << 1,
38 NetworkType = 1 << 2,
39 Ssid = 1 << 4,
40 SceneId = 1 << 5,
41 IntentId = LocalCommunicationId | SceneId,
42 NetworkId = IntentId | SessionId,
43};
44
45enum class NetworkType : u32 {
46 None,
47 General,
48 Ldn,
49 All,
50};
51
52enum class PackedNetworkType : u8 {
53 None,
54 General,
55 Ldn,
56 All,
57};
58
59enum class State : u32 {
60 None,
61 Initialized,
62 AccessPointOpened,
63 AccessPointCreated,
64 StationOpened,
65 StationConnected,
66 Error,
67};
68
69enum class DisconnectReason : s16 {
70 Unknown = -1,
71 None,
72 DisconnectedByUser,
73 DisconnectedBySystem,
74 DestroyedByUser,
75 DestroyedBySystem,
76 Rejected,
77 SignalLost,
78};
79
80enum class NetworkError {
81 Unknown = -1,
82 None = 0,
83 PortUnreachable,
84 TooManyPlayers,
85 VersionTooLow,
86 VersionTooHigh,
87 ConnectFailure,
88 ConnectNotFound,
89 ConnectTimeout,
90 ConnectRejected,
91 RejectFailed,
92};
93
94enum class AcceptPolicy : u8 {
95 AcceptAll,
96 RejectAll,
97 BlackList,
98 WhiteList,
99};
100
101enum class WifiChannel : s16 {
102 Default = 0,
103 wifi24_1 = 1,
104 wifi24_6 = 6,
105 wifi24_11 = 11,
106 wifi50_36 = 36,
107 wifi50_40 = 40,
108 wifi50_44 = 44,
109 wifi50_48 = 48,
110};
111
112enum class LinkLevel : s8 {
113 Bad,
114 Low,
115 Good,
116 Excellent,
117};
118
119struct NodeLatestUpdate {
120 NodeStateChange state_change;
121 INSERT_PADDING_BYTES(0x7); // Unknown
122};
123static_assert(sizeof(NodeLatestUpdate) == 0x8, "NodeLatestUpdate is an invalid size");
124
125struct SessionId {
126 u64 high;
127 u64 low;
128
129 bool operator==(const SessionId&) const = default;
130};
131static_assert(sizeof(SessionId) == 0x10, "SessionId is an invalid size");
132
133struct IntentId {
134 u64 local_communication_id;
135 INSERT_PADDING_BYTES(0x2); // Reserved
136 u16 scene_id;
137 INSERT_PADDING_BYTES(0x4); // Reserved
138};
139static_assert(sizeof(IntentId) == 0x10, "IntentId is an invalid size");
140
141struct NetworkId {
142 IntentId intent_id;
143 SessionId session_id;
144};
145static_assert(sizeof(NetworkId) == 0x20, "NetworkId is an invalid size");
146
147struct Ssid {
148 u8 length{};
149 std::array<char, SsidLengthMax + 1> raw{};
150
151 Ssid() = default;
152
153 explicit Ssid(std::string_view data) {
154 length = static_cast<u8>(std::min(data.size(), SsidLengthMax));
155 data.copy(raw.data(), length);
156 raw[length] = 0;
157 }
158
159 std::string GetStringValue() const {
160 return std::string(raw.data());
161 }
162};
163static_assert(sizeof(Ssid) == 0x22, "Ssid is an invalid size");
164
165struct Ipv4Address {
166 union {
167 u32 raw{};
168 std::array<u8, 4> bytes;
169 };
170
171 std::string GetStringValue() const {
172 return fmt::format("{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]);
173 }
174};
175static_assert(sizeof(Ipv4Address) == 0x4, "Ipv4Address is an invalid size");
176
177struct MacAddress {
178 std::array<u8, 6> raw{};
179
180 friend bool operator==(const MacAddress& lhs, const MacAddress& rhs) = default;
181};
182static_assert(sizeof(MacAddress) == 0x6, "MacAddress is an invalid size");
183
184struct ScanFilter {
185 NetworkId network_id;
186 NetworkType network_type;
187 MacAddress mac_address;
188 Ssid ssid;
189 INSERT_PADDING_BYTES(0x10);
190 ScanFilterFlag flag;
191};
192static_assert(sizeof(ScanFilter) == 0x60, "ScanFilter is an invalid size");
193
194struct CommonNetworkInfo {
195 MacAddress bssid;
196 Ssid ssid;
197 WifiChannel channel;
198 LinkLevel link_level;
199 PackedNetworkType network_type;
200 INSERT_PADDING_BYTES(0x4);
201};
202static_assert(sizeof(CommonNetworkInfo) == 0x30, "CommonNetworkInfo is an invalid size");
203
204struct NodeInfo {
205 Ipv4Address ipv4_address;
206 MacAddress mac_address;
207 s8 node_id;
208 u8 is_connected;
209 std::array<u8, UserNameBytesMax + 1> user_name;
210 INSERT_PADDING_BYTES(0x1); // Reserved
211 s16 local_communication_version;
212 INSERT_PADDING_BYTES(0x10); // Reserved
213};
214static_assert(sizeof(NodeInfo) == 0x40, "NodeInfo is an invalid size");
215
216struct LdnNetworkInfo {
217 std::array<u8, 0x10> security_parameter;
218 SecurityMode security_mode;
219 AcceptPolicy station_accept_policy;
220 u8 has_action_frame;
221 INSERT_PADDING_BYTES(0x2); // Padding
222 u8 node_count_max;
223 u8 node_count;
224 std::array<NodeInfo, NodeCountMax> nodes;
225 INSERT_PADDING_BYTES(0x2); // Reserved
226 u16 advertise_data_size;
227 std::array<u8, AdvertiseDataSizeMax> advertise_data;
228 INSERT_PADDING_BYTES(0x8C); // Reserved
229 u64 random_authentication_id;
230};
231static_assert(sizeof(LdnNetworkInfo) == 0x430, "LdnNetworkInfo is an invalid size");
232
233struct NetworkInfo {
234 NetworkId network_id;
235 CommonNetworkInfo common;
236 LdnNetworkInfo ldn;
237};
238static_assert(sizeof(NetworkInfo) == 0x480, "NetworkInfo is an invalid size");
239
240struct SecurityConfig {
241 SecurityMode security_mode;
242 u16 passphrase_size;
243 std::array<u8, PassphraseLengthMax> passphrase;
244};
245static_assert(sizeof(SecurityConfig) == 0x44, "SecurityConfig is an invalid size");
246
247struct UserConfig {
248 std::array<u8, UserNameBytesMax + 1> user_name;
249 INSERT_PADDING_BYTES(0xF); // Reserved
250};
251static_assert(sizeof(UserConfig) == 0x30, "UserConfig is an invalid size");
252
253#pragma pack(push, 4)
254struct ConnectRequest {
255 SecurityConfig security_config;
256 UserConfig user_config;
257 u32 local_communication_version;
258 u32 option_unknown;
259 NetworkInfo network_info;
260};
261static_assert(sizeof(ConnectRequest) == 0x4FC, "ConnectRequest is an invalid size");
262#pragma pack(pop)
263
264struct SecurityParameter {
265 std::array<u8, 0x10> data; // Data, used with the same key derivation as SecurityConfig
266 SessionId session_id;
267};
268static_assert(sizeof(SecurityParameter) == 0x20, "SecurityParameter is an invalid size");
269
270struct NetworkConfig {
271 IntentId intent_id;
272 WifiChannel channel;
273 u8 node_count_max;
274 INSERT_PADDING_BYTES(0x1); // Reserved
275 u16 local_communication_version;
276 INSERT_PADDING_BYTES(0xA); // Reserved
277};
278static_assert(sizeof(NetworkConfig) == 0x20, "NetworkConfig is an invalid size");
279
280struct AddressEntry {
281 Ipv4Address ipv4_address;
282 MacAddress mac_address;
283 INSERT_PADDING_BYTES(0x2); // Reserved
284};
285static_assert(sizeof(AddressEntry) == 0xC, "AddressEntry is an invalid size");
286
287struct AddressList {
288 std::array<AddressEntry, 0x8> addresses;
289};
290static_assert(sizeof(AddressList) == 0x60, "AddressList is an invalid size");
291
292} // namespace Service::LDN
diff --git a/src/core/hle/service/mm/mm_u.cpp b/src/core/hle/service/mm/mm_u.cpp
index 5ebb124a7..ba8c0e230 100644
--- a/src/core/hle/service/mm/mm_u.cpp
+++ b/src/core/hle/service/mm/mm_u.cpp
@@ -46,7 +46,7 @@ private:
46 IPC::RequestParser rp{ctx}; 46 IPC::RequestParser rp{ctx};
47 min = rp.Pop<u32>(); 47 min = rp.Pop<u32>();
48 max = rp.Pop<u32>(); 48 max = rp.Pop<u32>();
49 LOG_WARNING(Service_MM, "(STUBBED) called, min=0x{:X}, max=0x{:X}", min, max); 49 LOG_DEBUG(Service_MM, "(STUBBED) called, min=0x{:X}, max=0x{:X}", min, max);
50 50
51 current = min; 51 current = min;
52 IPC::ResponseBuilder rb{ctx, 2}; 52 IPC::ResponseBuilder rb{ctx, 2};
@@ -54,7 +54,7 @@ private:
54 } 54 }
55 55
56 void GetOld(Kernel::HLERequestContext& ctx) { 56 void GetOld(Kernel::HLERequestContext& ctx) {
57 LOG_WARNING(Service_MM, "(STUBBED) called"); 57 LOG_DEBUG(Service_MM, "(STUBBED) called");
58 58
59 IPC::ResponseBuilder rb{ctx, 3}; 59 IPC::ResponseBuilder rb{ctx, 3};
60 rb.Push(ResultSuccess); 60 rb.Push(ResultSuccess);
@@ -81,8 +81,8 @@ private:
81 u32 input_id = rp.Pop<u32>(); 81 u32 input_id = rp.Pop<u32>();
82 min = rp.Pop<u32>(); 82 min = rp.Pop<u32>();
83 max = rp.Pop<u32>(); 83 max = rp.Pop<u32>();
84 LOG_WARNING(Service_MM, "(STUBBED) called, input_id=0x{:X}, min=0x{:X}, max=0x{:X}", 84 LOG_DEBUG(Service_MM, "(STUBBED) called, input_id=0x{:X}, min=0x{:X}, max=0x{:X}", input_id,
85 input_id, min, max); 85 min, max);
86 86
87 current = min; 87 current = min;
88 IPC::ResponseBuilder rb{ctx, 2}; 88 IPC::ResponseBuilder rb{ctx, 2};
@@ -90,7 +90,7 @@ private:
90 } 90 }
91 91
92 void Get(Kernel::HLERequestContext& ctx) { 92 void Get(Kernel::HLERequestContext& ctx) {
93 LOG_WARNING(Service_MM, "(STUBBED) called"); 93 LOG_DEBUG(Service_MM, "(STUBBED) called");
94 94
95 IPC::ResponseBuilder rb{ctx, 3}; 95 IPC::ResponseBuilder rb{ctx, 3};
96 rb.Push(ResultSuccess); 96 rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index 2889973e4..e3ef06481 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -6,7 +6,6 @@
6#include "core/hle/kernel/k_event.h" 6#include "core/hle/kernel/k_event.h"
7#include "core/hle/service/kernel_helpers.h" 7#include "core/hle/service/kernel_helpers.h"
8#include "core/hle/service/nifm/nifm.h" 8#include "core/hle/service/nifm/nifm.h"
9#include "core/hle/service/service.h"
10 9
11namespace { 10namespace {
12 11
@@ -271,213 +270,228 @@ public:
271 } 270 }
272}; 271};
273 272
274class IGeneralService final : public ServiceFramework<IGeneralService> { 273void IGeneralService::GetClientId(Kernel::HLERequestContext& ctx) {
275public: 274 static constexpr u32 client_id = 1;
276 explicit IGeneralService(Core::System& system_); 275 LOG_WARNING(Service_NIFM, "(STUBBED) called");
277 276
278private: 277 IPC::ResponseBuilder rb{ctx, 4};
279 void GetClientId(Kernel::HLERequestContext& ctx) { 278 rb.Push(ResultSuccess);
280 static constexpr u32 client_id = 1; 279 rb.Push<u64>(client_id); // Client ID needs to be non zero otherwise it's considered invalid
281 LOG_WARNING(Service_NIFM, "(STUBBED) called"); 280}
282 281
283 IPC::ResponseBuilder rb{ctx, 4}; 282void IGeneralService::CreateScanRequest(Kernel::HLERequestContext& ctx) {
284 rb.Push(ResultSuccess); 283 LOG_DEBUG(Service_NIFM, "called");
285 rb.Push<u64>(client_id); // Client ID needs to be non zero otherwise it's considered invalid
286 }
287 284
288 void CreateScanRequest(Kernel::HLERequestContext& ctx) { 285 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
289 LOG_DEBUG(Service_NIFM, "called");
290 286
291 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 287 rb.Push(ResultSuccess);
288 rb.PushIpcInterface<IScanRequest>(system);
289}
292 290
293 rb.Push(ResultSuccess); 291void IGeneralService::CreateRequest(Kernel::HLERequestContext& ctx) {
294 rb.PushIpcInterface<IScanRequest>(system); 292 LOG_DEBUG(Service_NIFM, "called");
295 }
296 293
297 void CreateRequest(Kernel::HLERequestContext& ctx) { 294 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
298 LOG_DEBUG(Service_NIFM, "called");
299 295
300 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 296 rb.Push(ResultSuccess);
297 rb.PushIpcInterface<IRequest>(system);
298}
301 299
302 rb.Push(ResultSuccess); 300void IGeneralService::GetCurrentNetworkProfile(Kernel::HLERequestContext& ctx) {
303 rb.PushIpcInterface<IRequest>(system); 301 LOG_WARNING(Service_NIFM, "(STUBBED) called");
304 }
305 302
306 void GetCurrentNetworkProfile(Kernel::HLERequestContext& ctx) { 303 const auto net_iface = Network::GetSelectedNetworkInterface();
307 LOG_WARNING(Service_NIFM, "(STUBBED) called");
308 304
309 const auto net_iface = Network::GetSelectedNetworkInterface(); 305 SfNetworkProfileData network_profile_data = [&net_iface] {
310 306 if (!net_iface) {
311 const SfNetworkProfileData network_profile_data = [&net_iface] { 307 return SfNetworkProfileData{};
312 if (!net_iface) { 308 }
313 return SfNetworkProfileData{}; 309
314 } 310 return SfNetworkProfileData{
315 311 .ip_setting_data{
316 return SfNetworkProfileData{ 312 .ip_address_setting{
317 .ip_setting_data{ 313 .is_automatic{true},
318 .ip_address_setting{ 314 .current_address{Network::TranslateIPv4(net_iface->ip_address)},
319 .is_automatic{true}, 315 .subnet_mask{Network::TranslateIPv4(net_iface->subnet_mask)},
320 .current_address{Network::TranslateIPv4(net_iface->ip_address)}, 316 .gateway{Network::TranslateIPv4(net_iface->gateway)},
321 .subnet_mask{Network::TranslateIPv4(net_iface->subnet_mask)},
322 .gateway{Network::TranslateIPv4(net_iface->gateway)},
323 },
324 .dns_setting{
325 .is_automatic{true},
326 .primary_dns{1, 1, 1, 1},
327 .secondary_dns{1, 0, 0, 1},
328 },
329 .proxy_setting{
330 .enabled{false},
331 .port{},
332 .proxy_server{},
333 .automatic_auth_enabled{},
334 .user{},
335 .password{},
336 },
337 .mtu{1500},
338 }, 317 },
339 .uuid{0xdeadbeef, 0xdeadbeef}, 318 .dns_setting{
340 .network_name{"yuzu Network"}, 319 .is_automatic{true},
341 .wireless_setting_data{ 320 .primary_dns{1, 1, 1, 1},
342 .ssid_length{12}, 321 .secondary_dns{1, 0, 0, 1},
343 .ssid{"yuzu Network"},
344 .passphrase{"yuzupassword"},
345 }, 322 },
346 }; 323 .proxy_setting{
347 }(); 324 .enabled{false},
348 325 .port{},
349 ctx.WriteBuffer(network_profile_data); 326 .proxy_server{},
327 .automatic_auth_enabled{},
328 .user{},
329 .password{},
330 },
331 .mtu{1500},
332 },
333 .uuid{0xdeadbeef, 0xdeadbeef},
334 .network_name{"yuzu Network"},
335 .wireless_setting_data{
336 .ssid_length{12},
337 .ssid{"yuzu Network"},
338 .passphrase{"yuzupassword"},
339 },
340 };
341 }();
350 342
351 IPC::ResponseBuilder rb{ctx, 2}; 343 // When we're connected to a room, spoof the hosts IP address
352 rb.Push(ResultSuccess); 344 if (auto room_member = network.GetRoomMember().lock()) {
345 if (room_member->IsConnected()) {
346 network_profile_data.ip_setting_data.ip_address_setting.current_address =
347 room_member->GetFakeIpAddress();
348 }
353 } 349 }
354 350
355 void RemoveNetworkProfile(Kernel::HLERequestContext& ctx) { 351 ctx.WriteBuffer(network_profile_data);
356 LOG_WARNING(Service_NIFM, "(STUBBED) called");
357 352
358 IPC::ResponseBuilder rb{ctx, 2}; 353 IPC::ResponseBuilder rb{ctx, 2};
359 rb.Push(ResultSuccess); 354 rb.Push(ResultSuccess);
360 } 355}
361 356
362 void GetCurrentIpAddress(Kernel::HLERequestContext& ctx) { 357void IGeneralService::RemoveNetworkProfile(Kernel::HLERequestContext& ctx) {
363 LOG_WARNING(Service_NIFM, "(STUBBED) called"); 358 LOG_WARNING(Service_NIFM, "(STUBBED) called");
364 359
365 auto ipv4 = Network::GetHostIPv4Address(); 360 IPC::ResponseBuilder rb{ctx, 2};
366 if (!ipv4) { 361 rb.Push(ResultSuccess);
367 LOG_ERROR(Service_NIFM, "Couldn't get host IPv4 address, defaulting to 0.0.0.0"); 362}
368 ipv4.emplace(Network::IPv4Address{0, 0, 0, 0});
369 }
370 363
371 IPC::ResponseBuilder rb{ctx, 3}; 364void IGeneralService::GetCurrentIpAddress(Kernel::HLERequestContext& ctx) {
372 rb.Push(ResultSuccess); 365 LOG_WARNING(Service_NIFM, "(STUBBED) called");
373 rb.PushRaw(*ipv4); 366
367 auto ipv4 = Network::GetHostIPv4Address();
368 if (!ipv4) {
369 LOG_ERROR(Service_NIFM, "Couldn't get host IPv4 address, defaulting to 0.0.0.0");
370 ipv4.emplace(Network::IPv4Address{0, 0, 0, 0});
374 } 371 }
375 372
376 void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) { 373 // When we're connected to a room, spoof the hosts IP address
377 LOG_DEBUG(Service_NIFM, "called"); 374 if (auto room_member = network.GetRoomMember().lock()) {
375 if (room_member->IsConnected()) {
376 ipv4 = room_member->GetFakeIpAddress();
377 }
378 }
378 379
379 ASSERT_MSG(ctx.GetReadBufferSize() == 0x17c, 380 IPC::ResponseBuilder rb{ctx, 3};
380 "SfNetworkProfileData is not the correct size"); 381 rb.Push(ResultSuccess);
381 u128 uuid{}; 382 rb.PushRaw(*ipv4);
382 auto buffer = ctx.ReadBuffer(); 383}
383 std::memcpy(&uuid, buffer.data() + 8, sizeof(u128));
384 384
385 IPC::ResponseBuilder rb{ctx, 6, 0, 1}; 385void IGeneralService::CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) {
386 LOG_DEBUG(Service_NIFM, "called");
386 387
387 rb.Push(ResultSuccess); 388 ASSERT_MSG(ctx.GetReadBufferSize() == 0x17c, "SfNetworkProfileData is not the correct size");
388 rb.PushIpcInterface<INetworkProfile>(system); 389 u128 uuid{};
389 rb.PushRaw<u128>(uuid); 390 auto buffer = ctx.ReadBuffer();
390 } 391 std::memcpy(&uuid, buffer.data() + 8, sizeof(u128));
391 392
392 void GetCurrentIpConfigInfo(Kernel::HLERequestContext& ctx) { 393 IPC::ResponseBuilder rb{ctx, 6, 0, 1};
393 LOG_WARNING(Service_NIFM, "(STUBBED) called");
394 394
395 struct IpConfigInfo { 395 rb.Push(ResultSuccess);
396 IpAddressSetting ip_address_setting{}; 396 rb.PushIpcInterface<INetworkProfile>(system);
397 DnsSetting dns_setting{}; 397 rb.PushRaw<u128>(uuid);
398 }; 398}
399 static_assert(sizeof(IpConfigInfo) == sizeof(IpAddressSetting) + sizeof(DnsSetting),
400 "IpConfigInfo has incorrect size.");
401 399
402 const auto net_iface = Network::GetSelectedNetworkInterface(); 400void IGeneralService::GetCurrentIpConfigInfo(Kernel::HLERequestContext& ctx) {
401 LOG_WARNING(Service_NIFM, "(STUBBED) called");
403 402
404 const IpConfigInfo ip_config_info = [&net_iface] { 403 struct IpConfigInfo {
405 if (!net_iface) { 404 IpAddressSetting ip_address_setting{};
406 return IpConfigInfo{}; 405 DnsSetting dns_setting{};
407 } 406 };
407 static_assert(sizeof(IpConfigInfo) == sizeof(IpAddressSetting) + sizeof(DnsSetting),
408 "IpConfigInfo has incorrect size.");
408 409
409 return IpConfigInfo{ 410 const auto net_iface = Network::GetSelectedNetworkInterface();
410 .ip_address_setting{
411 .is_automatic{true},
412 .current_address{Network::TranslateIPv4(net_iface->ip_address)},
413 .subnet_mask{Network::TranslateIPv4(net_iface->subnet_mask)},
414 .gateway{Network::TranslateIPv4(net_iface->gateway)},
415 },
416 .dns_setting{
417 .is_automatic{true},
418 .primary_dns{1, 1, 1, 1},
419 .secondary_dns{1, 0, 0, 1},
420 },
421 };
422 }();
423 411
424 IPC::ResponseBuilder rb{ctx, 2 + (sizeof(IpConfigInfo) + 3) / sizeof(u32)}; 412 IpConfigInfo ip_config_info = [&net_iface] {
425 rb.Push(ResultSuccess); 413 if (!net_iface) {
426 rb.PushRaw<IpConfigInfo>(ip_config_info); 414 return IpConfigInfo{};
427 } 415 }
428 416
429 void IsWirelessCommunicationEnabled(Kernel::HLERequestContext& ctx) { 417 return IpConfigInfo{
430 LOG_WARNING(Service_NIFM, "(STUBBED) called"); 418 .ip_address_setting{
419 .is_automatic{true},
420 .current_address{Network::TranslateIPv4(net_iface->ip_address)},
421 .subnet_mask{Network::TranslateIPv4(net_iface->subnet_mask)},
422 .gateway{Network::TranslateIPv4(net_iface->gateway)},
423 },
424 .dns_setting{
425 .is_automatic{true},
426 .primary_dns{1, 1, 1, 1},
427 .secondary_dns{1, 0, 0, 1},
428 },
429 };
430 }();
431 431
432 IPC::ResponseBuilder rb{ctx, 3}; 432 // When we're connected to a room, spoof the hosts IP address
433 rb.Push(ResultSuccess); 433 if (auto room_member = network.GetRoomMember().lock()) {
434 rb.Push<u8>(0); 434 if (room_member->IsConnected()) {
435 ip_config_info.ip_address_setting.current_address = room_member->GetFakeIpAddress();
436 }
435 } 437 }
436 438
437 void GetInternetConnectionStatus(Kernel::HLERequestContext& ctx) { 439 IPC::ResponseBuilder rb{ctx, 2 + (sizeof(IpConfigInfo) + 3) / sizeof(u32)};
438 LOG_WARNING(Service_NIFM, "(STUBBED) called"); 440 rb.Push(ResultSuccess);
441 rb.PushRaw<IpConfigInfo>(ip_config_info);
442}
439 443
440 struct Output { 444void IGeneralService::IsWirelessCommunicationEnabled(Kernel::HLERequestContext& ctx) {
441 InternetConnectionType type{InternetConnectionType::WiFi}; 445 LOG_WARNING(Service_NIFM, "(STUBBED) called");
442 u8 wifi_strength{3};
443 InternetConnectionStatus state{InternetConnectionStatus::Connected};
444 };
445 static_assert(sizeof(Output) == 0x3, "Output has incorrect size.");
446 446
447 constexpr Output out{}; 447 IPC::ResponseBuilder rb{ctx, 3};
448 rb.Push(ResultSuccess);
449 rb.Push<u8>(1);
450}
448 451
449 IPC::ResponseBuilder rb{ctx, 3}; 452void IGeneralService::GetInternetConnectionStatus(Kernel::HLERequestContext& ctx) {
450 rb.Push(ResultSuccess); 453 LOG_WARNING(Service_NIFM, "(STUBBED) called");
451 rb.PushRaw(out);
452 }
453 454
454 void IsEthernetCommunicationEnabled(Kernel::HLERequestContext& ctx) { 455 struct Output {
455 LOG_WARNING(Service_NIFM, "(STUBBED) called"); 456 InternetConnectionType type{InternetConnectionType::WiFi};
457 u8 wifi_strength{3};
458 InternetConnectionStatus state{InternetConnectionStatus::Connected};
459 };
460 static_assert(sizeof(Output) == 0x3, "Output has incorrect size.");
456 461
457 IPC::ResponseBuilder rb{ctx, 3}; 462 constexpr Output out{};
458 rb.Push(ResultSuccess); 463
459 if (Network::GetHostIPv4Address().has_value()) { 464 IPC::ResponseBuilder rb{ctx, 3};
460 rb.Push<u8>(1); 465 rb.Push(ResultSuccess);
461 } else { 466 rb.PushRaw(out);
462 rb.Push<u8>(0); 467}
463 } 468
469void IGeneralService::IsEthernetCommunicationEnabled(Kernel::HLERequestContext& ctx) {
470 LOG_WARNING(Service_NIFM, "(STUBBED) called");
471
472 IPC::ResponseBuilder rb{ctx, 3};
473 rb.Push(ResultSuccess);
474 if (Network::GetHostIPv4Address().has_value()) {
475 rb.Push<u8>(1);
476 } else {
477 rb.Push<u8>(0);
464 } 478 }
479}
465 480
466 void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) { 481void IGeneralService::IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) {
467 LOG_WARNING(Service_NIFM, "(STUBBED) called"); 482 LOG_ERROR(Service_NIFM, "(STUBBED) called");
468 483
469 IPC::ResponseBuilder rb{ctx, 3}; 484 IPC::ResponseBuilder rb{ctx, 3};
470 rb.Push(ResultSuccess); 485 rb.Push(ResultSuccess);
471 if (Network::GetHostIPv4Address().has_value()) { 486 if (Network::GetHostIPv4Address().has_value()) {
472 rb.Push<u8>(1); 487 rb.Push<u8>(1);
473 } else { 488 } else {
474 rb.Push<u8>(0); 489 rb.Push<u8>(0);
475 }
476 } 490 }
477}; 491}
478 492
479IGeneralService::IGeneralService(Core::System& system_) 493IGeneralService::IGeneralService(Core::System& system_)
480 : ServiceFramework{system_, "IGeneralService"} { 494 : ServiceFramework{system_, "IGeneralService"}, network{system_.GetRoomNetwork()} {
481 // clang-format off 495 // clang-format off
482 static const FunctionInfo functions[] = { 496 static const FunctionInfo functions[] = {
483 {1, &IGeneralService::GetClientId, "GetClientId"}, 497 {1, &IGeneralService::GetClientId, "GetClientId"},
@@ -528,6 +542,8 @@ IGeneralService::IGeneralService(Core::System& system_)
528 RegisterHandlers(functions); 542 RegisterHandlers(functions);
529} 543}
530 544
545IGeneralService::~IGeneralService() = default;
546
531class NetworkInterface final : public ServiceFramework<NetworkInterface> { 547class NetworkInterface final : public ServiceFramework<NetworkInterface> {
532public: 548public:
533 explicit NetworkInterface(const char* name, Core::System& system_) 549 explicit NetworkInterface(const char* name, Core::System& system_)
diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h
index 5f62d0014..48161be28 100644
--- a/src/core/hle/service/nifm/nifm.h
+++ b/src/core/hle/service/nifm/nifm.h
@@ -3,6 +3,11 @@
3 3
4#pragma once 4#pragma once
5 5
6#include "core/hle/service/service.h"
7#include "network/network.h"
8#include "network/room.h"
9#include "network/room_member.h"
10
6namespace Core { 11namespace Core {
7class System; 12class System;
8} 13}
@@ -16,4 +21,26 @@ namespace Service::NIFM {
16/// Registers all NIFM services with the specified service manager. 21/// Registers all NIFM services with the specified service manager.
17void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); 22void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
18 23
24class IGeneralService final : public ServiceFramework<IGeneralService> {
25public:
26 explicit IGeneralService(Core::System& system_);
27 ~IGeneralService() override;
28
29private:
30 void GetClientId(Kernel::HLERequestContext& ctx);
31 void CreateScanRequest(Kernel::HLERequestContext& ctx);
32 void CreateRequest(Kernel::HLERequestContext& ctx);
33 void GetCurrentNetworkProfile(Kernel::HLERequestContext& ctx);
34 void RemoveNetworkProfile(Kernel::HLERequestContext& ctx);
35 void GetCurrentIpAddress(Kernel::HLERequestContext& ctx);
36 void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx);
37 void GetCurrentIpConfigInfo(Kernel::HLERequestContext& ctx);
38 void IsWirelessCommunicationEnabled(Kernel::HLERequestContext& ctx);
39 void GetInternetConnectionStatus(Kernel::HLERequestContext& ctx);
40 void IsEthernetCommunicationEnabled(Kernel::HLERequestContext& ctx);
41 void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx);
42
43 Network::RoomNetwork& network;
44};
45
19} // namespace Service::NIFM 46} // namespace Service::NIFM
diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/iplatform_service_manager.cpp
index cc11f3e08..fd047ff26 100644
--- a/src/core/hle/service/ns/pl_u.cpp
+++ b/src/core/hle/service/ns/iplatform_service_manager.cpp
@@ -20,7 +20,7 @@
20#include "core/hle/kernel/kernel.h" 20#include "core/hle/kernel/kernel.h"
21#include "core/hle/kernel/physical_memory.h" 21#include "core/hle/kernel/physical_memory.h"
22#include "core/hle/service/filesystem/filesystem.h" 22#include "core/hle/service/filesystem/filesystem.h"
23#include "core/hle/service/ns/pl_u.h" 23#include "core/hle/service/ns/iplatform_service_manager.h"
24 24
25namespace Service::NS { 25namespace Service::NS {
26 26
@@ -99,7 +99,7 @@ static u32 GetU32Swapped(const u8* data) {
99 return Common::swap32(value); 99 return Common::swap32(value);
100} 100}
101 101
102struct PL_U::Impl { 102struct IPlatformServiceManager::Impl {
103 const FontRegion& GetSharedFontRegion(std::size_t index) const { 103 const FontRegion& GetSharedFontRegion(std::size_t index) const {
104 if (index >= shared_font_regions.size() || shared_font_regions.empty()) { 104 if (index >= shared_font_regions.size() || shared_font_regions.empty()) {
105 // No font fallback 105 // No font fallback
@@ -134,16 +134,16 @@ struct PL_U::Impl {
134 std::vector<FontRegion> shared_font_regions; 134 std::vector<FontRegion> shared_font_regions;
135}; 135};
136 136
137PL_U::PL_U(Core::System& system_) 137IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const char* service_name_)
138 : ServiceFramework{system_, "pl:u"}, impl{std::make_unique<Impl>()} { 138 : ServiceFramework{system_, service_name_}, impl{std::make_unique<Impl>()} {
139 // clang-format off 139 // clang-format off
140 static const FunctionInfo functions[] = { 140 static const FunctionInfo functions[] = {
141 {0, &PL_U::RequestLoad, "RequestLoad"}, 141 {0, &IPlatformServiceManager::RequestLoad, "RequestLoad"},
142 {1, &PL_U::GetLoadState, "GetLoadState"}, 142 {1, &IPlatformServiceManager::GetLoadState, "GetLoadState"},
143 {2, &PL_U::GetSize, "GetSize"}, 143 {2, &IPlatformServiceManager::GetSize, "GetSize"},
144 {3, &PL_U::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"}, 144 {3, &IPlatformServiceManager::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"},
145 {4, &PL_U::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"}, 145 {4, &IPlatformServiceManager::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"},
146 {5, &PL_U::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"}, 146 {5, &IPlatformServiceManager::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"},
147 {6, nullptr, "GetSharedFontInOrderOfPriorityForSystem"}, 147 {6, nullptr, "GetSharedFontInOrderOfPriorityForSystem"},
148 {100, nullptr, "RequestApplicationFunctionAuthorization"}, 148 {100, nullptr, "RequestApplicationFunctionAuthorization"},
149 {101, nullptr, "RequestApplicationFunctionAuthorizationByProcessId"}, 149 {101, nullptr, "RequestApplicationFunctionAuthorizationByProcessId"},
@@ -206,9 +206,9 @@ PL_U::PL_U(Core::System& system_)
206 } 206 }
207} 207}
208 208
209PL_U::~PL_U() = default; 209IPlatformServiceManager::~IPlatformServiceManager() = default;
210 210
211void PL_U::RequestLoad(Kernel::HLERequestContext& ctx) { 211void IPlatformServiceManager::RequestLoad(Kernel::HLERequestContext& ctx) {
212 IPC::RequestParser rp{ctx}; 212 IPC::RequestParser rp{ctx};
213 const u32 shared_font_type{rp.Pop<u32>()}; 213 const u32 shared_font_type{rp.Pop<u32>()};
214 // Games don't call this so all fonts should be loaded 214 // Games don't call this so all fonts should be loaded
@@ -218,7 +218,7 @@ void PL_U::RequestLoad(Kernel::HLERequestContext& ctx) {
218 rb.Push(ResultSuccess); 218 rb.Push(ResultSuccess);
219} 219}
220 220
221void PL_U::GetLoadState(Kernel::HLERequestContext& ctx) { 221void IPlatformServiceManager::GetLoadState(Kernel::HLERequestContext& ctx) {
222 IPC::RequestParser rp{ctx}; 222 IPC::RequestParser rp{ctx};
223 const u32 font_id{rp.Pop<u32>()}; 223 const u32 font_id{rp.Pop<u32>()};
224 LOG_DEBUG(Service_NS, "called, font_id={}", font_id); 224 LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
@@ -228,7 +228,7 @@ void PL_U::GetLoadState(Kernel::HLERequestContext& ctx) {
228 rb.Push<u32>(static_cast<u32>(LoadState::Done)); 228 rb.Push<u32>(static_cast<u32>(LoadState::Done));
229} 229}
230 230
231void PL_U::GetSize(Kernel::HLERequestContext& ctx) { 231void IPlatformServiceManager::GetSize(Kernel::HLERequestContext& ctx) {
232 IPC::RequestParser rp{ctx}; 232 IPC::RequestParser rp{ctx};
233 const u32 font_id{rp.Pop<u32>()}; 233 const u32 font_id{rp.Pop<u32>()};
234 LOG_DEBUG(Service_NS, "called, font_id={}", font_id); 234 LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
@@ -238,7 +238,7 @@ void PL_U::GetSize(Kernel::HLERequestContext& ctx) {
238 rb.Push<u32>(impl->GetSharedFontRegion(font_id).size); 238 rb.Push<u32>(impl->GetSharedFontRegion(font_id).size);
239} 239}
240 240
241void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) { 241void IPlatformServiceManager::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) {
242 IPC::RequestParser rp{ctx}; 242 IPC::RequestParser rp{ctx};
243 const u32 font_id{rp.Pop<u32>()}; 243 const u32 font_id{rp.Pop<u32>()};
244 LOG_DEBUG(Service_NS, "called, font_id={}", font_id); 244 LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
@@ -248,7 +248,7 @@ void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) {
248 rb.Push<u32>(impl->GetSharedFontRegion(font_id).offset); 248 rb.Push<u32>(impl->GetSharedFontRegion(font_id).offset);
249} 249}
250 250
251void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { 251void IPlatformServiceManager::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) {
252 // Map backing memory for the font data 252 // Map backing memory for the font data
253 LOG_DEBUG(Service_NS, "called"); 253 LOG_DEBUG(Service_NS, "called");
254 254
@@ -261,7 +261,7 @@ void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) {
261 rb.PushCopyObjects(&kernel.GetFontSharedMem()); 261 rb.PushCopyObjects(&kernel.GetFontSharedMem());
262} 262}
263 263
264void PL_U::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) { 264void IPlatformServiceManager::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) {
265 IPC::RequestParser rp{ctx}; 265 IPC::RequestParser rp{ctx};
266 const u64 language_code{rp.Pop<u64>()}; // TODO(ogniK): Find out what this is used for 266 const u64 language_code{rp.Pop<u64>()}; // TODO(ogniK): Find out what this is used for
267 LOG_DEBUG(Service_NS, "called, language_code={:X}", language_code); 267 LOG_DEBUG(Service_NS, "called, language_code={:X}", language_code);
diff --git a/src/core/hle/service/ns/pl_u.h b/src/core/hle/service/ns/iplatform_service_manager.h
index 07d0ac934..ed6eda89f 100644
--- a/src/core/hle/service/ns/pl_u.h
+++ b/src/core/hle/service/ns/iplatform_service_manager.h
@@ -36,10 +36,10 @@ constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{
36void DecryptSharedFontToTTF(const std::vector<u32>& input, std::vector<u8>& output); 36void DecryptSharedFontToTTF(const std::vector<u32>& input, std::vector<u8>& output);
37void EncryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output, std::size_t& offset); 37void EncryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output, std::size_t& offset);
38 38
39class PL_U final : public ServiceFramework<PL_U> { 39class IPlatformServiceManager final : public ServiceFramework<IPlatformServiceManager> {
40public: 40public:
41 explicit PL_U(Core::System& system_); 41 explicit IPlatformServiceManager(Core::System& system_, const char* service_name_);
42 ~PL_U() override; 42 ~IPlatformServiceManager() override;
43 43
44private: 44private:
45 void RequestLoad(Kernel::HLERequestContext& ctx); 45 void RequestLoad(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index aafc8fe03..f7318c3cb 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -9,10 +9,10 @@
9#include "core/file_sys/vfs.h" 9#include "core/file_sys/vfs.h"
10#include "core/hle/ipc_helpers.h" 10#include "core/hle/ipc_helpers.h"
11#include "core/hle/service/ns/errors.h" 11#include "core/hle/service/ns/errors.h"
12#include "core/hle/service/ns/iplatform_service_manager.h"
12#include "core/hle/service/ns/language.h" 13#include "core/hle/service/ns/language.h"
13#include "core/hle/service/ns/ns.h" 14#include "core/hle/service/ns/ns.h"
14#include "core/hle/service/ns/pdm_qry.h" 15#include "core/hle/service/ns/pdm_qry.h"
15#include "core/hle/service/ns/pl_u.h"
16#include "core/hle/service/set/set.h" 16#include "core/hle/service/set/set.h"
17 17
18namespace Service::NS { 18namespace Service::NS {
@@ -764,7 +764,8 @@ void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system
764 764
765 std::make_shared<PDM_QRY>(system)->InstallAsService(service_manager); 765 std::make_shared<PDM_QRY>(system)->InstallAsService(service_manager);
766 766
767 std::make_shared<PL_U>(system)->InstallAsService(service_manager); 767 std::make_shared<IPlatformServiceManager>(system, "pl:s")->InstallAsService(service_manager);
768 std::make_shared<IPlatformServiceManager>(system, "pl:u")->InstallAsService(service_manager);
768} 769}
769 770
770} // namespace Service::NS 771} // namespace Service::NS
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
index 2a5128c60..a7385fce8 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
@@ -1,6 +1,7 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/audio_core.h"
4#include "common/assert.h" 5#include "common/assert.h"
5#include "common/logging/log.h" 6#include "common/logging/log.h"
6#include "core/core.h" 7#include "core/core.h"
@@ -65,7 +66,10 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>&
65 return NvResult::NotImplemented; 66 return NvResult::NotImplemented;
66} 67}
67 68
68void nvhost_nvdec::OnOpen(DeviceFD fd) {} 69void nvhost_nvdec::OnOpen(DeviceFD fd) {
70 LOG_INFO(Service_NVDRV, "NVDEC video stream started");
71 system.AudioCore().SetNVDECActive(true);
72}
69 73
70void nvhost_nvdec::OnClose(DeviceFD fd) { 74void nvhost_nvdec::OnClose(DeviceFD fd) {
71 LOG_INFO(Service_NVDRV, "NVDEC video stream ended"); 75 LOG_INFO(Service_NVDRV, "NVDEC video stream ended");
@@ -73,6 +77,7 @@ void nvhost_nvdec::OnClose(DeviceFD fd) {
73 if (iter != fd_to_id.end()) { 77 if (iter != fd_to_id.end()) {
74 system.GPU().ClearCdmaInstance(iter->second); 78 system.GPU().ClearCdmaInstance(iter->second);
75 } 79 }
80 system.AudioCore().SetNVDECActive(false);
76} 81}
77 82
78} // namespace Service::Nvidia::Devices 83} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp
index 728bfa00b..d8518149d 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp
@@ -198,7 +198,7 @@ NvResult nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output)
198 IocParamParams params; 198 IocParamParams params;
199 std::memcpy(&params, input.data(), sizeof(params)); 199 std::memcpy(&params, input.data(), sizeof(params));
200 200
201 LOG_WARNING(Service_NVDRV, "(STUBBED) called type={}", params.param); 201 LOG_DEBUG(Service_NVDRV, "(STUBBED) called type={}", params.param);
202 202
203 auto object = GetObject(params.handle); 203 auto object = GetObject(params.handle);
204 if (!object) { 204 if (!object) {
@@ -243,7 +243,7 @@ NvResult nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
243 IocFreeParams params; 243 IocFreeParams params;
244 std::memcpy(&params, input.data(), sizeof(params)); 244 std::memcpy(&params, input.data(), sizeof(params));
245 245
246 LOG_WARNING(Service_NVDRV, "(STUBBED) called"); 246 LOG_DEBUG(Service_NVDRV, "(STUBBED) called");
247 247
248 auto itr = handles.find(params.handle); 248 auto itr = handles.find(params.handle);
249 if (itr == handles.end()) { 249 if (itr == handles.end()) {
diff --git a/src/core/hle/service/pcv/pcv.cpp b/src/core/hle/service/pcv/pcv.cpp
index 0989474be..f7a497a14 100644
--- a/src/core/hle/service/pcv/pcv.cpp
+++ b/src/core/hle/service/pcv/pcv.cpp
@@ -3,6 +3,7 @@
3 3
4#include <memory> 4#include <memory>
5 5
6#include "core/hle/ipc_helpers.h"
6#include "core/hle/service/pcv/pcv.h" 7#include "core/hle/service/pcv/pcv.h"
7#include "core/hle/service/service.h" 8#include "core/hle/service/service.h"
8#include "core/hle/service/sm/sm.h" 9#include "core/hle/service/sm/sm.h"
@@ -77,10 +78,102 @@ public:
77 } 78 }
78}; 79};
79 80
81class IClkrstSession final : public ServiceFramework<IClkrstSession> {
82public:
83 explicit IClkrstSession(Core::System& system_, DeviceCode deivce_code_)
84 : ServiceFramework{system_, "IClkrstSession"}, deivce_code(deivce_code_) {
85 // clang-format off
86 static const FunctionInfo functions[] = {
87 {0, nullptr, "SetClockEnabled"},
88 {1, nullptr, "SetClockDisabled"},
89 {2, nullptr, "SetResetAsserted"},
90 {3, nullptr, "SetResetDeasserted"},
91 {4, nullptr, "SetPowerEnabled"},
92 {5, nullptr, "SetPowerDisabled"},
93 {6, nullptr, "GetState"},
94 {7, &IClkrstSession::SetClockRate, "SetClockRate"},
95 {8, &IClkrstSession::GetClockRate, "GetClockRate"},
96 {9, nullptr, "SetMinVClockRate"},
97 {10, nullptr, "GetPossibleClockRates"},
98 {11, nullptr, "GetDvfsTable"},
99 };
100 // clang-format on
101 RegisterHandlers(functions);
102 }
103
104private:
105 void SetClockRate(Kernel::HLERequestContext& ctx) {
106 IPC::RequestParser rp{ctx};
107 clock_rate = rp.Pop<u32>();
108 LOG_DEBUG(Service_PCV, "(STUBBED) called, clock_rate={}", clock_rate);
109
110 IPC::ResponseBuilder rb{ctx, 2};
111 rb.Push(ResultSuccess);
112 }
113
114 void GetClockRate(Kernel::HLERequestContext& ctx) {
115 LOG_DEBUG(Service_PCV, "(STUBBED) called");
116
117 IPC::ResponseBuilder rb{ctx, 3};
118 rb.Push(ResultSuccess);
119 rb.Push<u32>(clock_rate);
120 }
121
122 DeviceCode deivce_code;
123 u32 clock_rate{};
124};
125
126class CLKRST final : public ServiceFramework<CLKRST> {
127public:
128 explicit CLKRST(Core::System& system_, const char* name) : ServiceFramework{system_, name} {
129 // clang-format off
130 static const FunctionInfo functions[] = {
131 {0, &CLKRST::OpenSession, "OpenSession"},
132 {1, nullptr, "GetTemperatureThresholds"},
133 {2, nullptr, "SetTemperature"},
134 {3, nullptr, "GetModuleStateTable"},
135 {4, nullptr, "GetModuleStateTableEvent"},
136 {5, nullptr, "GetModuleStateTableMaxCount"},
137 };
138 // clang-format on
139
140 RegisterHandlers(functions);
141 }
142
143private:
144 void OpenSession(Kernel::HLERequestContext& ctx) {
145 IPC::RequestParser rp{ctx};
146 const auto device_code = static_cast<DeviceCode>(rp.Pop<u32>());
147 const auto unkonwn_input = rp.Pop<u32>();
148
149 LOG_DEBUG(Service_PCV, "called, device_code={}, input={}", device_code, unkonwn_input);
150
151 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
152 rb.Push(ResultSuccess);
153 rb.PushIpcInterface<IClkrstSession>(system, device_code);
154 }
155};
156
157class CLKRST_A final : public ServiceFramework<CLKRST_A> {
158public:
159 explicit CLKRST_A(Core::System& system_) : ServiceFramework{system_, "clkrst:a"} {
160 // clang-format off
161 static const FunctionInfo functions[] = {
162 {0, nullptr, "ReleaseControl"},
163 };
164 // clang-format on
165
166 RegisterHandlers(functions);
167 }
168};
169
80void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { 170void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
81 std::make_shared<PCV>(system)->InstallAsService(sm); 171 std::make_shared<PCV>(system)->InstallAsService(sm);
82 std::make_shared<PCV_ARB>(system)->InstallAsService(sm); 172 std::make_shared<PCV_ARB>(system)->InstallAsService(sm);
83 std::make_shared<PCV_IMM>(system)->InstallAsService(sm); 173 std::make_shared<PCV_IMM>(system)->InstallAsService(sm);
174 std::make_shared<CLKRST>(system, "clkrst")->InstallAsService(sm);
175 std::make_shared<CLKRST>(system, "clkrst:i")->InstallAsService(sm);
176 std::make_shared<CLKRST_A>(system)->InstallAsService(sm);
84} 177}
85 178
86} // namespace Service::PCV 179} // namespace Service::PCV
diff --git a/src/core/hle/service/pcv/pcv.h b/src/core/hle/service/pcv/pcv.h
index a42e6f8f6..6b26b6fa7 100644
--- a/src/core/hle/service/pcv/pcv.h
+++ b/src/core/hle/service/pcv/pcv.h
@@ -13,6 +13,97 @@ class ServiceManager;
13 13
14namespace Service::PCV { 14namespace Service::PCV {
15 15
16enum class DeviceCode : u32 {
17 Cpu = 0x40000001,
18 Gpu = 0x40000002,
19 I2s1 = 0x40000003,
20 I2s2 = 0x40000004,
21 I2s3 = 0x40000005,
22 Pwm = 0x40000006,
23 I2c1 = 0x02000001,
24 I2c2 = 0x02000002,
25 I2c3 = 0x02000003,
26 I2c4 = 0x02000004,
27 I2c5 = 0x02000005,
28 I2c6 = 0x02000006,
29 Spi1 = 0x07000000,
30 Spi2 = 0x07000001,
31 Spi3 = 0x07000002,
32 Spi4 = 0x07000003,
33 Disp1 = 0x40000011,
34 Disp2 = 0x40000012,
35 Isp = 0x40000013,
36 Vi = 0x40000014,
37 Sdmmc1 = 0x40000015,
38 Sdmmc2 = 0x40000016,
39 Sdmmc3 = 0x40000017,
40 Sdmmc4 = 0x40000018,
41 Owr = 0x40000019,
42 Csite = 0x4000001A,
43 Tsec = 0x4000001B,
44 Mselect = 0x4000001C,
45 Hda2codec2x = 0x4000001D,
46 Actmon = 0x4000001E,
47 I2cSlow = 0x4000001F,
48 Sor1 = 0x40000020,
49 Sata = 0x40000021,
50 Hda = 0x40000022,
51 XusbCoreHostSrc = 0x40000023,
52 XusbFalconSrc = 0x40000024,
53 XusbFsSrc = 0x40000025,
54 XusbCoreDevSrc = 0x40000026,
55 XusbSsSrc = 0x40000027,
56 UartA = 0x03000001,
57 UartB = 0x35000405,
58 UartC = 0x3500040F,
59 UartD = 0x37000001,
60 Host1x = 0x4000002C,
61 Entropy = 0x4000002D,
62 SocTherm = 0x4000002E,
63 Vic = 0x4000002F,
64 Nvenc = 0x40000030,
65 Nvjpg = 0x40000031,
66 Nvdec = 0x40000032,
67 Qspi = 0x40000033,
68 ViI2c = 0x40000034,
69 Tsecb = 0x40000035,
70 Ape = 0x40000036,
71 AudioDsp = 0x40000037,
72 AudioUart = 0x40000038,
73 Emc = 0x40000039,
74 Plle = 0x4000003A,
75 PlleHwSeq = 0x4000003B,
76 Dsi = 0x4000003C,
77 Maud = 0x4000003D,
78 Dpaux1 = 0x4000003E,
79 MipiCal = 0x4000003F,
80 UartFstMipiCal = 0x40000040,
81 Osc = 0x40000041,
82 SysBus = 0x40000042,
83 SorSafe = 0x40000043,
84 XusbSs = 0x40000044,
85 XusbHost = 0x40000045,
86 XusbDevice = 0x40000046,
87 Extperiph1 = 0x40000047,
88 Ahub = 0x40000048,
89 Hda2hdmicodec = 0x40000049,
90 Gpuaux = 0x4000004A,
91 UsbD = 0x4000004B,
92 Usb2 = 0x4000004C,
93 Pcie = 0x4000004D,
94 Afi = 0x4000004E,
95 PciExClk = 0x4000004F,
96 PExUsbPhy = 0x40000050,
97 XUsbPadCtl = 0x40000051,
98 Apbdma = 0x40000052,
99 Usb2TrkClk = 0x40000053,
100 XUsbIoPll = 0x40000054,
101 XUsbIoPllHwSeq = 0x40000055,
102 Cec = 0x40000056,
103 Extperiph2 = 0x40000057,
104 OscClk = 0x40000080
105};
106
16void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); 107void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
17 108
18} // namespace Service::PCV 109} // namespace Service::PCV
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index c64291e7f..dadaf897f 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -194,13 +194,16 @@ Result ServiceFrameworkBase::HandleSyncRequest(Kernel::KServerSession& session,
194 Kernel::HLERequestContext& ctx) { 194 Kernel::HLERequestContext& ctx) {
195 const auto guard = LockService(); 195 const auto guard = LockService();
196 196
197 Result result = ResultSuccess;
198
197 switch (ctx.GetCommandType()) { 199 switch (ctx.GetCommandType()) {
198 case IPC::CommandType::Close: 200 case IPC::CommandType::Close:
199 case IPC::CommandType::TIPC_Close: { 201 case IPC::CommandType::TIPC_Close: {
200 session.Close(); 202 session.Close();
201 IPC::ResponseBuilder rb{ctx, 2}; 203 IPC::ResponseBuilder rb{ctx, 2};
202 rb.Push(ResultSuccess); 204 rb.Push(ResultSuccess);
203 return IPC::ERR_REMOTE_PROCESS_DEAD; 205 result = IPC::ERR_REMOTE_PROCESS_DEAD;
206 break;
204 } 207 }
205 case IPC::CommandType::ControlWithContext: 208 case IPC::CommandType::ControlWithContext:
206 case IPC::CommandType::Control: { 209 case IPC::CommandType::Control: {
@@ -227,7 +230,7 @@ Result ServiceFrameworkBase::HandleSyncRequest(Kernel::KServerSession& session,
227 ctx.WriteToOutgoingCommandBuffer(ctx.GetThread()); 230 ctx.WriteToOutgoingCommandBuffer(ctx.GetThread());
228 } 231 }
229 232
230 return ResultSuccess; 233 return result;
231} 234}
232 235
233/// Initialize Services 236/// Initialize Services
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index c7194731e..cc679cc81 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -9,12 +9,16 @@
9#include <fmt/format.h> 9#include <fmt/format.h>
10 10
11#include "common/microprofile.h" 11#include "common/microprofile.h"
12#include "common/socket_types.h"
13#include "core/core.h"
12#include "core/hle/ipc_helpers.h" 14#include "core/hle/ipc_helpers.h"
13#include "core/hle/kernel/k_thread.h" 15#include "core/hle/kernel/k_thread.h"
14#include "core/hle/service/sockets/bsd.h" 16#include "core/hle/service/sockets/bsd.h"
15#include "core/hle/service/sockets/sockets_translate.h" 17#include "core/hle/service/sockets/sockets_translate.h"
16#include "core/internal_network/network.h" 18#include "core/internal_network/network.h"
19#include "core/internal_network/socket_proxy.h"
17#include "core/internal_network/sockets.h" 20#include "core/internal_network/sockets.h"
21#include "network/network.h"
18 22
19namespace Service::Sockets { 23namespace Service::Sockets {
20 24
@@ -472,7 +476,13 @@ std::pair<s32, Errno> BSD::SocketImpl(Domain domain, Type type, Protocol protoco
472 476
473 LOG_INFO(Service, "New socket fd={}", fd); 477 LOG_INFO(Service, "New socket fd={}", fd);
474 478
475 descriptor.socket = std::make_unique<Network::Socket>(); 479 auto room_member = room_network.GetRoomMember().lock();
480 if (room_member && room_member->IsConnected()) {
481 descriptor.socket = std::make_unique<Network::ProxySocket>(room_network);
482 } else {
483 descriptor.socket = std::make_unique<Network::Socket>();
484 }
485
476 descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(type, protocol)); 486 descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(type, protocol));
477 descriptor.is_connection_based = IsConnectionBased(type); 487 descriptor.is_connection_based = IsConnectionBased(type);
478 488
@@ -648,7 +658,7 @@ std::pair<s32, Errno> BSD::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) {
648 ASSERT(arg == 0); 658 ASSERT(arg == 0);
649 return {descriptor.flags, Errno::SUCCESS}; 659 return {descriptor.flags, Errno::SUCCESS};
650 case FcntlCmd::SETFL: { 660 case FcntlCmd::SETFL: {
651 const bool enable = (arg & FLAG_O_NONBLOCK) != 0; 661 const bool enable = (arg & Network::FLAG_O_NONBLOCK) != 0;
652 const Errno bsd_errno = Translate(descriptor.socket->SetNonBlock(enable)); 662 const Errno bsd_errno = Translate(descriptor.socket->SetNonBlock(enable));
653 if (bsd_errno != Errno::SUCCESS) { 663 if (bsd_errno != Errno::SUCCESS) {
654 return {-1, bsd_errno}; 664 return {-1, bsd_errno};
@@ -669,7 +679,7 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, con
669 return Errno::BADF; 679 return Errno::BADF;
670 } 680 }
671 681
672 Network::Socket* const socket = file_descriptors[fd]->socket.get(); 682 Network::SocketBase* const socket = file_descriptors[fd]->socket.get();
673 683
674 if (optname == OptName::LINGER) { 684 if (optname == OptName::LINGER) {
675 ASSERT(optlen == sizeof(Linger)); 685 ASSERT(optlen == sizeof(Linger));
@@ -724,6 +734,8 @@ std::pair<s32, Errno> BSD::RecvImpl(s32 fd, u32 flags, std::vector<u8>& message)
724 FileDescriptor& descriptor = *file_descriptors[fd]; 734 FileDescriptor& descriptor = *file_descriptors[fd];
725 735
726 // Apply flags 736 // Apply flags
737 using Network::FLAG_MSG_DONTWAIT;
738 using Network::FLAG_O_NONBLOCK;
727 if ((flags & FLAG_MSG_DONTWAIT) != 0) { 739 if ((flags & FLAG_MSG_DONTWAIT) != 0) {
728 flags &= ~FLAG_MSG_DONTWAIT; 740 flags &= ~FLAG_MSG_DONTWAIT;
729 if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) { 741 if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) {
@@ -759,6 +771,8 @@ std::pair<s32, Errno> BSD::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& mess
759 } 771 }
760 772
761 // Apply flags 773 // Apply flags
774 using Network::FLAG_MSG_DONTWAIT;
775 using Network::FLAG_O_NONBLOCK;
762 if ((flags & FLAG_MSG_DONTWAIT) != 0) { 776 if ((flags & FLAG_MSG_DONTWAIT) != 0) {
763 flags &= ~FLAG_MSG_DONTWAIT; 777 flags &= ~FLAG_MSG_DONTWAIT;
764 if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) { 778 if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) {
@@ -857,8 +871,19 @@ void BSD::BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) co
857 rb.PushEnum(bsd_errno); 871 rb.PushEnum(bsd_errno);
858} 872}
859 873
874void BSD::OnProxyPacketReceived(const Network::ProxyPacket& packet) {
875 for (auto& optional_descriptor : file_descriptors) {
876 if (!optional_descriptor.has_value()) {
877 continue;
878 }
879 FileDescriptor& descriptor = *optional_descriptor;
880 descriptor.socket.get()->HandleProxyPacket(packet);
881 }
882}
883
860BSD::BSD(Core::System& system_, const char* name) 884BSD::BSD(Core::System& system_, const char* name)
861 : ServiceFramework{system_, name, ServiceThreadType::CreateNew} { 885 : ServiceFramework{system_, name, ServiceThreadType::CreateNew}, room_network{
886 system_.GetRoomNetwork()} {
862 // clang-format off 887 // clang-format off
863 static const FunctionInfo functions[] = { 888 static const FunctionInfo functions[] = {
864 {0, &BSD::RegisterClient, "RegisterClient"}, 889 {0, &BSD::RegisterClient, "RegisterClient"},
@@ -899,9 +924,20 @@ BSD::BSD(Core::System& system_, const char* name)
899 // clang-format on 924 // clang-format on
900 925
901 RegisterHandlers(functions); 926 RegisterHandlers(functions);
927
928 if (auto room_member = room_network.GetRoomMember().lock()) {
929 proxy_packet_received = room_member->BindOnProxyPacketReceived(
930 [this](const Network::ProxyPacket& packet) { OnProxyPacketReceived(packet); });
931 } else {
932 LOG_ERROR(Service, "Network isn't initalized");
933 }
902} 934}
903 935
904BSD::~BSD() = default; 936BSD::~BSD() {
937 if (auto room_member = room_network.GetRoomMember().lock()) {
938 room_member->Unbind(proxy_packet_received);
939 }
940}
905 941
906BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} { 942BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} {
907 // clang-format off 943 // clang-format off
diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h
index 9ea36428d..81e855e0f 100644
--- a/src/core/hle/service/sockets/bsd.h
+++ b/src/core/hle/service/sockets/bsd.h
@@ -7,14 +7,17 @@
7#include <vector> 7#include <vector>
8 8
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "common/socket_types.h"
10#include "core/hle/service/service.h" 11#include "core/hle/service/service.h"
11#include "core/hle/service/sockets/sockets.h" 12#include "core/hle/service/sockets/sockets.h"
13#include "network/network.h"
12 14
13namespace Core { 15namespace Core {
14class System; 16class System;
15} 17}
16 18
17namespace Network { 19namespace Network {
20class SocketBase;
18class Socket; 21class Socket;
19} // namespace Network 22} // namespace Network
20 23
@@ -30,7 +33,7 @@ private:
30 static constexpr size_t MAX_FD = 128; 33 static constexpr size_t MAX_FD = 128;
31 34
32 struct FileDescriptor { 35 struct FileDescriptor {
33 std::unique_ptr<Network::Socket> socket; 36 std::unique_ptr<Network::SocketBase> socket;
34 s32 flags = 0; 37 s32 flags = 0;
35 bool is_connection_based = false; 38 bool is_connection_based = false;
36 }; 39 };
@@ -165,6 +168,14 @@ private:
165 void BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept; 168 void BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept;
166 169
167 std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors; 170 std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors;
171
172 Network::RoomNetwork& room_network;
173
174 /// Callback to parse and handle a received wifi packet.
175 void OnProxyPacketReceived(const Network::ProxyPacket& packet);
176
177 // Callback identifier for the OnProxyPacketReceived event.
178 Network::RoomMember::CallbackHandle<Network::ProxyPacket> proxy_packet_received;
168}; 179};
169 180
170class BSDCFG final : public ServiceFramework<BSDCFG> { 181class BSDCFG final : public ServiceFramework<BSDCFG> {
diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h
index b735b00fc..31b7dad33 100644
--- a/src/core/hle/service/sockets/sockets.h
+++ b/src/core/hle/service/sockets/sockets.h
@@ -22,7 +22,9 @@ enum class Errno : u32 {
22 AGAIN = 11, 22 AGAIN = 11,
23 INVAL = 22, 23 INVAL = 22,
24 MFILE = 24, 24 MFILE = 24,
25 MSGSIZE = 90,
25 NOTCONN = 107, 26 NOTCONN = 107,
27 TIMEDOUT = 110,
26}; 28};
27 29
28enum class Domain : u32 { 30enum class Domain : u32 {
@@ -96,10 +98,6 @@ struct Linger {
96 u32 linger; 98 u32 linger;
97}; 99};
98 100
99constexpr u32 FLAG_MSG_DONTWAIT = 0x80;
100
101constexpr u32 FLAG_O_NONBLOCK = 0x800;
102
103/// Registers all Sockets services with the specified service manager. 101/// Registers all Sockets services with the specified service manager.
104void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); 102void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
105 103
diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp
index 2db10ec81..023aa0486 100644
--- a/src/core/hle/service/sockets/sockets_translate.cpp
+++ b/src/core/hle/service/sockets/sockets_translate.cpp
@@ -25,6 +25,8 @@ Errno Translate(Network::Errno value) {
25 return Errno::MFILE; 25 return Errno::MFILE;
26 case Network::Errno::NOTCONN: 26 case Network::Errno::NOTCONN:
27 return Errno::NOTCONN; 27 return Errno::NOTCONN;
28 case Network::Errno::TIMEDOUT:
29 return Errno::TIMEDOUT;
28 default: 30 default:
29 UNIMPLEMENTED_MSG("Unimplemented errno={}", value); 31 UNIMPLEMENTED_MSG("Unimplemented errno={}", value);
30 return Errno::SUCCESS; 32 return Errno::SUCCESS;
diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp
index 36c43cc8f..cdf38a2a4 100644
--- a/src/core/internal_network/network.cpp
+++ b/src/core/internal_network/network.cpp
@@ -32,6 +32,7 @@
32#include "core/internal_network/network.h" 32#include "core/internal_network/network.h"
33#include "core/internal_network/network_interface.h" 33#include "core/internal_network/network_interface.h"
34#include "core/internal_network/sockets.h" 34#include "core/internal_network/sockets.h"
35#include "network/network.h"
35 36
36namespace Network { 37namespace Network {
37 38
@@ -114,7 +115,10 @@ Errno TranslateNativeError(int e) {
114 return Errno::NETDOWN; 115 return Errno::NETDOWN;
115 case WSAENETUNREACH: 116 case WSAENETUNREACH:
116 return Errno::NETUNREACH; 117 return Errno::NETUNREACH;
118 case WSAEMSGSIZE:
119 return Errno::MSGSIZE;
117 default: 120 default:
121 UNIMPLEMENTED_MSG("Unimplemented errno={}", e);
118 return Errno::OTHER; 122 return Errno::OTHER;
119 } 123 }
120} 124}
@@ -125,7 +129,6 @@ using SOCKET = int;
125using WSAPOLLFD = pollfd; 129using WSAPOLLFD = pollfd;
126using ULONG = u64; 130using ULONG = u64;
127 131
128constexpr SOCKET INVALID_SOCKET = -1;
129constexpr SOCKET SOCKET_ERROR = -1; 132constexpr SOCKET SOCKET_ERROR = -1;
130 133
131constexpr int SD_RECEIVE = SHUT_RD; 134constexpr int SD_RECEIVE = SHUT_RD;
@@ -206,7 +209,10 @@ Errno TranslateNativeError(int e) {
206 return Errno::NETDOWN; 209 return Errno::NETDOWN;
207 case ENETUNREACH: 210 case ENETUNREACH:
208 return Errno::NETUNREACH; 211 return Errno::NETUNREACH;
212 case EMSGSIZE:
213 return Errno::MSGSIZE;
209 default: 214 default:
215 UNIMPLEMENTED_MSG("Unimplemented errno={}", e);
210 return Errno::OTHER; 216 return Errno::OTHER;
211 } 217 }
212} 218}
@@ -329,16 +335,6 @@ PollEvents TranslatePollRevents(short revents) {
329 return result; 335 return result;
330} 336}
331 337
332template <typename T>
333Errno SetSockOpt(SOCKET fd, int option, T value) {
334 const int result =
335 setsockopt(fd, SOL_SOCKET, option, reinterpret_cast<const char*>(&value), sizeof(value));
336 if (result != SOCKET_ERROR) {
337 return Errno::SUCCESS;
338 }
339 return GetAndLogLastError();
340}
341
342} // Anonymous namespace 338} // Anonymous namespace
343 339
344NetworkInstance::NetworkInstance() { 340NetworkInstance::NetworkInstance() {
@@ -350,26 +346,16 @@ NetworkInstance::~NetworkInstance() {
350} 346}
351 347
352std::optional<IPv4Address> GetHostIPv4Address() { 348std::optional<IPv4Address> GetHostIPv4Address() {
353 const std::string& selected_network_interface = Settings::values.network_interface.GetValue(); 349 const auto network_interface = Network::GetSelectedNetworkInterface();
354 const auto network_interfaces = Network::GetAvailableNetworkInterfaces(); 350 if (!network_interface.has_value()) {
355 if (network_interfaces.size() == 0) { 351 LOG_ERROR(Network, "GetSelectedNetworkInterface returned no interface");
356 LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces");
357 return {}; 352 return {};
358 } 353 }
359 354
360 const auto res = 355 std::array<char, 16> ip_addr = {};
361 std::ranges::find_if(network_interfaces, [&selected_network_interface](const auto& iface) { 356 ASSERT(inet_ntop(AF_INET, &network_interface->ip_address, ip_addr.data(), sizeof(ip_addr)) !=
362 return iface.name == selected_network_interface; 357 nullptr);
363 }); 358 return TranslateIPv4(network_interface->ip_address);
364
365 if (res != network_interfaces.end()) {
366 char ip_addr[16] = {};
367 ASSERT(inet_ntop(AF_INET, &res->ip_address, ip_addr, sizeof(ip_addr)) != nullptr);
368 return TranslateIPv4(res->ip_address);
369 } else {
370 LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", selected_network_interface);
371 return {};
372 }
373} 359}
374 360
375std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) { 361std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
@@ -412,7 +398,19 @@ Socket::~Socket() {
412 fd = INVALID_SOCKET; 398 fd = INVALID_SOCKET;
413} 399}
414 400
415Socket::Socket(Socket&& rhs) noexcept : fd{std::exchange(rhs.fd, INVALID_SOCKET)} {} 401Socket::Socket(Socket&& rhs) noexcept {
402 fd = std::exchange(rhs.fd, INVALID_SOCKET);
403}
404
405template <typename T>
406Errno Socket::SetSockOpt(SOCKET fd_, int option, T value) {
407 const int result =
408 setsockopt(fd_, SOL_SOCKET, option, reinterpret_cast<const char*>(&value), sizeof(value));
409 if (result != SOCKET_ERROR) {
410 return Errno::SUCCESS;
411 }
412 return GetAndLogLastError();
413}
416 414
417Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) { 415Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) {
418 fd = socket(TranslateDomain(domain), TranslateType(type), TranslateProtocol(protocol)); 416 fd = socket(TranslateDomain(domain), TranslateType(type), TranslateProtocol(protocol));
@@ -423,7 +421,7 @@ Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) {
423 return GetAndLogLastError(); 421 return GetAndLogLastError();
424} 422}
425 423
426std::pair<Socket::AcceptResult, Errno> Socket::Accept() { 424std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() {
427 sockaddr addr; 425 sockaddr addr;
428 socklen_t addrlen = sizeof(addr); 426 socklen_t addrlen = sizeof(addr);
429 const SOCKET new_socket = accept(fd, &addr, &addrlen); 427 const SOCKET new_socket = accept(fd, &addr, &addrlen);
@@ -634,4 +632,8 @@ bool Socket::IsOpened() const {
634 return fd != INVALID_SOCKET; 632 return fd != INVALID_SOCKET;
635} 633}
636 634
635void Socket::HandleProxyPacket(const ProxyPacket& packet) {
636 LOG_WARNING(Network, "ProxyPacket received, but not in Proxy mode!");
637}
638
637} // namespace Network 639} // namespace Network
diff --git a/src/core/internal_network/network.h b/src/core/internal_network/network.h
index 10e5ef10d..36994c22e 100644
--- a/src/core/internal_network/network.h
+++ b/src/core/internal_network/network.h
@@ -8,6 +8,7 @@
8 8
9#include "common/common_funcs.h" 9#include "common/common_funcs.h"
10#include "common/common_types.h" 10#include "common/common_types.h"
11#include "common/socket_types.h"
11 12
12#ifdef _WIN32 13#ifdef _WIN32
13#include <winsock2.h> 14#include <winsock2.h>
@@ -17,6 +18,7 @@
17 18
18namespace Network { 19namespace Network {
19 20
21class SocketBase;
20class Socket; 22class Socket;
21 23
22/// Error code for network functions 24/// Error code for network functions
@@ -31,46 +33,11 @@ enum class Errno {
31 HOSTUNREACH, 33 HOSTUNREACH,
32 NETDOWN, 34 NETDOWN,
33 NETUNREACH, 35 NETUNREACH,
36 TIMEDOUT,
37 MSGSIZE,
34 OTHER, 38 OTHER,
35}; 39};
36 40
37/// Address families
38enum class Domain {
39 INET, ///< Address family for IPv4
40};
41
42/// Socket types
43enum class Type {
44 STREAM,
45 DGRAM,
46 RAW,
47 SEQPACKET,
48};
49
50/// Protocol values for sockets
51enum class Protocol {
52 ICMP,
53 TCP,
54 UDP,
55};
56
57/// Shutdown mode
58enum class ShutdownHow {
59 RD,
60 WR,
61 RDWR,
62};
63
64/// Array of IPv4 address
65using IPv4Address = std::array<u8, 4>;
66
67/// Cross-platform sockaddr structure
68struct SockAddrIn {
69 Domain family;
70 IPv4Address ip;
71 u16 portno;
72};
73
74/// Cross-platform poll fd structure 41/// Cross-platform poll fd structure
75 42
76enum class PollEvents : u16 { 43enum class PollEvents : u16 {
@@ -86,7 +53,7 @@ enum class PollEvents : u16 {
86DECLARE_ENUM_FLAG_OPERATORS(PollEvents); 53DECLARE_ENUM_FLAG_OPERATORS(PollEvents);
87 54
88struct PollFD { 55struct PollFD {
89 Socket* socket; 56 SocketBase* socket;
90 PollEvents events; 57 PollEvents events;
91 PollEvents revents; 58 PollEvents revents;
92}; 59};
diff --git a/src/core/internal_network/socket_proxy.cpp b/src/core/internal_network/socket_proxy.cpp
new file mode 100644
index 000000000..0c746bd82
--- /dev/null
+++ b/src/core/internal_network/socket_proxy.cpp
@@ -0,0 +1,290 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <chrono>
5#include <thread>
6
7#include "common/assert.h"
8#include "common/logging/log.h"
9#include "core/internal_network/network.h"
10#include "core/internal_network/network_interface.h"
11#include "core/internal_network/socket_proxy.h"
12
13namespace Network {
14
15ProxySocket::ProxySocket(RoomNetwork& room_network_) noexcept : room_network{room_network_} {}
16
17ProxySocket::~ProxySocket() {
18 if (fd == INVALID_SOCKET) {
19 return;
20 }
21 fd = INVALID_SOCKET;
22}
23
24void ProxySocket::HandleProxyPacket(const ProxyPacket& packet) {
25 if (protocol != packet.protocol || local_endpoint.portno != packet.remote_endpoint.portno ||
26 closed) {
27 return;
28 }
29
30 if (!broadcast && packet.broadcast) {
31 LOG_INFO(Network, "Received broadcast packet, but not configured for broadcast mode");
32 return;
33 }
34
35 std::lock_guard guard(packets_mutex);
36 received_packets.push(packet);
37}
38
39template <typename T>
40Errno ProxySocket::SetSockOpt(SOCKET fd_, int option, T value) {
41 LOG_DEBUG(Network, "(STUBBED) called");
42 return Errno::SUCCESS;
43}
44
45Errno ProxySocket::Initialize(Domain domain, Type type, Protocol socket_protocol) {
46 protocol = socket_protocol;
47 SetSockOpt(fd, SO_TYPE, type);
48
49 return Errno::SUCCESS;
50}
51
52std::pair<ProxySocket::AcceptResult, Errno> ProxySocket::Accept() {
53 LOG_WARNING(Network, "(STUBBED) called");
54 return {AcceptResult{}, Errno::SUCCESS};
55}
56
57Errno ProxySocket::Connect(SockAddrIn addr_in) {
58 LOG_WARNING(Network, "(STUBBED) called");
59 return Errno::SUCCESS;
60}
61
62std::pair<SockAddrIn, Errno> ProxySocket::GetPeerName() {
63 LOG_WARNING(Network, "(STUBBED) called");
64 return {SockAddrIn{}, Errno::SUCCESS};
65}
66
67std::pair<SockAddrIn, Errno> ProxySocket::GetSockName() {
68 LOG_WARNING(Network, "(STUBBED) called");
69 return {SockAddrIn{}, Errno::SUCCESS};
70}
71
72Errno ProxySocket::Bind(SockAddrIn addr) {
73 if (is_bound) {
74 LOG_WARNING(Network, "Rebinding Socket is unimplemented!");
75 return Errno::SUCCESS;
76 }
77 local_endpoint = addr;
78 is_bound = true;
79
80 return Errno::SUCCESS;
81}
82
83Errno ProxySocket::Listen(s32 backlog) {
84 LOG_WARNING(Network, "(STUBBED) called");
85 return Errno::SUCCESS;
86}
87
88Errno ProxySocket::Shutdown(ShutdownHow how) {
89 LOG_WARNING(Network, "(STUBBED) called");
90 return Errno::SUCCESS;
91}
92
93std::pair<s32, Errno> ProxySocket::Recv(int flags, std::vector<u8>& message) {
94 LOG_WARNING(Network, "(STUBBED) called");
95 ASSERT(flags == 0);
96 ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
97
98 return {static_cast<s32>(0), Errno::SUCCESS};
99}
100
101std::pair<s32, Errno> ProxySocket::RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) {
102 ASSERT(flags == 0);
103 ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
104
105 // TODO (flTobi): Verify the timeout behavior and break when connection is lost
106 const auto timestamp = std::chrono::steady_clock::now();
107 // When receive_timeout is set to zero, the socket is supposed to wait indefinitely until a
108 // packet arrives. In order to prevent lost packets from hanging the emulation thread, we set
109 // the timeout to 5s instead
110 const auto timeout = receive_timeout == 0 ? 5000 : receive_timeout;
111 while (true) {
112 {
113 std::lock_guard guard(packets_mutex);
114 if (received_packets.size() > 0) {
115 return ReceivePacket(flags, message, addr, message.size());
116 }
117 }
118
119 if (!blocking) {
120 return {-1, Errno::AGAIN};
121 }
122
123 std::this_thread::yield();
124
125 const auto time_diff = std::chrono::steady_clock::now() - timestamp;
126 const auto time_diff_ms =
127 std::chrono::duration_cast<std::chrono::milliseconds>(time_diff).count();
128
129 if (time_diff_ms > timeout) {
130 return {-1, Errno::TIMEDOUT};
131 }
132 }
133}
134
135std::pair<s32, Errno> ProxySocket::ReceivePacket(int flags, std::vector<u8>& message,
136 SockAddrIn* addr, std::size_t max_length) {
137 ProxyPacket& packet = received_packets.front();
138 if (addr) {
139 addr->family = Domain::INET;
140 addr->ip = packet.local_endpoint.ip; // The senders ip address
141 addr->portno = packet.local_endpoint.portno; // The senders port number
142 }
143
144 bool peek = (flags & FLAG_MSG_PEEK) != 0;
145 std::size_t read_bytes;
146 if (packet.data.size() > max_length) {
147 read_bytes = max_length;
148 message.clear();
149 std::copy(packet.data.begin(), packet.data.begin() + read_bytes,
150 std::back_inserter(message));
151 message.resize(max_length);
152
153 if (protocol == Protocol::UDP) {
154 if (!peek) {
155 received_packets.pop();
156 }
157 return {-1, Errno::MSGSIZE};
158 } else if (protocol == Protocol::TCP) {
159 std::vector<u8> numArray(packet.data.size() - max_length);
160 std::copy(packet.data.begin() + max_length, packet.data.end(),
161 std::back_inserter(numArray));
162 packet.data = numArray;
163 }
164 } else {
165 read_bytes = packet.data.size();
166 message.clear();
167 std::copy(packet.data.begin(), packet.data.end(), std::back_inserter(message));
168 message.resize(max_length);
169 if (!peek) {
170 received_packets.pop();
171 }
172 }
173
174 return {static_cast<u32>(read_bytes), Errno::SUCCESS};
175}
176
177std::pair<s32, Errno> ProxySocket::Send(const std::vector<u8>& message, int flags) {
178 LOG_WARNING(Network, "(STUBBED) called");
179 ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
180 ASSERT(flags == 0);
181
182 return {static_cast<s32>(0), Errno::SUCCESS};
183}
184
185void ProxySocket::SendPacket(ProxyPacket& packet) {
186 if (auto room_member = room_network.GetRoomMember().lock()) {
187 if (room_member->IsConnected()) {
188 room_member->SendProxyPacket(packet);
189 }
190 }
191}
192
193std::pair<s32, Errno> ProxySocket::SendTo(u32 flags, const std::vector<u8>& message,
194 const SockAddrIn* addr) {
195 ASSERT(flags == 0);
196
197 if (!is_bound) {
198 LOG_ERROR(Network, "ProxySocket is not bound!");
199 return {static_cast<s32>(message.size()), Errno::SUCCESS};
200 }
201
202 if (auto room_member = room_network.GetRoomMember().lock()) {
203 if (!room_member->IsConnected()) {
204 return {static_cast<s32>(message.size()), Errno::SUCCESS};
205 }
206 }
207
208 ProxyPacket packet;
209 packet.local_endpoint = local_endpoint;
210 packet.remote_endpoint = *addr;
211 packet.protocol = protocol;
212 packet.broadcast = broadcast && packet.remote_endpoint.ip[3] == 255;
213
214 auto& ip = local_endpoint.ip;
215 auto ipv4 = Network::GetHostIPv4Address();
216 // If the ip is all zeroes (INADDR_ANY) or if it matches the hosts ip address,
217 // replace it with a "fake" routing address
218 if (std::all_of(ip.begin(), ip.end(), [](u8 i) { return i == 0; }) || (ipv4 && ipv4 == ip)) {
219 if (auto room_member = room_network.GetRoomMember().lock()) {
220 packet.local_endpoint.ip = room_member->GetFakeIpAddress();
221 }
222 }
223
224 packet.data.clear();
225 std::copy(message.begin(), message.end(), std::back_inserter(packet.data));
226
227 SendPacket(packet);
228
229 return {static_cast<s32>(message.size()), Errno::SUCCESS};
230}
231
232Errno ProxySocket::Close() {
233 fd = INVALID_SOCKET;
234 closed = true;
235
236 return Errno::SUCCESS;
237}
238
239Errno ProxySocket::SetLinger(bool enable, u32 linger) {
240 struct Linger {
241 u16 linger_enable;
242 u16 linger_time;
243 } values;
244 values.linger_enable = enable ? 1 : 0;
245 values.linger_time = static_cast<u16>(linger);
246
247 return SetSockOpt(fd, SO_LINGER, values);
248}
249
250Errno ProxySocket::SetReuseAddr(bool enable) {
251 return SetSockOpt<u32>(fd, SO_REUSEADDR, enable ? 1 : 0);
252}
253
254Errno ProxySocket::SetBroadcast(bool enable) {
255 broadcast = enable;
256 return SetSockOpt<u32>(fd, SO_BROADCAST, enable ? 1 : 0);
257}
258
259Errno ProxySocket::SetSndBuf(u32 value) {
260 return SetSockOpt(fd, SO_SNDBUF, value);
261}
262
263Errno ProxySocket::SetKeepAlive(bool enable) {
264 return Errno::SUCCESS;
265}
266
267Errno ProxySocket::SetRcvBuf(u32 value) {
268 return SetSockOpt(fd, SO_RCVBUF, value);
269}
270
271Errno ProxySocket::SetSndTimeo(u32 value) {
272 send_timeout = value;
273 return SetSockOpt(fd, SO_SNDTIMEO, static_cast<int>(value));
274}
275
276Errno ProxySocket::SetRcvTimeo(u32 value) {
277 receive_timeout = value;
278 return SetSockOpt(fd, SO_RCVTIMEO, static_cast<int>(value));
279}
280
281Errno ProxySocket::SetNonBlock(bool enable) {
282 blocking = !enable;
283 return Errno::SUCCESS;
284}
285
286bool ProxySocket::IsOpened() const {
287 return fd != INVALID_SOCKET;
288}
289
290} // namespace Network
diff --git a/src/core/internal_network/socket_proxy.h b/src/core/internal_network/socket_proxy.h
new file mode 100644
index 000000000..f12b5f567
--- /dev/null
+++ b/src/core/internal_network/socket_proxy.h
@@ -0,0 +1,97 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <mutex>
7#include <vector>
8#include <queue>
9
10#include "common/common_funcs.h"
11#include "core/internal_network/sockets.h"
12#include "network/network.h"
13
14namespace Network {
15
16class ProxySocket : public SocketBase {
17public:
18 YUZU_NON_COPYABLE(ProxySocket);
19 YUZU_NON_MOVEABLE(ProxySocket);
20
21 explicit ProxySocket(RoomNetwork& room_network_) noexcept;
22 ~ProxySocket() override;
23
24 void HandleProxyPacket(const ProxyPacket& packet) override;
25
26 Errno Initialize(Domain domain, Type type, Protocol socket_protocol) override;
27
28 Errno Close() override;
29
30 std::pair<AcceptResult, Errno> Accept() override;
31
32 Errno Connect(SockAddrIn addr_in) override;
33
34 std::pair<SockAddrIn, Errno> GetPeerName() override;
35
36 std::pair<SockAddrIn, Errno> GetSockName() override;
37
38 Errno Bind(SockAddrIn addr) override;
39
40 Errno Listen(s32 backlog) override;
41
42 Errno Shutdown(ShutdownHow how) override;
43
44 std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message) override;
45
46 std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) override;
47
48 std::pair<s32, Errno> ReceivePacket(int flags, std::vector<u8>& message, SockAddrIn* addr,
49 std::size_t max_length);
50
51 std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags) override;
52
53 void SendPacket(ProxyPacket& packet);
54
55 std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message,
56 const SockAddrIn* addr) override;
57
58 Errno SetLinger(bool enable, u32 linger) override;
59
60 Errno SetReuseAddr(bool enable) override;
61
62 Errno SetBroadcast(bool enable) override;
63
64 Errno SetKeepAlive(bool enable) override;
65
66 Errno SetSndBuf(u32 value) override;
67
68 Errno SetRcvBuf(u32 value) override;
69
70 Errno SetSndTimeo(u32 value) override;
71
72 Errno SetRcvTimeo(u32 value) override;
73
74 Errno SetNonBlock(bool enable) override;
75
76 template <typename T>
77 Errno SetSockOpt(SOCKET fd, int option, T value);
78
79 bool IsOpened() const override;
80
81private:
82 bool broadcast = false;
83 bool closed = false;
84 u32 send_timeout = 0;
85 u32 receive_timeout = 0;
86 bool is_bound = false;
87 SockAddrIn local_endpoint{};
88 bool blocking = true;
89 std::queue<ProxyPacket> received_packets;
90 Protocol protocol;
91
92 std::mutex packets_mutex;
93
94 RoomNetwork& room_network;
95};
96
97} // namespace Network
diff --git a/src/core/internal_network/sockets.h b/src/core/internal_network/sockets.h
index 77e27e928..a70429b19 100644
--- a/src/core/internal_network/sockets.h
+++ b/src/core/internal_network/sockets.h
@@ -14,20 +14,88 @@
14 14
15#include "common/common_types.h" 15#include "common/common_types.h"
16#include "core/internal_network/network.h" 16#include "core/internal_network/network.h"
17#include "network/network.h"
17 18
18// TODO: C++20 Replace std::vector usages with std::span 19// TODO: C++20 Replace std::vector usages with std::span
19 20
20namespace Network { 21namespace Network {
21 22
22class Socket { 23class SocketBase {
23public: 24public:
25#ifdef YUZU_UNIX
26 using SOCKET = int;
27 static constexpr SOCKET INVALID_SOCKET = -1;
28 static constexpr SOCKET SOCKET_ERROR = -1;
29#endif
30
24 struct AcceptResult { 31 struct AcceptResult {
25 std::unique_ptr<Socket> socket; 32 std::unique_ptr<SocketBase> socket;
26 SockAddrIn sockaddr_in; 33 SockAddrIn sockaddr_in;
27 }; 34 };
35 virtual ~SocketBase() = default;
36
37 virtual SocketBase& operator=(const SocketBase&) = delete;
38
39 // Avoid closing sockets implicitly
40 virtual SocketBase& operator=(SocketBase&&) noexcept = delete;
41
42 virtual Errno Initialize(Domain domain, Type type, Protocol protocol) = 0;
43
44 virtual Errno Close() = 0;
45
46 virtual std::pair<AcceptResult, Errno> Accept() = 0;
47
48 virtual Errno Connect(SockAddrIn addr_in) = 0;
49
50 virtual std::pair<SockAddrIn, Errno> GetPeerName() = 0;
51
52 virtual std::pair<SockAddrIn, Errno> GetSockName() = 0;
53
54 virtual Errno Bind(SockAddrIn addr) = 0;
55
56 virtual Errno Listen(s32 backlog) = 0;
57
58 virtual Errno Shutdown(ShutdownHow how) = 0;
59
60 virtual std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message) = 0;
61
62 virtual std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message,
63 SockAddrIn* addr) = 0;
64
65 virtual std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags) = 0;
66
67 virtual std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message,
68 const SockAddrIn* addr) = 0;
69
70 virtual Errno SetLinger(bool enable, u32 linger) = 0;
28 71
29 explicit Socket() = default; 72 virtual Errno SetReuseAddr(bool enable) = 0;
30 ~Socket(); 73
74 virtual Errno SetKeepAlive(bool enable) = 0;
75
76 virtual Errno SetBroadcast(bool enable) = 0;
77
78 virtual Errno SetSndBuf(u32 value) = 0;
79
80 virtual Errno SetRcvBuf(u32 value) = 0;
81
82 virtual Errno SetSndTimeo(u32 value) = 0;
83
84 virtual Errno SetRcvTimeo(u32 value) = 0;
85
86 virtual Errno SetNonBlock(bool enable) = 0;
87
88 virtual bool IsOpened() const = 0;
89
90 virtual void HandleProxyPacket(const ProxyPacket& packet) = 0;
91
92 SOCKET fd = INVALID_SOCKET;
93};
94
95class Socket : public SocketBase {
96public:
97 Socket() = default;
98 ~Socket() override;
31 99
32 Socket(const Socket&) = delete; 100 Socket(const Socket&) = delete;
33 Socket& operator=(const Socket&) = delete; 101 Socket& operator=(const Socket&) = delete;
@@ -37,57 +105,57 @@ public:
37 // Avoid closing sockets implicitly 105 // Avoid closing sockets implicitly
38 Socket& operator=(Socket&&) noexcept = delete; 106 Socket& operator=(Socket&&) noexcept = delete;
39 107
40 Errno Initialize(Domain domain, Type type, Protocol protocol); 108 Errno Initialize(Domain domain, Type type, Protocol protocol) override;
41 109
42 Errno Close(); 110 Errno Close() override;
43 111
44 std::pair<AcceptResult, Errno> Accept(); 112 std::pair<AcceptResult, Errno> Accept() override;
45 113
46 Errno Connect(SockAddrIn addr_in); 114 Errno Connect(SockAddrIn addr_in) override;
47 115
48 std::pair<SockAddrIn, Errno> GetPeerName(); 116 std::pair<SockAddrIn, Errno> GetPeerName() override;
49 117
50 std::pair<SockAddrIn, Errno> GetSockName(); 118 std::pair<SockAddrIn, Errno> GetSockName() override;
51 119
52 Errno Bind(SockAddrIn addr); 120 Errno Bind(SockAddrIn addr) override;
53 121
54 Errno Listen(s32 backlog); 122 Errno Listen(s32 backlog) override;
55 123
56 Errno Shutdown(ShutdownHow how); 124 Errno Shutdown(ShutdownHow how) override;
57 125
58 std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message); 126 std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message) override;
59 127
60 std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr); 128 std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) override;
61 129
62 std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags); 130 std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags) override;
63 131
64 std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message, const SockAddrIn* addr); 132 std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message,
133 const SockAddrIn* addr) override;
65 134
66 Errno SetLinger(bool enable, u32 linger); 135 Errno SetLinger(bool enable, u32 linger) override;
67 136
68 Errno SetReuseAddr(bool enable); 137 Errno SetReuseAddr(bool enable) override;
69 138
70 Errno SetKeepAlive(bool enable); 139 Errno SetKeepAlive(bool enable) override;
71 140
72 Errno SetBroadcast(bool enable); 141 Errno SetBroadcast(bool enable) override;
73 142
74 Errno SetSndBuf(u32 value); 143 Errno SetSndBuf(u32 value) override;
75 144
76 Errno SetRcvBuf(u32 value); 145 Errno SetRcvBuf(u32 value) override;
77 146
78 Errno SetSndTimeo(u32 value); 147 Errno SetSndTimeo(u32 value) override;
79 148
80 Errno SetRcvTimeo(u32 value); 149 Errno SetRcvTimeo(u32 value) override;
81 150
82 Errno SetNonBlock(bool enable); 151 Errno SetNonBlock(bool enable) override;
83 152
84 bool IsOpened() const; 153 template <typename T>
154 Errno SetSockOpt(SOCKET fd, int option, T value);
85 155
86#if defined(_WIN32) 156 bool IsOpened() const override;
87 SOCKET fd = INVALID_SOCKET; 157
88#elif YUZU_UNIX 158 void HandleProxyPacket(const ProxyPacket& packet) override;
89 int fd = -1;
90#endif
91}; 159};
92 160
93std::pair<s32, Errno> Poll(std::vector<PollFD>& poll_fds, s32 timeout); 161std::pair<s32, Errno> Poll(std::vector<PollFD>& poll_fds, s32 timeout);
diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp
deleted file mode 100644
index dfb10c34f..000000000
--- a/src/core/loader/elf.cpp
+++ /dev/null
@@ -1,263 +0,0 @@
1// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
2// SPDX-FileCopyrightText: 2014 Citra Emulator Project
3// SPDX-License-Identifier: GPL-2.0-or-later
4
5#include <cstring>
6#include <memory>
7#include "common/common_funcs.h"
8#include "common/common_types.h"
9#include "common/elf.h"
10#include "common/logging/log.h"
11#include "core/hle/kernel/code_set.h"
12#include "core/hle/kernel/k_page_table.h"
13#include "core/hle/kernel/k_process.h"
14#include "core/loader/elf.h"
15#include "core/memory.h"
16
17using namespace Common::ELF;
18
19////////////////////////////////////////////////////////////////////////////////////////////////////
20// ElfReader class
21
22typedef int SectionID;
23
24class ElfReader {
25private:
26 char* base;
27 u32* base32;
28
29 Elf32_Ehdr* header;
30 Elf32_Phdr* segments;
31 Elf32_Shdr* sections;
32
33 u32* sectionAddrs;
34 bool relocate;
35 VAddr entryPoint;
36
37public:
38 explicit ElfReader(void* ptr);
39
40 u32 Read32(int off) const {
41 return base32[off >> 2];
42 }
43
44 // Quick accessors
45 u16 GetType() const {
46 return header->e_type;
47 }
48 u16 GetMachine() const {
49 return header->e_machine;
50 }
51 VAddr GetEntryPoint() const {
52 return entryPoint;
53 }
54 u32 GetFlags() const {
55 return (u32)(header->e_flags);
56 }
57 Kernel::CodeSet LoadInto(VAddr vaddr);
58
59 int GetNumSegments() const {
60 return (int)(header->e_phnum);
61 }
62 int GetNumSections() const {
63 return (int)(header->e_shnum);
64 }
65 const u8* GetPtr(int offset) const {
66 return (u8*)base + offset;
67 }
68 const char* GetSectionName(int section) const;
69 const u8* GetSectionDataPtr(int section) const {
70 if (section < 0 || section >= header->e_shnum)
71 return nullptr;
72 if (sections[section].sh_type != ElfShtNobits)
73 return GetPtr(sections[section].sh_offset);
74 else
75 return nullptr;
76 }
77 bool IsCodeSection(int section) const {
78 return sections[section].sh_type == ElfShtProgBits;
79 }
80 const u8* GetSegmentPtr(int segment) {
81 return GetPtr(segments[segment].p_offset);
82 }
83 u32 GetSectionAddr(SectionID section) const {
84 return sectionAddrs[section];
85 }
86 unsigned int GetSectionSize(SectionID section) const {
87 return sections[section].sh_size;
88 }
89 SectionID GetSectionByName(const char* name, int firstSection = 0) const; //-1 for not found
90
91 bool DidRelocate() const {
92 return relocate;
93 }
94};
95
96ElfReader::ElfReader(void* ptr) {
97 base = (char*)ptr;
98 base32 = (u32*)ptr;
99 header = (Elf32_Ehdr*)ptr;
100
101 segments = (Elf32_Phdr*)(base + header->e_phoff);
102 sections = (Elf32_Shdr*)(base + header->e_shoff);
103
104 entryPoint = header->e_entry;
105}
106
107const char* ElfReader::GetSectionName(int section) const {
108 if (sections[section].sh_type == ElfShtNull)
109 return nullptr;
110
111 int name_offset = sections[section].sh_name;
112 const char* ptr = reinterpret_cast<const char*>(GetSectionDataPtr(header->e_shstrndx));
113
114 if (ptr)
115 return ptr + name_offset;
116
117 return nullptr;
118}
119
120Kernel::CodeSet ElfReader::LoadInto(VAddr vaddr) {
121 LOG_DEBUG(Loader, "String section: {}", header->e_shstrndx);
122
123 // Should we relocate?
124 relocate = (header->e_type != ElfTypeExec);
125
126 if (relocate) {
127 LOG_DEBUG(Loader, "Relocatable module");
128 entryPoint += vaddr;
129 } else {
130 LOG_DEBUG(Loader, "Prerelocated executable");
131 }
132 LOG_DEBUG(Loader, "{} segments:", header->e_phnum);
133
134 // First pass : Get the bits into RAM
135 const VAddr base_addr = relocate ? vaddr : 0;
136
137 u64 total_image_size = 0;
138 for (unsigned int i = 0; i < header->e_phnum; ++i) {
139 const Elf32_Phdr* p = &segments[i];
140 if (p->p_type == ElfPtLoad) {
141 total_image_size += (p->p_memsz + 0xFFF) & ~0xFFF;
142 }
143 }
144
145 Kernel::PhysicalMemory program_image(total_image_size);
146 std::size_t current_image_position = 0;
147
148 Kernel::CodeSet codeset;
149
150 for (unsigned int i = 0; i < header->e_phnum; ++i) {
151 const Elf32_Phdr* p = &segments[i];
152 LOG_DEBUG(Loader, "Type: {} Vaddr: {:08X} Filesz: {:08X} Memsz: {:08X} ", p->p_type,
153 p->p_vaddr, p->p_filesz, p->p_memsz);
154
155 if (p->p_type == ElfPtLoad) {
156 Kernel::CodeSet::Segment* codeset_segment;
157 u32 permission_flags = p->p_flags & (ElfPfRead | ElfPfWrite | ElfPfExec);
158 if (permission_flags == (ElfPfRead | ElfPfExec)) {
159 codeset_segment = &codeset.CodeSegment();
160 } else if (permission_flags == (ElfPfRead)) {
161 codeset_segment = &codeset.RODataSegment();
162 } else if (permission_flags == (ElfPfRead | ElfPfWrite)) {
163 codeset_segment = &codeset.DataSegment();
164 } else {
165 LOG_ERROR(Loader, "Unexpected ELF PT_LOAD segment id {} with flags {:X}", i,
166 p->p_flags);
167 continue;
168 }
169
170 if (codeset_segment->size != 0) {
171 LOG_ERROR(Loader,
172 "ELF has more than one segment of the same type. Skipping extra "
173 "segment (id {})",
174 i);
175 continue;
176 }
177
178 const VAddr segment_addr = base_addr + p->p_vaddr;
179 const u32 aligned_size = (p->p_memsz + 0xFFF) & ~0xFFF;
180
181 codeset_segment->offset = current_image_position;
182 codeset_segment->addr = segment_addr;
183 codeset_segment->size = aligned_size;
184
185 std::memcpy(program_image.data() + current_image_position, GetSegmentPtr(i),
186 p->p_filesz);
187 current_image_position += aligned_size;
188 }
189 }
190
191 codeset.entrypoint = base_addr + header->e_entry;
192 codeset.memory = std::move(program_image);
193
194 LOG_DEBUG(Loader, "Done loading.");
195
196 return codeset;
197}
198
199SectionID ElfReader::GetSectionByName(const char* name, int firstSection) const {
200 for (int i = firstSection; i < header->e_shnum; i++) {
201 const char* secname = GetSectionName(i);
202
203 if (secname != nullptr && strcmp(name, secname) == 0)
204 return i;
205 }
206 return -1;
207}
208
209////////////////////////////////////////////////////////////////////////////////////////////////////
210// Loader namespace
211
212namespace Loader {
213
214AppLoader_ELF::AppLoader_ELF(FileSys::VirtualFile file_) : AppLoader(std::move(file_)) {}
215
216FileType AppLoader_ELF::IdentifyType(const FileSys::VirtualFile& elf_file) {
217 static constexpr u16 ELF_MACHINE_ARM{0x28};
218
219 u32 magic = 0;
220 if (4 != elf_file->ReadObject(&magic)) {
221 return FileType::Error;
222 }
223
224 u16 machine = 0;
225 if (2 != elf_file->ReadObject(&machine, 18)) {
226 return FileType::Error;
227 }
228
229 if (Common::MakeMagic('\x7f', 'E', 'L', 'F') == magic && ELF_MACHINE_ARM == machine) {
230 return FileType::ELF;
231 }
232
233 return FileType::Error;
234}
235
236AppLoader_ELF::LoadResult AppLoader_ELF::Load(Kernel::KProcess& process,
237 [[maybe_unused]] Core::System& system) {
238 if (is_loaded) {
239 return {ResultStatus::ErrorAlreadyLoaded, {}};
240 }
241
242 std::vector<u8> buffer = file->ReadAllBytes();
243 if (buffer.size() != file->GetSize()) {
244 return {ResultStatus::ErrorIncorrectELFFileSize, {}};
245 }
246
247 const VAddr base_address = process.PageTable().GetCodeRegionStart();
248 ElfReader elf_reader(&buffer[0]);
249 Kernel::CodeSet codeset = elf_reader.LoadInto(base_address);
250 const VAddr entry_point = codeset.entrypoint;
251
252 // Setup the process code layout
253 if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), buffer.size()).IsError()) {
254 return {ResultStatus::ErrorNotInitialized, {}};
255 }
256
257 process.LoadModule(std::move(codeset), entry_point);
258
259 is_loaded = true;
260 return {ResultStatus::Success, LoadParameters{48, Core::Memory::DEFAULT_STACK_SIZE}};
261}
262
263} // namespace Loader
diff --git a/src/core/loader/elf.h b/src/core/loader/elf.h
deleted file mode 100644
index acd33dc3d..000000000
--- a/src/core/loader/elf.h
+++ /dev/null
@@ -1,36 +0,0 @@
1// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
2// SPDX-FileCopyrightText: 2014 Citra Emulator Project
3// SPDX-License-Identifier: GPL-2.0-or-later
4
5#pragma once
6
7#include "core/loader/loader.h"
8
9namespace Core {
10class System;
11}
12
13namespace Loader {
14
15/// Loads an ELF/AXF file
16class AppLoader_ELF final : public AppLoader {
17public:
18 explicit AppLoader_ELF(FileSys::VirtualFile file);
19
20 /**
21 * Identifies whether or not the given file is an ELF file.
22 *
23 * @param elf_file The file to identify.
24 *
25 * @return FileType::ELF, or FileType::Error if the file is not an ELF file.
26 */
27 static FileType IdentifyType(const FileSys::VirtualFile& elf_file);
28
29 FileType GetFileType() const override {
30 return IdentifyType(file);
31 }
32
33 LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
34};
35
36} // namespace Loader
diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp
index 9af46a0f7..d8a1bf82a 100644
--- a/src/core/loader/kip.cpp
+++ b/src/core/loader/kip.cpp
@@ -14,7 +14,7 @@ namespace Loader {
14 14
15namespace { 15namespace {
16constexpr u32 PageAlignSize(u32 size) { 16constexpr u32 PageAlignSize(u32 size) {
17 return static_cast<u32>((size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK); 17 return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
18} 18}
19} // Anonymous namespace 19} // Anonymous namespace
20 20
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 994ee891f..104d16efa 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -12,7 +12,6 @@
12#include "core/core.h" 12#include "core/core.h"
13#include "core/hle/kernel/k_process.h" 13#include "core/hle/kernel/k_process.h"
14#include "core/loader/deconstructed_rom_directory.h" 14#include "core/loader/deconstructed_rom_directory.h"
15#include "core/loader/elf.h"
16#include "core/loader/kip.h" 15#include "core/loader/kip.h"
17#include "core/loader/nax.h" 16#include "core/loader/nax.h"
18#include "core/loader/nca.h" 17#include "core/loader/nca.h"
@@ -39,8 +38,6 @@ std::optional<FileType> IdentifyFileLoader(FileSys::VirtualFile file) {
39FileType IdentifyFile(FileSys::VirtualFile file) { 38FileType IdentifyFile(FileSys::VirtualFile file) {
40 if (const auto romdir_type = IdentifyFileLoader<AppLoader_DeconstructedRomDirectory>(file)) { 39 if (const auto romdir_type = IdentifyFileLoader<AppLoader_DeconstructedRomDirectory>(file)) {
41 return *romdir_type; 40 return *romdir_type;
42 } else if (const auto elf_type = IdentifyFileLoader<AppLoader_ELF>(file)) {
43 return *elf_type;
44 } else if (const auto nso_type = IdentifyFileLoader<AppLoader_NSO>(file)) { 41 } else if (const auto nso_type = IdentifyFileLoader<AppLoader_NSO>(file)) {
45 return *nso_type; 42 return *nso_type;
46 } else if (const auto nro_type = IdentifyFileLoader<AppLoader_NRO>(file)) { 43 } else if (const auto nro_type = IdentifyFileLoader<AppLoader_NRO>(file)) {
@@ -69,8 +66,6 @@ FileType GuessFromFilename(const std::string& name) {
69 const std::string extension = 66 const std::string extension =
70 Common::ToLower(std::string(Common::FS::GetExtensionFromFilename(name))); 67 Common::ToLower(std::string(Common::FS::GetExtensionFromFilename(name)));
71 68
72 if (extension == "elf")
73 return FileType::ELF;
74 if (extension == "nro") 69 if (extension == "nro")
75 return FileType::NRO; 70 return FileType::NRO;
76 if (extension == "nso") 71 if (extension == "nso")
@@ -89,8 +84,6 @@ FileType GuessFromFilename(const std::string& name) {
89 84
90std::string GetFileTypeString(FileType type) { 85std::string GetFileTypeString(FileType type) {
91 switch (type) { 86 switch (type) {
92 case FileType::ELF:
93 return "ELF";
94 case FileType::NRO: 87 case FileType::NRO:
95 return "NRO"; 88 return "NRO";
96 case FileType::NSO: 89 case FileType::NSO:
@@ -208,10 +201,6 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileSys::V
208 FileType type, u64 program_id, 201 FileType type, u64 program_id,
209 std::size_t program_index) { 202 std::size_t program_index) {
210 switch (type) { 203 switch (type) {
211 // Standard ELF file format.
212 case FileType::ELF:
213 return std::make_unique<AppLoader_ELF>(std::move(file));
214
215 // NX NSO file format. 204 // NX NSO file format.
216 case FileType::NSO: 205 case FileType::NSO:
217 return std::make_unique<AppLoader_NSO>(std::move(file)); 206 return std::make_unique<AppLoader_NSO>(std::move(file));
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 7bf4faaf1..7b43f70ed 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -34,7 +34,6 @@ namespace Loader {
34enum class FileType { 34enum class FileType {
35 Error, 35 Error,
36 Unknown, 36 Unknown,
37 ELF,
38 NSO, 37 NSO,
39 NRO, 38 NRO,
40 NCA, 39 NCA,
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 1b0bb0876..73d04d7ee 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -125,7 +125,7 @@ FileType AppLoader_NRO::IdentifyType(const FileSys::VirtualFile& nro_file) {
125} 125}
126 126
127static constexpr u32 PageAlignSize(u32 size) { 127static constexpr u32 PageAlignSize(u32 size) {
128 return static_cast<u32>((size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK); 128 return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
129} 129}
130 130
131static bool LoadNroImpl(Kernel::KProcess& process, const std::vector<u8>& data) { 131static bool LoadNroImpl(Kernel::KProcess& process, const std::vector<u8>& data) {
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 8dd956fc6..4c3b3c655 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -45,7 +45,7 @@ std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data,
45} 45}
46 46
47constexpr u32 PageAlignSize(u32 size) { 47constexpr u32 PageAlignSize(u32 size) {
48 return static_cast<u32>((size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK); 48 return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
49} 49}
50} // Anonymous namespace 50} // Anonymous namespace
51 51
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 1b44280b5..34ad7cadd 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -36,10 +36,11 @@ struct Memory::Impl {
36 } 36 }
37 37
38 void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, PAddr target) { 38 void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, PAddr target) {
39 ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); 39 ASSERT_MSG((size & YUZU_PAGEMASK) == 0, "non-page aligned size: {:016X}", size);
40 ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base); 40 ASSERT_MSG((base & YUZU_PAGEMASK) == 0, "non-page aligned base: {:016X}", base);
41 ASSERT_MSG(target >= DramMemoryMap::Base, "Out of bounds target: {:016X}", target); 41 ASSERT_MSG(target >= DramMemoryMap::Base, "Out of bounds target: {:016X}", target);
42 MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, Common::PageType::Memory); 42 MapPages(page_table, base / YUZU_PAGESIZE, size / YUZU_PAGESIZE, target,
43 Common::PageType::Memory);
43 44
44 if (Settings::IsFastmemEnabled()) { 45 if (Settings::IsFastmemEnabled()) {
45 system.DeviceMemory().buffer.Map(base, target - DramMemoryMap::Base, size); 46 system.DeviceMemory().buffer.Map(base, target - DramMemoryMap::Base, size);
@@ -47,9 +48,10 @@ struct Memory::Impl {
47 } 48 }
48 49
49 void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) { 50 void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) {
50 ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); 51 ASSERT_MSG((size & YUZU_PAGEMASK) == 0, "non-page aligned size: {:016X}", size);
51 ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base); 52 ASSERT_MSG((base & YUZU_PAGEMASK) == 0, "non-page aligned base: {:016X}", base);
52 MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, 0, Common::PageType::Unmapped); 53 MapPages(page_table, base / YUZU_PAGESIZE, size / YUZU_PAGESIZE, 0,
54 Common::PageType::Unmapped);
53 55
54 if (Settings::IsFastmemEnabled()) { 56 if (Settings::IsFastmemEnabled()) {
55 system.DeviceMemory().buffer.Unmap(base, size); 57 system.DeviceMemory().buffer.Unmap(base, size);
@@ -57,7 +59,7 @@ struct Memory::Impl {
57 } 59 }
58 60
59 [[nodiscard]] u8* GetPointerFromRasterizerCachedMemory(VAddr vaddr) const { 61 [[nodiscard]] u8* GetPointerFromRasterizerCachedMemory(VAddr vaddr) const {
60 const PAddr paddr{current_page_table->backing_addr[vaddr >> PAGE_BITS]}; 62 const PAddr paddr{current_page_table->backing_addr[vaddr >> YUZU_PAGEBITS]};
61 63
62 if (!paddr) { 64 if (!paddr) {
63 return {}; 65 return {};
@@ -67,7 +69,7 @@ struct Memory::Impl {
67 } 69 }
68 70
69 [[nodiscard]] u8* GetPointerFromDebugMemory(VAddr vaddr) const { 71 [[nodiscard]] u8* GetPointerFromDebugMemory(VAddr vaddr) const {
70 const PAddr paddr{current_page_table->backing_addr[vaddr >> PAGE_BITS]}; 72 const PAddr paddr{current_page_table->backing_addr[vaddr >> YUZU_PAGEBITS]};
71 73
72 if (paddr == 0) { 74 if (paddr == 0) {
73 return {}; 75 return {};
@@ -176,13 +178,14 @@ struct Memory::Impl {
176 auto on_unmapped, auto on_memory, auto on_rasterizer, auto increment) { 178 auto on_unmapped, auto on_memory, auto on_rasterizer, auto increment) {
177 const auto& page_table = process.PageTable().PageTableImpl(); 179 const auto& page_table = process.PageTable().PageTableImpl();
178 std::size_t remaining_size = size; 180 std::size_t remaining_size = size;
179 std::size_t page_index = addr >> PAGE_BITS; 181 std::size_t page_index = addr >> YUZU_PAGEBITS;
180 std::size_t page_offset = addr & PAGE_MASK; 182 std::size_t page_offset = addr & YUZU_PAGEMASK;
181 183
182 while (remaining_size) { 184 while (remaining_size) {
183 const std::size_t copy_amount = 185 const std::size_t copy_amount =
184 std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size); 186 std::min(static_cast<std::size_t>(YUZU_PAGESIZE) - page_offset, remaining_size);
185 const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); 187 const auto current_vaddr =
188 static_cast<VAddr>((page_index << YUZU_PAGEBITS) + page_offset);
186 189
187 const auto [pointer, type] = page_table.pointers[page_index].PointerType(); 190 const auto [pointer, type] = page_table.pointers[page_index].PointerType();
188 switch (type) { 191 switch (type) {
@@ -192,7 +195,7 @@ struct Memory::Impl {
192 } 195 }
193 case Common::PageType::Memory: { 196 case Common::PageType::Memory: {
194 DEBUG_ASSERT(pointer); 197 DEBUG_ASSERT(pointer);
195 u8* mem_ptr = pointer + page_offset + (page_index << PAGE_BITS); 198 u8* mem_ptr = pointer + page_offset + (page_index << YUZU_PAGEBITS);
196 on_memory(copy_amount, mem_ptr); 199 on_memory(copy_amount, mem_ptr);
197 break; 200 break;
198 } 201 }
@@ -339,10 +342,10 @@ struct Memory::Impl {
339 // Iterate over a contiguous CPU address space, marking/unmarking the region. 342 // Iterate over a contiguous CPU address space, marking/unmarking the region.
340 // The region is at a granularity of CPU pages. 343 // The region is at a granularity of CPU pages.
341 344
342 const u64 num_pages = ((vaddr + size - 1) >> PAGE_BITS) - (vaddr >> PAGE_BITS) + 1; 345 const u64 num_pages = ((vaddr + size - 1) >> YUZU_PAGEBITS) - (vaddr >> YUZU_PAGEBITS) + 1;
343 for (u64 i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) { 346 for (u64 i = 0; i < num_pages; ++i, vaddr += YUZU_PAGESIZE) {
344 const Common::PageType page_type{ 347 const Common::PageType page_type{
345 current_page_table->pointers[vaddr >> PAGE_BITS].Type()}; 348 current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Type()};
346 if (debug) { 349 if (debug) {
347 // Switch page type to debug if now debug 350 // Switch page type to debug if now debug
348 switch (page_type) { 351 switch (page_type) {
@@ -354,7 +357,7 @@ struct Memory::Impl {
354 // Page is already marked. 357 // Page is already marked.
355 break; 358 break;
356 case Common::PageType::Memory: 359 case Common::PageType::Memory:
357 current_page_table->pointers[vaddr >> PAGE_BITS].Store( 360 current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Store(
358 nullptr, Common::PageType::DebugMemory); 361 nullptr, Common::PageType::DebugMemory);
359 break; 362 break;
360 default: 363 default:
@@ -371,9 +374,9 @@ struct Memory::Impl {
371 // Don't mess with already non-debug or rasterizer memory. 374 // Don't mess with already non-debug or rasterizer memory.
372 break; 375 break;
373 case Common::PageType::DebugMemory: { 376 case Common::PageType::DebugMemory: {
374 u8* const pointer{GetPointerFromDebugMemory(vaddr & ~PAGE_MASK)}; 377 u8* const pointer{GetPointerFromDebugMemory(vaddr & ~YUZU_PAGEMASK)};
375 current_page_table->pointers[vaddr >> PAGE_BITS].Store( 378 current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Store(
376 pointer - (vaddr & ~PAGE_MASK), Common::PageType::Memory); 379 pointer - (vaddr & ~YUZU_PAGEMASK), Common::PageType::Memory);
377 break; 380 break;
378 } 381 }
379 default: 382 default:
@@ -398,10 +401,10 @@ struct Memory::Impl {
398 // granularity of CPU pages, hence why we iterate on a CPU page basis (note: GPU page size 401 // granularity of CPU pages, hence why we iterate on a CPU page basis (note: GPU page size
399 // is different). This assumes the specified GPU address region is contiguous as well. 402 // is different). This assumes the specified GPU address region is contiguous as well.
400 403
401 const u64 num_pages = ((vaddr + size - 1) >> PAGE_BITS) - (vaddr >> PAGE_BITS) + 1; 404 const u64 num_pages = ((vaddr + size - 1) >> YUZU_PAGEBITS) - (vaddr >> YUZU_PAGEBITS) + 1;
402 for (u64 i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) { 405 for (u64 i = 0; i < num_pages; ++i, vaddr += YUZU_PAGESIZE) {
403 const Common::PageType page_type{ 406 const Common::PageType page_type{
404 current_page_table->pointers[vaddr >> PAGE_BITS].Type()}; 407 current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Type()};
405 if (cached) { 408 if (cached) {
406 // Switch page type to cached if now cached 409 // Switch page type to cached if now cached
407 switch (page_type) { 410 switch (page_type) {
@@ -411,7 +414,7 @@ struct Memory::Impl {
411 break; 414 break;
412 case Common::PageType::DebugMemory: 415 case Common::PageType::DebugMemory:
413 case Common::PageType::Memory: 416 case Common::PageType::Memory:
414 current_page_table->pointers[vaddr >> PAGE_BITS].Store( 417 current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Store(
415 nullptr, Common::PageType::RasterizerCachedMemory); 418 nullptr, Common::PageType::RasterizerCachedMemory);
416 break; 419 break;
417 case Common::PageType::RasterizerCachedMemory: 420 case Common::PageType::RasterizerCachedMemory:
@@ -434,16 +437,16 @@ struct Memory::Impl {
434 // that this area is already unmarked as cached. 437 // that this area is already unmarked as cached.
435 break; 438 break;
436 case Common::PageType::RasterizerCachedMemory: { 439 case Common::PageType::RasterizerCachedMemory: {
437 u8* const pointer{GetPointerFromRasterizerCachedMemory(vaddr & ~PAGE_MASK)}; 440 u8* const pointer{GetPointerFromRasterizerCachedMemory(vaddr & ~YUZU_PAGEMASK)};
438 if (pointer == nullptr) { 441 if (pointer == nullptr) {
439 // It's possible that this function has been called while updating the 442 // It's possible that this function has been called while updating the
440 // pagetable after unmapping a VMA. In that case the underlying VMA will no 443 // pagetable after unmapping a VMA. In that case the underlying VMA will no
441 // longer exist, and we should just leave the pagetable entry blank. 444 // longer exist, and we should just leave the pagetable entry blank.
442 current_page_table->pointers[vaddr >> PAGE_BITS].Store( 445 current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Store(
443 nullptr, Common::PageType::Unmapped); 446 nullptr, Common::PageType::Unmapped);
444 } else { 447 } else {
445 current_page_table->pointers[vaddr >> PAGE_BITS].Store( 448 current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Store(
446 pointer - (vaddr & ~PAGE_MASK), Common::PageType::Memory); 449 pointer - (vaddr & ~YUZU_PAGEMASK), Common::PageType::Memory);
447 } 450 }
448 break; 451 break;
449 } 452 }
@@ -465,8 +468,8 @@ struct Memory::Impl {
465 */ 468 */
466 void MapPages(Common::PageTable& page_table, VAddr base, u64 size, PAddr target, 469 void MapPages(Common::PageTable& page_table, VAddr base, u64 size, PAddr target,
467 Common::PageType type) { 470 Common::PageType type) {
468 LOG_DEBUG(HW_Memory, "Mapping {:016X} onto {:016X}-{:016X}", target, base * PAGE_SIZE, 471 LOG_DEBUG(HW_Memory, "Mapping {:016X} onto {:016X}-{:016X}", target, base * YUZU_PAGESIZE,
469 (base + size) * PAGE_SIZE); 472 (base + size) * YUZU_PAGESIZE);
470 473
471 // During boot, current_page_table might not be set yet, in which case we need not flush 474 // During boot, current_page_table might not be set yet, in which case we need not flush
472 if (system.IsPoweredOn()) { 475 if (system.IsPoweredOn()) {
@@ -474,7 +477,7 @@ struct Memory::Impl {
474 for (u64 i = 0; i < size; i++) { 477 for (u64 i = 0; i < size; i++) {
475 const auto page = base + i; 478 const auto page = base + i;
476 if (page_table.pointers[page].Type() == Common::PageType::RasterizerCachedMemory) { 479 if (page_table.pointers[page].Type() == Common::PageType::RasterizerCachedMemory) {
477 gpu.FlushAndInvalidateRegion(page << PAGE_BITS, PAGE_SIZE); 480 gpu.FlushAndInvalidateRegion(page << YUZU_PAGEBITS, YUZU_PAGESIZE);
478 } 481 }
479 } 482 }
480 } 483 }
@@ -485,7 +488,7 @@ struct Memory::Impl {
485 488
486 if (!target) { 489 if (!target) {
487 ASSERT_MSG(type != Common::PageType::Memory, 490 ASSERT_MSG(type != Common::PageType::Memory,
488 "Mapping memory page without a pointer @ {:016x}", base * PAGE_SIZE); 491 "Mapping memory page without a pointer @ {:016x}", base * YUZU_PAGESIZE);
489 492
490 while (base != end) { 493 while (base != end) {
491 page_table.pointers[base].Store(nullptr, type); 494 page_table.pointers[base].Store(nullptr, type);
@@ -496,14 +499,14 @@ struct Memory::Impl {
496 } else { 499 } else {
497 while (base != end) { 500 while (base != end) {
498 page_table.pointers[base].Store( 501 page_table.pointers[base].Store(
499 system.DeviceMemory().GetPointer(target) - (base << PAGE_BITS), type); 502 system.DeviceMemory().GetPointer(target) - (base << YUZU_PAGEBITS), type);
500 page_table.backing_addr[base] = target - (base << PAGE_BITS); 503 page_table.backing_addr[base] = target - (base << YUZU_PAGEBITS);
501 504
502 ASSERT_MSG(page_table.pointers[base].Pointer(), 505 ASSERT_MSG(page_table.pointers[base].Pointer(),
503 "memory mapping base yield a nullptr within the table"); 506 "memory mapping base yield a nullptr within the table");
504 507
505 base += 1; 508 base += 1;
506 target += PAGE_SIZE; 509 target += YUZU_PAGESIZE;
507 } 510 }
508 } 511 }
509 } 512 }
@@ -518,7 +521,7 @@ struct Memory::Impl {
518 } 521 }
519 522
520 // Avoid adding any extra logic to this fast-path block 523 // Avoid adding any extra logic to this fast-path block
521 const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw(); 524 const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Raw();
522 if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) { 525 if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) {
523 return &pointer[vaddr]; 526 return &pointer[vaddr];
524 } 527 }
@@ -657,7 +660,7 @@ void Memory::UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) {
657bool Memory::IsValidVirtualAddress(const VAddr vaddr) const { 660bool Memory::IsValidVirtualAddress(const VAddr vaddr) const {
658 const Kernel::KProcess& process = *system.CurrentProcess(); 661 const Kernel::KProcess& process = *system.CurrentProcess();
659 const auto& page_table = process.PageTable().PageTableImpl(); 662 const auto& page_table = process.PageTable().PageTableImpl();
660 const size_t page = vaddr >> PAGE_BITS; 663 const size_t page = vaddr >> YUZU_PAGEBITS;
661 if (page >= page_table.pointers.size()) { 664 if (page >= page_table.pointers.size()) {
662 return false; 665 return false;
663 } 666 }
@@ -668,9 +671,9 @@ bool Memory::IsValidVirtualAddress(const VAddr vaddr) const {
668 671
669bool Memory::IsValidVirtualAddressRange(VAddr base, u64 size) const { 672bool Memory::IsValidVirtualAddressRange(VAddr base, u64 size) const {
670 VAddr end = base + size; 673 VAddr end = base + size;
671 VAddr page = Common::AlignDown(base, PAGE_SIZE); 674 VAddr page = Common::AlignDown(base, YUZU_PAGESIZE);
672 675
673 for (; page < end; page += PAGE_SIZE) { 676 for (; page < end; page += YUZU_PAGESIZE) {
674 if (!IsValidVirtualAddress(page)) { 677 if (!IsValidVirtualAddress(page)) {
675 return false; 678 return false;
676 } 679 }
diff --git a/src/core/memory.h b/src/core/memory.h
index 2a21fbcfd..a11ff8766 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -27,9 +27,9 @@ namespace Core::Memory {
27 * Page size used by the ARM architecture. This is the smallest granularity with which memory can 27 * Page size used by the ARM architecture. This is the smallest granularity with which memory can
28 * be mapped. 28 * be mapped.
29 */ 29 */
30constexpr std::size_t PAGE_BITS = 12; 30constexpr std::size_t YUZU_PAGEBITS = 12;
31constexpr u64 PAGE_SIZE = 1ULL << PAGE_BITS; 31constexpr u64 YUZU_PAGESIZE = 1ULL << YUZU_PAGEBITS;
32constexpr u64 PAGE_MASK = PAGE_SIZE - 1; 32constexpr u64 YUZU_PAGEMASK = YUZU_PAGESIZE - 1;
33 33
34/// Virtual user-space memory regions 34/// Virtual user-space memory regions
35enum : VAddr { 35enum : VAddr {
diff --git a/src/dedicated_room/CMakeLists.txt b/src/dedicated_room/CMakeLists.txt
new file mode 100644
index 000000000..1efdbc1f7
--- /dev/null
+++ b/src/dedicated_room/CMakeLists.txt
@@ -0,0 +1,27 @@
1# SPDX-FileCopyrightText: 2017 Citra Emulator Project
2# SPDX-License-Identifier: GPL-2.0-or-later
3
4set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
5
6add_executable(yuzu-room
7 yuzu_room.cpp
8 yuzu_room.rc
9)
10
11create_target_directory_groups(yuzu-room)
12
13target_link_libraries(yuzu-room PRIVATE common network)
14if (ENABLE_WEB_SERVICE)
15 target_compile_definitions(yuzu-room PRIVATE -DENABLE_WEB_SERVICE)
16 target_link_libraries(yuzu-room PRIVATE web_service)
17endif()
18
19target_link_libraries(yuzu-room PRIVATE mbedtls mbedcrypto)
20if (MSVC)
21 target_link_libraries(yuzu-room PRIVATE getopt)
22endif()
23target_link_libraries(yuzu-room PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
24
25if(UNIX AND NOT APPLE)
26 install(TARGETS yuzu-room RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
27endif()
diff --git a/src/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp
new file mode 100644
index 000000000..7b6deba41
--- /dev/null
+++ b/src/dedicated_room/yuzu_room.cpp
@@ -0,0 +1,382 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <chrono>
5#include <fstream>
6#include <iostream>
7#include <memory>
8#include <regex>
9#include <string>
10#include <thread>
11
12#ifdef _WIN32
13// windows.h needs to be included before shellapi.h
14#include <windows.h>
15
16#include <shellapi.h>
17#endif
18
19#include <mbedtls/base64.h>
20#include "common/common_types.h"
21#include "common/detached_tasks.h"
22#include "common/fs/file.h"
23#include "common/fs/fs.h"
24#include "common/fs/path_util.h"
25#include "common/logging/backend.h"
26#include "common/logging/log.h"
27#include "common/scm_rev.h"
28#include "common/settings.h"
29#include "common/string_util.h"
30#include "core/core.h"
31#include "network/announce_multiplayer_session.h"
32#include "network/network.h"
33#include "network/room.h"
34#include "network/verify_user.h"
35
36#ifdef ENABLE_WEB_SERVICE
37#include "web_service/verify_user_jwt.h"
38#endif
39
40#undef _UNICODE
41#include <getopt.h>
42#ifndef _MSC_VER
43#include <unistd.h>
44#endif
45
46static void PrintHelp(const char* argv0) {
47 LOG_INFO(Network,
48 "Usage: {}"
49 " [options] <filename>\n"
50 "--room-name The name of the room\n"
51 "--room-description The room description\n"
52 "--port The port used for the room\n"
53 "--max_members The maximum number of players for this room\n"
54 "--password The password for the room\n"
55 "--preferred-game The preferred game for this room\n"
56 "--preferred-game-id The preferred game-id for this room\n"
57 "--username The username used for announce\n"
58 "--token The token used for announce\n"
59 "--web-api-url yuzu Web API url\n"
60 "--ban-list-file The file for storing the room ban list\n"
61 "--log-file The file for storing the room log\n"
62 "--enable-yuzu-mods Allow yuzu Community Moderators to moderate on your room\n"
63 "-h, --help Display this help and exit\n"
64 "-v, --version Output version information and exit\n",
65 argv0);
66}
67
68static void PrintVersion() {
69 LOG_INFO(Network, "yuzu dedicated room {} {} Libnetwork: {}", Common::g_scm_branch,
70 Common::g_scm_desc, Network::network_version);
71}
72
73/// The magic text at the beginning of a yuzu-room ban list file.
74static constexpr char BanListMagic[] = "YuzuRoom-BanList-1";
75
76static constexpr char token_delimiter{':'};
77
78static void PadToken(std::string& token) {
79 while (token.size() % 4 != 0) {
80 token.push_back('=');
81 }
82}
83
84static std::string UsernameFromDisplayToken(const std::string& display_token) {
85 std::size_t outlen;
86
87 std::array<unsigned char, 512> output{};
88 mbedtls_base64_decode(output.data(), output.size(), &outlen,
89 reinterpret_cast<const unsigned char*>(display_token.c_str()),
90 display_token.length());
91 std::string decoded_display_token(reinterpret_cast<char*>(&output), outlen);
92 return decoded_display_token.substr(0, decoded_display_token.find(token_delimiter));
93}
94
95static std::string TokenFromDisplayToken(const std::string& display_token) {
96 std::size_t outlen;
97
98 std::array<unsigned char, 512> output{};
99 mbedtls_base64_decode(output.data(), output.size(), &outlen,
100 reinterpret_cast<const unsigned char*>(display_token.c_str()),
101 display_token.length());
102 std::string decoded_display_token(reinterpret_cast<char*>(&output), outlen);
103 return decoded_display_token.substr(decoded_display_token.find(token_delimiter) + 1);
104}
105
106static Network::Room::BanList LoadBanList(const std::string& path) {
107 std::ifstream file;
108 Common::FS::OpenFileStream(file, path, std::ios_base::in);
109 if (!file || file.eof()) {
110 LOG_ERROR(Network, "Could not open ban list!");
111 return {};
112 }
113 std::string magic;
114 std::getline(file, magic);
115 if (magic != BanListMagic) {
116 LOG_ERROR(Network, "Ban list is not valid!");
117 return {};
118 }
119
120 // false = username ban list, true = ip ban list
121 bool ban_list_type = false;
122 Network::Room::UsernameBanList username_ban_list;
123 Network::Room::IPBanList ip_ban_list;
124 while (!file.eof()) {
125 std::string line;
126 std::getline(file, line);
127 line.erase(std::remove(line.begin(), line.end(), '\0'), line.end());
128 line = Common::StripSpaces(line);
129 if (line.empty()) {
130 // An empty line marks start of the IP ban list
131 ban_list_type = true;
132 continue;
133 }
134 if (ban_list_type) {
135 ip_ban_list.emplace_back(line);
136 } else {
137 username_ban_list.emplace_back(line);
138 }
139 }
140
141 return {username_ban_list, ip_ban_list};
142}
143
144static void SaveBanList(const Network::Room::BanList& ban_list, const std::string& path) {
145 std::ofstream file;
146 Common::FS::OpenFileStream(file, path, std::ios_base::out);
147 if (!file) {
148 LOG_ERROR(Network, "Could not save ban list!");
149 return;
150 }
151
152 file << BanListMagic << "\n";
153
154 // Username ban list
155 for (const auto& username : ban_list.first) {
156 file << username << "\n";
157 }
158 file << "\n";
159
160 // IP ban list
161 for (const auto& ip : ban_list.second) {
162 file << ip << "\n";
163 }
164}
165
166static void InitializeLogging(const std::string& log_file) {
167 Common::Log::Initialize();
168 Common::Log::SetColorConsoleBackendEnabled(true);
169 Common::Log::Start();
170}
171
172/// Application entry point
173int main(int argc, char** argv) {
174 Common::DetachedTasks detached_tasks;
175 int option_index = 0;
176 char* endarg;
177
178 std::string room_name;
179 std::string room_description;
180 std::string password;
181 std::string preferred_game;
182 std::string username;
183 std::string token;
184 std::string web_api_url;
185 std::string ban_list_file;
186 std::string log_file = "yuzu-room.log";
187 u64 preferred_game_id = 0;
188 u32 port = Network::DefaultRoomPort;
189 u32 max_members = 16;
190 bool enable_yuzu_mods = false;
191
192 static struct option long_options[] = {
193 {"room-name", required_argument, 0, 'n'},
194 {"room-description", required_argument, 0, 'd'},
195 {"port", required_argument, 0, 'p'},
196 {"max_members", required_argument, 0, 'm'},
197 {"password", required_argument, 0, 'w'},
198 {"preferred-game", required_argument, 0, 'g'},
199 {"preferred-game-id", required_argument, 0, 'i'},
200 {"username", optional_argument, 0, 'u'},
201 {"token", required_argument, 0, 't'},
202 {"web-api-url", required_argument, 0, 'a'},
203 {"ban-list-file", required_argument, 0, 'b'},
204 {"log-file", required_argument, 0, 'l'},
205 {"enable-yuzu-mods", no_argument, 0, 'e'},
206 {"help", no_argument, 0, 'h'},
207 {"version", no_argument, 0, 'v'},
208 {0, 0, 0, 0},
209 };
210
211 InitializeLogging(log_file);
212
213 while (optind < argc) {
214 int arg = getopt_long(argc, argv, "n:d:p:m:w:g:u:t:a:i:l:hv", long_options, &option_index);
215 if (arg != -1) {
216 switch (static_cast<char>(arg)) {
217 case 'n':
218 room_name.assign(optarg);
219 break;
220 case 'd':
221 room_description.assign(optarg);
222 break;
223 case 'p':
224 port = strtoul(optarg, &endarg, 0);
225 break;
226 case 'm':
227 max_members = strtoul(optarg, &endarg, 0);
228 break;
229 case 'w':
230 password.assign(optarg);
231 break;
232 case 'g':
233 preferred_game.assign(optarg);
234 break;
235 case 'i':
236 preferred_game_id = strtoull(optarg, &endarg, 16);
237 break;
238 case 'u':
239 username.assign(optarg);
240 break;
241 case 't':
242 token.assign(optarg);
243 break;
244 case 'a':
245 web_api_url.assign(optarg);
246 break;
247 case 'b':
248 ban_list_file.assign(optarg);
249 break;
250 case 'l':
251 log_file.assign(optarg);
252 break;
253 case 'e':
254 enable_yuzu_mods = true;
255 break;
256 case 'h':
257 PrintHelp(argv[0]);
258 return 0;
259 case 'v':
260 PrintVersion();
261 return 0;
262 }
263 }
264 }
265
266 if (room_name.empty()) {
267 LOG_ERROR(Network, "Room name is empty!");
268 PrintHelp(argv[0]);
269 return -1;
270 }
271 if (preferred_game.empty()) {
272 LOG_ERROR(Network, "Preferred game is empty!");
273 PrintHelp(argv[0]);
274 return -1;
275 }
276 if (preferred_game_id == 0) {
277 LOG_ERROR(Network,
278 "preferred-game-id not set!\nThis should get set to allow users to find your "
279 "room.\nSet with --preferred-game-id id");
280 }
281 if (max_members > Network::MaxConcurrentConnections || max_members < 2) {
282 LOG_ERROR(Network, "max_members needs to be in the range 2 - {}!",
283 Network::MaxConcurrentConnections);
284 PrintHelp(argv[0]);
285 return -1;
286 }
287 if (port > UINT16_MAX) {
288 LOG_ERROR(Network, "Port needs to be in the range 0 - 65535!");
289 PrintHelp(argv[0]);
290 return -1;
291 }
292 if (ban_list_file.empty()) {
293 LOG_ERROR(Network, "Ban list file not set!\nThis should get set to load and save room ban "
294 "list.\nSet with --ban-list-file <file>");
295 }
296 bool announce = true;
297 if (token.empty() && announce) {
298 announce = false;
299 LOG_INFO(Network, "Token is empty: Hosting a private room");
300 }
301 if (web_api_url.empty() && announce) {
302 announce = false;
303 LOG_INFO(Network, "Endpoint url is empty: Hosting a private room");
304 }
305 if (announce) {
306 if (username.empty()) {
307 LOG_INFO(Network, "Hosting a public room");
308 Settings::values.web_api_url = web_api_url;
309 PadToken(token);
310 Settings::values.yuzu_username = UsernameFromDisplayToken(token);
311 username = Settings::values.yuzu_username.GetValue();
312 Settings::values.yuzu_token = TokenFromDisplayToken(token);
313 } else {
314 LOG_INFO(Network, "Hosting a public room");
315 Settings::values.web_api_url = web_api_url;
316 Settings::values.yuzu_username = username;
317 Settings::values.yuzu_token = token;
318 }
319 }
320 if (!announce && enable_yuzu_mods) {
321 enable_yuzu_mods = false;
322 LOG_INFO(Network, "Can not enable yuzu Moderators for private rooms");
323 }
324
325 // Load the ban list
326 Network::Room::BanList ban_list;
327 if (!ban_list_file.empty()) {
328 ban_list = LoadBanList(ban_list_file);
329 }
330
331 std::unique_ptr<Network::VerifyUser::Backend> verify_backend;
332 if (announce) {
333#ifdef ENABLE_WEB_SERVICE
334 verify_backend =
335 std::make_unique<WebService::VerifyUserJWT>(Settings::values.web_api_url.GetValue());
336#else
337 LOG_INFO(Network,
338 "yuzu Web Services is not available with this build: validation is disabled.");
339 verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
340#endif
341 } else {
342 verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
343 }
344
345 Network::RoomNetwork network{};
346 network.Init();
347 if (auto room = network.GetRoom().lock()) {
348 AnnounceMultiplayerRoom::GameInfo preferred_game_info{.name = preferred_game,
349 .id = preferred_game_id};
350 if (!room->Create(room_name, room_description, "", port, password, max_members, username,
351 preferred_game_info, std::move(verify_backend), ban_list,
352 enable_yuzu_mods)) {
353 LOG_INFO(Network, "Failed to create room: ");
354 return -1;
355 }
356 LOG_INFO(Network, "Room is open. Close with Q+Enter...");
357 auto announce_session = std::make_unique<Core::AnnounceMultiplayerSession>(network);
358 if (announce) {
359 announce_session->Start();
360 }
361 while (room->GetState() == Network::Room::State::Open) {
362 std::string in;
363 std::cin >> in;
364 if (in.size() > 0) {
365 break;
366 }
367 std::this_thread::sleep_for(std::chrono::milliseconds(100));
368 }
369 if (announce) {
370 announce_session->Stop();
371 }
372 announce_session.reset();
373 // Save the ban list
374 if (!ban_list_file.empty()) {
375 SaveBanList(room->GetBanList(), ban_list_file);
376 }
377 room->Destroy();
378 }
379 network.Shutdown();
380 detached_tasks.WaitForAllTasks();
381 return 0;
382}
diff --git a/src/dedicated_room/yuzu_room.rc b/src/dedicated_room/yuzu_room.rc
new file mode 100644
index 000000000..a08957684
--- /dev/null
+++ b/src/dedicated_room/yuzu_room.rc
@@ -0,0 +1,20 @@
1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "winresrc.h"
5/////////////////////////////////////////////////////////////////////////////
6//
7// Icon
8//
9
10// Icon with lowest ID value placed first to ensure application icon
11// remains consistent on all systems.
12YUZU_ICON ICON "../../dist/yuzu.ico"
13
14
15/////////////////////////////////////////////////////////////////////////////
16//
17// RT_MANIFEST
18//
19
200 RT_MANIFEST "../../dist/yuzu.manifest"
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index de388ec4c..5cc1ccbd9 100644
--- a/src/input_common/drivers/sdl_driver.cpp
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -40,13 +40,13 @@ public:
40 void EnableMotion() { 40 void EnableMotion() {
41 if (sdl_controller) { 41 if (sdl_controller) {
42 SDL_GameController* controller = sdl_controller.get(); 42 SDL_GameController* controller = sdl_controller.get();
43 if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) && !has_accel) { 43 has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL);
44 has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO);
45 if (has_accel) {
44 SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); 46 SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
45 has_accel = true;
46 } 47 }
47 if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) && !has_gyro) { 48 if (has_gyro) {
48 SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); 49 SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE);
49 has_gyro = true;
50 } 50 }
51 } 51 }
52 } 52 }
@@ -305,6 +305,7 @@ void SDLDriver::InitJoystick(int joystick_index) {
305 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller); 305 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
306 PreSetController(joystick->GetPadIdentifier()); 306 PreSetController(joystick->GetPadIdentifier());
307 SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel()); 307 SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel());
308 joystick->EnableMotion();
308 joystick_map[guid].emplace_back(std::move(joystick)); 309 joystick_map[guid].emplace_back(std::move(joystick));
309 return; 310 return;
310 } 311 }
@@ -316,6 +317,7 @@ void SDLDriver::InitJoystick(int joystick_index) {
316 317
317 if (joystick_it != joystick_guid_list.end()) { 318 if (joystick_it != joystick_guid_list.end()) {
318 (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller); 319 (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);
320 (*joystick_it)->EnableMotion();
319 return; 321 return;
320 } 322 }
321 323
@@ -323,6 +325,7 @@ void SDLDriver::InitJoystick(int joystick_index) {
323 auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller); 325 auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller);
324 PreSetController(joystick->GetPadIdentifier()); 326 PreSetController(joystick->GetPadIdentifier());
325 SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel()); 327 SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel());
328 joystick->EnableMotion();
326 joystick_guid_list.emplace_back(std::move(joystick)); 329 joystick_guid_list.emplace_back(std::move(joystick));
327} 330}
328 331
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp
index 133422d5c..ffb9b945e 100644
--- a/src/input_common/input_poller.cpp
+++ b/src/input_common/input_poller.cpp
@@ -824,6 +824,7 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateAnalogDevice(
824 .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f), 824 .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f),
825 .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f), 825 .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f),
826 .inverted = params.Get("invert", "+") == "-", 826 .inverted = params.Get("invert", "+") == "-",
827 .toggle = static_cast<bool>(params.Get("toggle", false)),
827 }; 828 };
828 input_engine->PreSetController(identifier); 829 input_engine->PreSetController(identifier);
829 input_engine->PreSetAxis(identifier, axis); 830 input_engine->PreSetAxis(identifier, axis);
diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt
index 312f79b68..6f8ca4b90 100644
--- a/src/network/CMakeLists.txt
+++ b/src/network/CMakeLists.txt
@@ -2,6 +2,8 @@
2# SPDX-License-Identifier: GPL-3.0-or-later 2# SPDX-License-Identifier: GPL-3.0-or-later
3 3
4add_library(network STATIC 4add_library(network STATIC
5 announce_multiplayer_session.cpp
6 announce_multiplayer_session.h
5 network.cpp 7 network.cpp
6 network.h 8 network.h
7 packet.cpp 9 packet.cpp
@@ -17,3 +19,7 @@ add_library(network STATIC
17create_target_directory_groups(network) 19create_target_directory_groups(network)
18 20
19target_link_libraries(network PRIVATE common enet Boost::boost) 21target_link_libraries(network PRIVATE common enet Boost::boost)
22if (ENABLE_WEB_SERVICE)
23 target_compile_definitions(network PRIVATE -DENABLE_WEB_SERVICE)
24 target_link_libraries(network PRIVATE web_service)
25endif()
diff --git a/src/core/announce_multiplayer_session.cpp b/src/network/announce_multiplayer_session.cpp
index d73a488cf..6737ce85a 100644
--- a/src/core/announce_multiplayer_session.cpp
+++ b/src/network/announce_multiplayer_session.cpp
@@ -31,7 +31,7 @@ AnnounceMultiplayerSession::AnnounceMultiplayerSession(Network::RoomNetwork& roo
31} 31}
32 32
33WebService::WebResult AnnounceMultiplayerSession::Register() { 33WebService::WebResult AnnounceMultiplayerSession::Register() {
34 std::shared_ptr<Network::Room> room = room_network.GetRoom().lock(); 34 auto room = room_network.GetRoom().lock();
35 if (!room) { 35 if (!room) {
36 return WebService::WebResult{WebService::WebResult::Code::LibError, 36 return WebService::WebResult{WebService::WebResult::Code::LibError,
37 "Network is not initialized", ""}; 37 "Network is not initialized", ""};
@@ -102,7 +102,7 @@ void AnnounceMultiplayerSession::UpdateBackendData(std::shared_ptr<Network::Room
102void AnnounceMultiplayerSession::AnnounceMultiplayerLoop() { 102void AnnounceMultiplayerSession::AnnounceMultiplayerLoop() {
103 // Invokes all current bound error callbacks. 103 // Invokes all current bound error callbacks.
104 const auto ErrorCallback = [this](WebService::WebResult result) { 104 const auto ErrorCallback = [this](WebService::WebResult result) {
105 std::lock_guard<std::mutex> lock(callback_mutex); 105 std::lock_guard lock(callback_mutex);
106 for (auto callback : error_callbacks) { 106 for (auto callback : error_callbacks) {
107 (*callback)(result); 107 (*callback)(result);
108 } 108 }
@@ -120,7 +120,7 @@ void AnnounceMultiplayerSession::AnnounceMultiplayerLoop() {
120 std::future<WebService::WebResult> future; 120 std::future<WebService::WebResult> future;
121 while (!shutdown_event.WaitUntil(update_time)) { 121 while (!shutdown_event.WaitUntil(update_time)) {
122 update_time += announce_time_interval; 122 update_time += announce_time_interval;
123 std::shared_ptr<Network::Room> room = room_network.GetRoom().lock(); 123 auto room = room_network.GetRoom().lock();
124 if (!room) { 124 if (!room) {
125 break; 125 break;
126 } 126 }
diff --git a/src/core/announce_multiplayer_session.h b/src/network/announce_multiplayer_session.h
index db790f7d2..db790f7d2 100644
--- a/src/core/announce_multiplayer_session.h
+++ b/src/network/announce_multiplayer_session.h
diff --git a/src/network/room.cpp b/src/network/room.cpp
index 3fc3a0383..8c63b255b 100644
--- a/src/network/room.cpp
+++ b/src/network/room.cpp
@@ -20,9 +20,7 @@ namespace Network {
20 20
21class Room::RoomImpl { 21class Room::RoomImpl {
22public: 22public:
23 // This MAC address is used to generate a 'Nintendo' like Mac address. 23 std::mt19937 random_gen; ///< Random number generator. Used for GenerateFakeIPAddress
24 const MacAddress NintendoOUI;
25 std::mt19937 random_gen; ///< Random number generator. Used for GenerateMacAddress
26 24
27 ENetHost* server = nullptr; ///< Network interface. 25 ENetHost* server = nullptr; ///< Network interface.
28 26
@@ -35,10 +33,9 @@ public:
35 std::string password; ///< The password required to connect to this room. 33 std::string password; ///< The password required to connect to this room.
36 34
37 struct Member { 35 struct Member {
38 std::string nickname; ///< The nickname of the member. 36 std::string nickname; ///< The nickname of the member.
39 std::string console_id_hash; ///< A hash of the console ID of the member. 37 GameInfo game_info; ///< The current game of the member
40 GameInfo game_info; ///< The current game of the member 38 IPv4Address fake_ip; ///< The assigned fake ip address of the member.
41 MacAddress mac_address; ///< The assigned mac address of the member.
42 /// Data of the user, often including authenticated forum username. 39 /// Data of the user, often including authenticated forum username.
43 VerifyUser::UserData user_data; 40 VerifyUser::UserData user_data;
44 ENetPeer* peer; ///< The remote peer. 41 ENetPeer* peer; ///< The remote peer.
@@ -51,8 +48,7 @@ public:
51 IPBanList ip_ban_list; ///< List of banned IP addresses 48 IPBanList ip_ban_list; ///< List of banned IP addresses
52 mutable std::mutex ban_list_mutex; ///< Mutex for the ban lists 49 mutable std::mutex ban_list_mutex; ///< Mutex for the ban lists
53 50
54 RoomImpl() 51 RoomImpl() : random_gen(std::random_device()()) {}
55 : NintendoOUI{0x00, 0x1F, 0x32, 0x00, 0x00, 0x00}, random_gen(std::random_device()()) {}
56 52
57 /// Thread that receives and dispatches network packets 53 /// Thread that receives and dispatches network packets
58 std::unique_ptr<std::thread> room_thread; 54 std::unique_ptr<std::thread> room_thread;
@@ -101,16 +97,10 @@ public:
101 bool IsValidNickname(const std::string& nickname) const; 97 bool IsValidNickname(const std::string& nickname) const;
102 98
103 /** 99 /**
104 * Returns whether the MAC address is valid, ie. isn't already taken by someone else in the 100 * Returns whether the fake ip address is valid, ie. isn't already taken by someone else in the
105 * room. 101 * room.
106 */ 102 */
107 bool IsValidMacAddress(const MacAddress& address) const; 103 bool IsValidFakeIPAddress(const IPv4Address& address) const;
108
109 /**
110 * Returns whether the console ID (hash) is valid, ie. isn't already taken by someone else in
111 * the room.
112 */
113 bool IsValidConsoleId(const std::string& console_id_hash) const;
114 104
115 /** 105 /**
116 * Returns whether a user has mod permissions. 106 * Returns whether a user has mod permissions.
@@ -128,15 +118,9 @@ public:
128 void SendNameCollision(ENetPeer* client); 118 void SendNameCollision(ENetPeer* client);
129 119
130 /** 120 /**
131 * Sends a ID_ROOM_MAC_COLLISION message telling the client that the MAC is invalid. 121 * Sends a ID_ROOM_IP_COLLISION message telling the client that the IP is invalid.
132 */ 122 */
133 void SendMacCollision(ENetPeer* client); 123 void SendIPCollision(ENetPeer* client);
134
135 /**
136 * Sends a IdConsoleIdCollison message telling the client that another member with the same
137 * console ID exists.
138 */
139 void SendConsoleIdCollision(ENetPeer* client);
140 124
141 /** 125 /**
142 * Sends a ID_ROOM_VERSION_MISMATCH message telling the client that the version is invalid. 126 * Sends a ID_ROOM_VERSION_MISMATCH message telling the client that the version is invalid.
@@ -152,13 +136,13 @@ public:
152 * Notifies the member that its connection attempt was successful, 136 * Notifies the member that its connection attempt was successful,
153 * and it is now part of the room. 137 * and it is now part of the room.
154 */ 138 */
155 void SendJoinSuccess(ENetPeer* client, MacAddress mac_address); 139 void SendJoinSuccess(ENetPeer* client, IPv4Address fake_ip);
156 140
157 /** 141 /**
158 * Notifies the member that its connection attempt was successful, 142 * Notifies the member that its connection attempt was successful,
159 * and it is now part of the room, and it has been granted mod permissions. 143 * and it is now part of the room, and it has been granted mod permissions.
160 */ 144 */
161 void SendJoinSuccessAsMod(ENetPeer* client, MacAddress mac_address); 145 void SendJoinSuccessAsMod(ENetPeer* client, IPv4Address fake_ip);
162 146
163 /** 147 /**
164 * Sends a IdHostKicked message telling the client that they have been kicked. 148 * Sends a IdHostKicked message telling the client that they have been kicked.
@@ -210,7 +194,7 @@ public:
210 * <u32> num_members: the number of currently joined clients 194 * <u32> num_members: the number of currently joined clients
211 * This is followed by the following three values for each member: 195 * This is followed by the following three values for each member:
212 * <String> nickname of that member 196 * <String> nickname of that member
213 * <MacAddress> mac_address of that member 197 * <IPv4Address> fake_ip of that member
214 * <String> game_name of that member 198 * <String> game_name of that member
215 */ 199 */
216 void BroadcastRoomInformation(); 200 void BroadcastRoomInformation();
@@ -219,13 +203,13 @@ public:
219 * Generates a free MAC address to assign to a new client. 203 * Generates a free MAC address to assign to a new client.
220 * The first 3 bytes are the NintendoOUI 0x00, 0x1F, 0x32 204 * The first 3 bytes are the NintendoOUI 0x00, 0x1F, 0x32
221 */ 205 */
222 MacAddress GenerateMacAddress(); 206 IPv4Address GenerateFakeIPAddress();
223 207
224 /** 208 /**
225 * Broadcasts this packet to all members except the sender. 209 * Broadcasts this packet to all members except the sender.
226 * @param event The ENet event containing the data 210 * @param event The ENet event containing the data
227 */ 211 */
228 void HandleWifiPacket(const ENetEvent* event); 212 void HandleProxyPacket(const ENetEvent* event);
229 213
230 /** 214 /**
231 * Extracts a chat entry from a received ENet packet and adds it to the chat queue. 215 * Extracts a chat entry from a received ENet packet and adds it to the chat queue.
@@ -237,7 +221,7 @@ public:
237 * Extracts the game name from a received ENet packet and broadcasts it. 221 * Extracts the game name from a received ENet packet and broadcasts it.
238 * @param event The ENet event that was received. 222 * @param event The ENet event that was received.
239 */ 223 */
240 void HandleGameNamePacket(const ENetEvent* event); 224 void HandleGameInfoPacket(const ENetEvent* event);
241 225
242 /** 226 /**
243 * Removes the client from the members list if it was in it and announces the change 227 * Removes the client from the members list if it was in it and announces the change
@@ -250,7 +234,7 @@ public:
250void Room::RoomImpl::ServerLoop() { 234void Room::RoomImpl::ServerLoop() {
251 while (state != State::Closed) { 235 while (state != State::Closed) {
252 ENetEvent event; 236 ENetEvent event;
253 if (enet_host_service(server, &event, 16) > 0) { 237 if (enet_host_service(server, &event, 5) > 0) {
254 switch (event.type) { 238 switch (event.type) {
255 case ENET_EVENT_TYPE_RECEIVE: 239 case ENET_EVENT_TYPE_RECEIVE:
256 switch (event.packet->data[0]) { 240 switch (event.packet->data[0]) {
@@ -258,10 +242,10 @@ void Room::RoomImpl::ServerLoop() {
258 HandleJoinRequest(&event); 242 HandleJoinRequest(&event);
259 break; 243 break;
260 case IdSetGameInfo: 244 case IdSetGameInfo:
261 HandleGameNamePacket(&event); 245 HandleGameInfoPacket(&event);
262 break; 246 break;
263 case IdWifiPacket: 247 case IdProxyPacket:
264 HandleWifiPacket(&event); 248 HandleProxyPacket(&event);
265 break; 249 break;
266 case IdChatMessage: 250 case IdChatMessage:
267 HandleChatPacket(&event); 251 HandleChatPacket(&event);
@@ -313,11 +297,8 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
313 std::string nickname; 297 std::string nickname;
314 packet.Read(nickname); 298 packet.Read(nickname);
315 299
316 std::string console_id_hash; 300 IPv4Address preferred_fake_ip;
317 packet.Read(console_id_hash); 301 packet.Read(preferred_fake_ip);
318
319 MacAddress preferred_mac;
320 packet.Read(preferred_mac);
321 302
322 u32 client_version; 303 u32 client_version;
323 packet.Read(client_version); 304 packet.Read(client_version);
@@ -338,20 +319,15 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
338 return; 319 return;
339 } 320 }
340 321
341 if (preferred_mac != NoPreferredMac) { 322 if (preferred_fake_ip != NoPreferredIP) {
342 // Verify if the preferred mac is available 323 // Verify if the preferred fake ip is available
343 if (!IsValidMacAddress(preferred_mac)) { 324 if (!IsValidFakeIPAddress(preferred_fake_ip)) {
344 SendMacCollision(event->peer); 325 SendIPCollision(event->peer);
345 return; 326 return;
346 } 327 }
347 } else { 328 } else {
348 // Assign a MAC address of this client automatically 329 // Assign a fake ip address of this client automatically
349 preferred_mac = GenerateMacAddress(); 330 preferred_fake_ip = GenerateFakeIPAddress();
350 }
351
352 if (!IsValidConsoleId(console_id_hash)) {
353 SendConsoleIdCollision(event->peer);
354 return;
355 } 331 }
356 332
357 if (client_version != network_version) { 333 if (client_version != network_version) {
@@ -361,8 +337,7 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
361 337
362 // At this point the client is ready to be added to the room. 338 // At this point the client is ready to be added to the room.
363 Member member{}; 339 Member member{};
364 member.mac_address = preferred_mac; 340 member.fake_ip = preferred_fake_ip;
365 member.console_id_hash = console_id_hash;
366 member.nickname = nickname; 341 member.nickname = nickname;
367 member.peer = event->peer; 342 member.peer = event->peer;
368 343
@@ -408,9 +383,9 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
408 // Notify everyone that the room information has changed. 383 // Notify everyone that the room information has changed.
409 BroadcastRoomInformation(); 384 BroadcastRoomInformation();
410 if (HasModPermission(event->peer)) { 385 if (HasModPermission(event->peer)) {
411 SendJoinSuccessAsMod(event->peer, preferred_mac); 386 SendJoinSuccessAsMod(event->peer, preferred_fake_ip);
412 } else { 387 } else {
413 SendJoinSuccess(event->peer, preferred_mac); 388 SendJoinSuccess(event->peer, preferred_fake_ip);
414 } 389 }
415} 390}
416 391
@@ -575,19 +550,11 @@ bool Room::RoomImpl::IsValidNickname(const std::string& nickname) const {
575 [&nickname](const auto& member) { return member.nickname != nickname; }); 550 [&nickname](const auto& member) { return member.nickname != nickname; });
576} 551}
577 552
578bool Room::RoomImpl::IsValidMacAddress(const MacAddress& address) const { 553bool Room::RoomImpl::IsValidFakeIPAddress(const IPv4Address& address) const {
579 // A MAC address is valid if it is not already taken by anybody else in the room. 554 // An IP address is valid if it is not already taken by anybody else in the room.
580 std::lock_guard lock(member_mutex); 555 std::lock_guard lock(member_mutex);
581 return std::all_of(members.begin(), members.end(), 556 return std::all_of(members.begin(), members.end(),
582 [&address](const auto& member) { return member.mac_address != address; }); 557 [&address](const auto& member) { return member.fake_ip != address; });
583}
584
585bool Room::RoomImpl::IsValidConsoleId(const std::string& console_id_hash) const {
586 // A Console ID is valid if it is not already taken by anybody else in the room.
587 std::lock_guard lock(member_mutex);
588 return std::all_of(members.begin(), members.end(), [&console_id_hash](const auto& member) {
589 return member.console_id_hash != console_id_hash;
590 });
591} 558}
592 559
593bool Room::RoomImpl::HasModPermission(const ENetPeer* client) const { 560bool Room::RoomImpl::HasModPermission(const ENetPeer* client) const {
@@ -621,19 +588,9 @@ void Room::RoomImpl::SendNameCollision(ENetPeer* client) {
621 enet_host_flush(server); 588 enet_host_flush(server);
622} 589}
623 590
624void Room::RoomImpl::SendMacCollision(ENetPeer* client) { 591void Room::RoomImpl::SendIPCollision(ENetPeer* client) {
625 Packet packet; 592 Packet packet;
626 packet.Write(static_cast<u8>(IdMacCollision)); 593 packet.Write(static_cast<u8>(IdIpCollision));
627
628 ENetPacket* enet_packet =
629 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
630 enet_peer_send(client, 0, enet_packet);
631 enet_host_flush(server);
632}
633
634void Room::RoomImpl::SendConsoleIdCollision(ENetPeer* client) {
635 Packet packet;
636 packet.Write(static_cast<u8>(IdConsoleIdCollision));
637 594
638 ENetPacket* enet_packet = 595 ENetPacket* enet_packet =
639 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); 596 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
@@ -672,20 +629,20 @@ void Room::RoomImpl::SendVersionMismatch(ENetPeer* client) {
672 enet_host_flush(server); 629 enet_host_flush(server);
673} 630}
674 631
675void Room::RoomImpl::SendJoinSuccess(ENetPeer* client, MacAddress mac_address) { 632void Room::RoomImpl::SendJoinSuccess(ENetPeer* client, IPv4Address fake_ip) {
676 Packet packet; 633 Packet packet;
677 packet.Write(static_cast<u8>(IdJoinSuccess)); 634 packet.Write(static_cast<u8>(IdJoinSuccess));
678 packet.Write(mac_address); 635 packet.Write(fake_ip);
679 ENetPacket* enet_packet = 636 ENetPacket* enet_packet =
680 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); 637 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
681 enet_peer_send(client, 0, enet_packet); 638 enet_peer_send(client, 0, enet_packet);
682 enet_host_flush(server); 639 enet_host_flush(server);
683} 640}
684 641
685void Room::RoomImpl::SendJoinSuccessAsMod(ENetPeer* client, MacAddress mac_address) { 642void Room::RoomImpl::SendJoinSuccessAsMod(ENetPeer* client, IPv4Address fake_ip) {
686 Packet packet; 643 Packet packet;
687 packet.Write(static_cast<u8>(IdJoinSuccessAsMod)); 644 packet.Write(static_cast<u8>(IdJoinSuccessAsMod));
688 packet.Write(mac_address); 645 packet.Write(fake_ip);
689 ENetPacket* enet_packet = 646 ENetPacket* enet_packet =
690 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); 647 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
691 enet_peer_send(client, 0, enet_packet); 648 enet_peer_send(client, 0, enet_packet);
@@ -818,9 +775,10 @@ void Room::RoomImpl::BroadcastRoomInformation() {
818 std::lock_guard lock(member_mutex); 775 std::lock_guard lock(member_mutex);
819 for (const auto& member : members) { 776 for (const auto& member : members) {
820 packet.Write(member.nickname); 777 packet.Write(member.nickname);
821 packet.Write(member.mac_address); 778 packet.Write(member.fake_ip);
822 packet.Write(member.game_info.name); 779 packet.Write(member.game_info.name);
823 packet.Write(member.game_info.id); 780 packet.Write(member.game_info.id);
781 packet.Write(member.game_info.version);
824 packet.Write(member.user_data.username); 782 packet.Write(member.user_data.username);
825 packet.Write(member.user_data.display_name); 783 packet.Write(member.user_data.display_name);
826 packet.Write(member.user_data.avatar_url); 784 packet.Write(member.user_data.avatar_url);
@@ -833,34 +791,44 @@ void Room::RoomImpl::BroadcastRoomInformation() {
833 enet_host_flush(server); 791 enet_host_flush(server);
834} 792}
835 793
836MacAddress Room::RoomImpl::GenerateMacAddress() { 794IPv4Address Room::RoomImpl::GenerateFakeIPAddress() {
837 MacAddress result_mac = 795 IPv4Address result_ip{192, 168, 0, 0};
838 NintendoOUI; // The first three bytes of each MAC address will be the NintendoOUI 796 std::uniform_int_distribution<> dis(0x01, 0xFE); // Random byte between 1 and 0xFE
839 std::uniform_int_distribution<> dis(0x00, 0xFF); // Random byte between 0 and 0xFF
840 do { 797 do {
841 for (std::size_t i = 3; i < result_mac.size(); ++i) { 798 for (std::size_t i = 2; i < result_ip.size(); ++i) {
842 result_mac[i] = dis(random_gen); 799 result_ip[i] = dis(random_gen);
843 } 800 }
844 } while (!IsValidMacAddress(result_mac)); 801 } while (!IsValidFakeIPAddress(result_ip));
845 return result_mac; 802
803 return result_ip;
846} 804}
847 805
848void Room::RoomImpl::HandleWifiPacket(const ENetEvent* event) { 806void Room::RoomImpl::HandleProxyPacket(const ENetEvent* event) {
849 Packet in_packet; 807 Packet in_packet;
850 in_packet.Append(event->packet->data, event->packet->dataLength); 808 in_packet.Append(event->packet->data, event->packet->dataLength);
851 in_packet.IgnoreBytes(sizeof(u8)); // Message type 809 in_packet.IgnoreBytes(sizeof(u8)); // Message type
852 in_packet.IgnoreBytes(sizeof(u8)); // WifiPacket Type 810
853 in_packet.IgnoreBytes(sizeof(u8)); // WifiPacket Channel 811 in_packet.IgnoreBytes(sizeof(u8)); // Domain
854 in_packet.IgnoreBytes(sizeof(MacAddress)); // WifiPacket Transmitter Address 812 in_packet.IgnoreBytes(sizeof(IPv4Address)); // IP
855 MacAddress destination_address; 813 in_packet.IgnoreBytes(sizeof(u16)); // Port
856 in_packet.Read(destination_address); 814
815 in_packet.IgnoreBytes(sizeof(u8)); // Domain
816 IPv4Address remote_ip;
817 in_packet.Read(remote_ip); // IP
818 in_packet.IgnoreBytes(sizeof(u16)); // Port
819
820 in_packet.IgnoreBytes(sizeof(u8)); // Protocol
821
822 bool broadcast;
823 in_packet.Read(broadcast); // Broadcast
857 824
858 Packet out_packet; 825 Packet out_packet;
859 out_packet.Append(event->packet->data, event->packet->dataLength); 826 out_packet.Append(event->packet->data, event->packet->dataLength);
860 ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(), 827 ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(),
861 ENET_PACKET_FLAG_RELIABLE); 828 ENET_PACKET_FLAG_RELIABLE);
862 829
863 if (destination_address == BroadcastMac) { // Send the data to everyone except the sender 830 const auto& destination_address = remote_ip;
831 if (broadcast) { // Send the data to everyone except the sender
864 std::lock_guard lock(member_mutex); 832 std::lock_guard lock(member_mutex);
865 bool sent_packet = false; 833 bool sent_packet = false;
866 for (const auto& member : members) { 834 for (const auto& member : members) {
@@ -877,16 +845,16 @@ void Room::RoomImpl::HandleWifiPacket(const ENetEvent* event) {
877 std::lock_guard lock(member_mutex); 845 std::lock_guard lock(member_mutex);
878 auto member = std::find_if(members.begin(), members.end(), 846 auto member = std::find_if(members.begin(), members.end(),
879 [destination_address](const Member& member_entry) -> bool { 847 [destination_address](const Member& member_entry) -> bool {
880 return member_entry.mac_address == destination_address; 848 return member_entry.fake_ip == destination_address;
881 }); 849 });
882 if (member != members.end()) { 850 if (member != members.end()) {
883 enet_peer_send(member->peer, 0, enet_packet); 851 enet_peer_send(member->peer, 0, enet_packet);
884 } else { 852 } else {
885 LOG_ERROR(Network, 853 LOG_ERROR(Network,
886 "Attempting to send to unknown MAC address: " 854 "Attempting to send to unknown IP address: "
887 "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", 855 "{}.{}.{}.{}",
888 destination_address[0], destination_address[1], destination_address[2], 856 destination_address[0], destination_address[1], destination_address[2],
889 destination_address[3], destination_address[4], destination_address[5]); 857 destination_address[3]);
890 enet_packet_destroy(enet_packet); 858 enet_packet_destroy(enet_packet);
891 } 859 }
892 } 860 }
@@ -943,7 +911,7 @@ void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) {
943 } 911 }
944} 912}
945 913
946void Room::RoomImpl::HandleGameNamePacket(const ENetEvent* event) { 914void Room::RoomImpl::HandleGameInfoPacket(const ENetEvent* event) {
947 Packet in_packet; 915 Packet in_packet;
948 in_packet.Append(event->packet->data, event->packet->dataLength); 916 in_packet.Append(event->packet->data, event->packet->dataLength);
949 917
@@ -951,6 +919,7 @@ void Room::RoomImpl::HandleGameNamePacket(const ENetEvent* event) {
951 GameInfo game_info; 919 GameInfo game_info;
952 in_packet.Read(game_info.name); 920 in_packet.Read(game_info.name);
953 in_packet.Read(game_info.id); 921 in_packet.Read(game_info.id);
922 in_packet.Read(game_info.version);
954 923
955 { 924 {
956 std::lock_guard lock(member_mutex); 925 std::lock_guard lock(member_mutex);
@@ -969,7 +938,8 @@ void Room::RoomImpl::HandleGameNamePacket(const ENetEvent* event) {
969 if (game_info.name.empty()) { 938 if (game_info.name.empty()) {
970 LOG_INFO(Network, "{} is not playing", display_name); 939 LOG_INFO(Network, "{} is not playing", display_name);
971 } else { 940 } else {
972 LOG_INFO(Network, "{} is playing {}", display_name, game_info.name); 941 LOG_INFO(Network, "{} is playing {} ({})", display_name, game_info.name,
942 game_info.version);
973 } 943 }
974 } 944 }
975 } 945 }
@@ -1073,7 +1043,7 @@ std::vector<Member> Room::GetRoomMemberList() const {
1073 member.username = member_impl.user_data.username; 1043 member.username = member_impl.user_data.username;
1074 member.display_name = member_impl.user_data.display_name; 1044 member.display_name = member_impl.user_data.display_name;
1075 member.avatar_url = member_impl.user_data.avatar_url; 1045 member.avatar_url = member_impl.user_data.avatar_url;
1076 member.mac_address = member_impl.mac_address; 1046 member.fake_ip = member_impl.fake_ip;
1077 member.game = member_impl.game_info; 1047 member.game = member_impl.game_info;
1078 member_list.push_back(member); 1048 member_list.push_back(member);
1079 } 1049 }
diff --git a/src/network/room.h b/src/network/room.h
index 6f7e3b5b5..c2a4b1a70 100644
--- a/src/network/room.h
+++ b/src/network/room.h
@@ -9,12 +9,12 @@
9#include <vector> 9#include <vector>
10#include "common/announce_multiplayer_room.h" 10#include "common/announce_multiplayer_room.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "common/socket_types.h"
12#include "network/verify_user.h" 13#include "network/verify_user.h"
13 14
14namespace Network { 15namespace Network {
15 16
16using AnnounceMultiplayerRoom::GameInfo; 17using AnnounceMultiplayerRoom::GameInfo;
17using AnnounceMultiplayerRoom::MacAddress;
18using AnnounceMultiplayerRoom::Member; 18using AnnounceMultiplayerRoom::Member;
19using AnnounceMultiplayerRoom::RoomInformation; 19using AnnounceMultiplayerRoom::RoomInformation;
20 20
@@ -29,12 +29,9 @@ static constexpr u32 MaxConcurrentConnections = 254;
29 29
30constexpr std::size_t NumChannels = 1; // Number of channels used for the connection 30constexpr std::size_t NumChannels = 1; // Number of channels used for the connection
31 31
32/// A special MAC address that tells the room we're joining to assign us a MAC address 32/// A special IP address that tells the room we're joining to assign us a IP address
33/// automatically. 33/// automatically.
34constexpr MacAddress NoPreferredMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; 34constexpr IPv4Address NoPreferredIP = {0xFF, 0xFF, 0xFF, 0xFF};
35
36// 802.11 broadcast MAC address
37constexpr MacAddress BroadcastMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
38 35
39// The different types of messages that can be sent. The first byte of each packet defines the type 36// The different types of messages that can be sent. The first byte of each packet defines the type
40enum RoomMessageTypes : u8 { 37enum RoomMessageTypes : u8 {
@@ -42,15 +39,14 @@ enum RoomMessageTypes : u8 {
42 IdJoinSuccess, 39 IdJoinSuccess,
43 IdRoomInformation, 40 IdRoomInformation,
44 IdSetGameInfo, 41 IdSetGameInfo,
45 IdWifiPacket, 42 IdProxyPacket,
46 IdChatMessage, 43 IdChatMessage,
47 IdNameCollision, 44 IdNameCollision,
48 IdMacCollision, 45 IdIpCollision,
49 IdVersionMismatch, 46 IdVersionMismatch,
50 IdWrongPassword, 47 IdWrongPassword,
51 IdCloseRoom, 48 IdCloseRoom,
52 IdRoomIsFull, 49 IdRoomIsFull,
53 IdConsoleIdCollision,
54 IdStatusMessage, 50 IdStatusMessage,
55 IdHostKicked, 51 IdHostKicked,
56 IdHostBanned, 52 IdHostBanned,
diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp
index e4f823e98..06818af78 100644
--- a/src/network/room_member.cpp
+++ b/src/network/room_member.cpp
@@ -7,6 +7,7 @@
7#include <set> 7#include <set>
8#include <thread> 8#include <thread>
9#include "common/assert.h" 9#include "common/assert.h"
10#include "common/socket_types.h"
10#include "enet/enet.h" 11#include "enet/enet.h"
11#include "network/packet.h" 12#include "network/packet.h"
12#include "network/room_member.h" 13#include "network/room_member.h"
@@ -38,7 +39,7 @@ public:
38 std::string username; ///< The username of this member. 39 std::string username; ///< The username of this member.
39 mutable std::mutex username_mutex; ///< Mutex for locking username. 40 mutable std::mutex username_mutex; ///< Mutex for locking username.
40 41
41 MacAddress mac_address; ///< The mac_address of this member. 42 IPv4Address fake_ip; ///< The fake ip of this member.
42 43
43 std::mutex network_mutex; ///< Mutex that controls access to the `client` variable. 44 std::mutex network_mutex; ///< Mutex that controls access to the `client` variable.
44 /// Thread that receives and dispatches network packets 45 /// Thread that receives and dispatches network packets
@@ -56,7 +57,7 @@ public:
56 CallbackSet<T>& Get(); 57 CallbackSet<T>& Get();
57 58
58 private: 59 private:
59 CallbackSet<WifiPacket> callback_set_wifi_packet; 60 CallbackSet<ProxyPacket> callback_set_proxy_packet;
60 CallbackSet<ChatEntry> callback_set_chat_messages; 61 CallbackSet<ChatEntry> callback_set_chat_messages;
61 CallbackSet<StatusMessageEntry> callback_set_status_messages; 62 CallbackSet<StatusMessageEntry> callback_set_status_messages;
62 CallbackSet<RoomInformation> callback_set_room_information; 63 CallbackSet<RoomInformation> callback_set_room_information;
@@ -78,15 +79,15 @@ public:
78 79
79 /** 80 /**
80 * Sends a request to the server, asking for permission to join a room with the specified 81 * Sends a request to the server, asking for permission to join a room with the specified
81 * nickname and preferred mac. 82 * nickname and preferred fake ip.
82 * @params nickname The desired nickname. 83 * @params nickname The desired nickname.
83 * @params console_id_hash A hash of the Console ID. 84 * @params preferred_fake_ip The preferred IP address to use in the room, the NoPreferredIP
84 * @params preferred_mac The preferred MAC address to use in the room, the NoPreferredMac tells 85 * tells
85 * @params password The password for the room 86 * @params password The password for the room
86 * the server to assign one for us. 87 * the server to assign one for us.
87 */ 88 */
88 void SendJoinRequest(const std::string& nickname_, const std::string& console_id_hash, 89 void SendJoinRequest(const std::string& nickname_,
89 const MacAddress& preferred_mac = NoPreferredMac, 90 const IPv4Address& preferred_fake_ip = NoPreferredIP,
90 const std::string& password = "", const std::string& token = ""); 91 const std::string& password = "", const std::string& token = "");
91 92
92 /** 93 /**
@@ -101,10 +102,10 @@ public:
101 void HandleRoomInformationPacket(const ENetEvent* event); 102 void HandleRoomInformationPacket(const ENetEvent* event);
102 103
103 /** 104 /**
104 * Extracts a WifiPacket from a received ENet packet. 105 * Extracts a ProxyPacket from a received ENet packet.
105 * @param event The ENet event that was received. 106 * @param event The ENet event that was received.
106 */ 107 */
107 void HandleWifiPackets(const ENetEvent* event); 108 void HandleProxyPackets(const ENetEvent* event);
108 109
109 /** 110 /**
110 * Extracts a chat entry from a received ENet packet and adds it to the chat queue. 111 * Extracts a chat entry from a received ENet packet and adds it to the chat queue.
@@ -158,12 +159,12 @@ void RoomMember::RoomMemberImpl::MemberLoop() {
158 while (IsConnected()) { 159 while (IsConnected()) {
159 std::lock_guard lock(network_mutex); 160 std::lock_guard lock(network_mutex);
160 ENetEvent event; 161 ENetEvent event;
161 if (enet_host_service(client, &event, 16) > 0) { 162 if (enet_host_service(client, &event, 5) > 0) {
162 switch (event.type) { 163 switch (event.type) {
163 case ENET_EVENT_TYPE_RECEIVE: 164 case ENET_EVENT_TYPE_RECEIVE:
164 switch (event.packet->data[0]) { 165 switch (event.packet->data[0]) {
165 case IdWifiPacket: 166 case IdProxyPacket:
166 HandleWifiPackets(&event); 167 HandleProxyPackets(&event);
167 break; 168 break;
168 case IdChatMessage: 169 case IdChatMessage:
169 HandleChatPacket(&event); 170 HandleChatPacket(&event);
@@ -198,13 +199,9 @@ void RoomMember::RoomMemberImpl::MemberLoop() {
198 SetState(State::Idle); 199 SetState(State::Idle);
199 SetError(Error::NameCollision); 200 SetError(Error::NameCollision);
200 break; 201 break;
201 case IdMacCollision: 202 case IdIpCollision:
202 SetState(State::Idle);
203 SetError(Error::MacCollision);
204 break;
205 case IdConsoleIdCollision:
206 SetState(State::Idle); 203 SetState(State::Idle);
207 SetError(Error::ConsoleIdCollision); 204 SetError(Error::IpCollision);
208 break; 205 break;
209 case IdVersionMismatch: 206 case IdVersionMismatch:
210 SetState(State::Idle); 207 SetState(State::Idle);
@@ -275,15 +272,13 @@ void RoomMember::RoomMemberImpl::Send(Packet&& packet) {
275} 272}
276 273
277void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname_, 274void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname_,
278 const std::string& console_id_hash, 275 const IPv4Address& preferred_fake_ip,
279 const MacAddress& preferred_mac,
280 const std::string& password, 276 const std::string& password,
281 const std::string& token) { 277 const std::string& token) {
282 Packet packet; 278 Packet packet;
283 packet.Write(static_cast<u8>(IdJoinRequest)); 279 packet.Write(static_cast<u8>(IdJoinRequest));
284 packet.Write(nickname_); 280 packet.Write(nickname_);
285 packet.Write(console_id_hash); 281 packet.Write(preferred_fake_ip);
286 packet.Write(preferred_mac);
287 packet.Write(network_version); 282 packet.Write(network_version);
288 packet.Write(password); 283 packet.Write(password);
289 packet.Write(token); 284 packet.Write(token);
@@ -317,9 +312,10 @@ void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* ev
317 312
318 for (auto& member : member_information) { 313 for (auto& member : member_information) {
319 packet.Read(member.nickname); 314 packet.Read(member.nickname);
320 packet.Read(member.mac_address); 315 packet.Read(member.fake_ip);
321 packet.Read(member.game_info.name); 316 packet.Read(member.game_info.name);
322 packet.Read(member.game_info.id); 317 packet.Read(member.game_info.id);
318 packet.Read(member.game_info.version);
323 packet.Read(member.username); 319 packet.Read(member.username);
324 packet.Read(member.display_name); 320 packet.Read(member.display_name);
325 packet.Read(member.avatar_url); 321 packet.Read(member.avatar_url);
@@ -342,29 +338,38 @@ void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) {
342 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type 338 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
343 339
344 // Parse the MAC Address from the packet 340 // Parse the MAC Address from the packet
345 packet.Read(mac_address); 341 packet.Read(fake_ip);
346} 342}
347 343
348void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) { 344void RoomMember::RoomMemberImpl::HandleProxyPackets(const ENetEvent* event) {
349 WifiPacket wifi_packet{}; 345 ProxyPacket proxy_packet{};
350 Packet packet; 346 Packet packet;
351 packet.Append(event->packet->data, event->packet->dataLength); 347 packet.Append(event->packet->data, event->packet->dataLength);
352 348
353 // Ignore the first byte, which is the message id. 349 // Ignore the first byte, which is the message id.
354 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type 350 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
355 351
356 // Parse the WifiPacket from the packet 352 // Parse the ProxyPacket from the packet
357 u8 frame_type; 353 u8 local_family;
358 packet.Read(frame_type); 354 packet.Read(local_family);
359 WifiPacket::PacketType type = static_cast<WifiPacket::PacketType>(frame_type); 355 proxy_packet.local_endpoint.family = static_cast<Domain>(local_family);
356 packet.Read(proxy_packet.local_endpoint.ip);
357 packet.Read(proxy_packet.local_endpoint.portno);
358
359 u8 remote_family;
360 packet.Read(remote_family);
361 proxy_packet.remote_endpoint.family = static_cast<Domain>(remote_family);
362 packet.Read(proxy_packet.remote_endpoint.ip);
363 packet.Read(proxy_packet.remote_endpoint.portno);
364
365 u8 protocol_type;
366 packet.Read(protocol_type);
367 proxy_packet.protocol = static_cast<Protocol>(protocol_type);
360 368
361 wifi_packet.type = type; 369 packet.Read(proxy_packet.broadcast);
362 packet.Read(wifi_packet.channel); 370 packet.Read(proxy_packet.data);
363 packet.Read(wifi_packet.transmitter_address);
364 packet.Read(wifi_packet.destination_address);
365 packet.Read(wifi_packet.data);
366 371
367 Invoke<WifiPacket>(wifi_packet); 372 Invoke<ProxyPacket>(proxy_packet);
368} 373}
369 374
370void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) { 375void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) {
@@ -440,8 +445,8 @@ void RoomMember::RoomMemberImpl::Disconnect() {
440} 445}
441 446
442template <> 447template <>
443RoomMember::RoomMemberImpl::CallbackSet<WifiPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() { 448RoomMember::RoomMemberImpl::CallbackSet<ProxyPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() {
444 return callback_set_wifi_packet; 449 return callback_set_proxy_packet;
445} 450}
446 451
447template <> 452template <>
@@ -525,19 +530,18 @@ const std::string& RoomMember::GetUsername() const {
525 return room_member_impl->username; 530 return room_member_impl->username;
526} 531}
527 532
528const MacAddress& RoomMember::GetMacAddress() const { 533const IPv4Address& RoomMember::GetFakeIpAddress() const {
529 ASSERT_MSG(IsConnected(), "Tried to get MAC address while not connected"); 534 ASSERT_MSG(IsConnected(), "Tried to get fake ip address while not connected");
530 return room_member_impl->mac_address; 535 return room_member_impl->fake_ip;
531} 536}
532 537
533RoomInformation RoomMember::GetRoomInformation() const { 538RoomInformation RoomMember::GetRoomInformation() const {
534 return room_member_impl->room_information; 539 return room_member_impl->room_information;
535} 540}
536 541
537void RoomMember::Join(const std::string& nick, const std::string& console_id_hash, 542void RoomMember::Join(const std::string& nick, const char* server_addr, u16 server_port,
538 const char* server_addr, u16 server_port, u16 client_port, 543 u16 client_port, const IPv4Address& preferred_fake_ip,
539 const MacAddress& preferred_mac, const std::string& password, 544 const std::string& password, const std::string& token) {
540 const std::string& token) {
541 // If the member is connected, kill the connection first 545 // If the member is connected, kill the connection first
542 if (room_member_impl->loop_thread && room_member_impl->loop_thread->joinable()) { 546 if (room_member_impl->loop_thread && room_member_impl->loop_thread->joinable()) {
543 Leave(); 547 Leave();
@@ -571,7 +575,7 @@ void RoomMember::Join(const std::string& nick, const std::string& console_id_has
571 if (net > 0 && event.type == ENET_EVENT_TYPE_CONNECT) { 575 if (net > 0 && event.type == ENET_EVENT_TYPE_CONNECT) {
572 room_member_impl->nickname = nick; 576 room_member_impl->nickname = nick;
573 room_member_impl->StartLoop(); 577 room_member_impl->StartLoop();
574 room_member_impl->SendJoinRequest(nick, console_id_hash, preferred_mac, password, token); 578 room_member_impl->SendJoinRequest(nick, preferred_fake_ip, password, token);
575 SendGameInfo(room_member_impl->current_game_info); 579 SendGameInfo(room_member_impl->current_game_info);
576 } else { 580 } else {
577 enet_peer_disconnect(room_member_impl->server, 0); 581 enet_peer_disconnect(room_member_impl->server, 0);
@@ -584,14 +588,22 @@ bool RoomMember::IsConnected() const {
584 return room_member_impl->IsConnected(); 588 return room_member_impl->IsConnected();
585} 589}
586 590
587void RoomMember::SendWifiPacket(const WifiPacket& wifi_packet) { 591void RoomMember::SendProxyPacket(const ProxyPacket& proxy_packet) {
588 Packet packet; 592 Packet packet;
589 packet.Write(static_cast<u8>(IdWifiPacket)); 593 packet.Write(static_cast<u8>(IdProxyPacket));
590 packet.Write(static_cast<u8>(wifi_packet.type)); 594
591 packet.Write(wifi_packet.channel); 595 packet.Write(static_cast<u8>(proxy_packet.local_endpoint.family));
592 packet.Write(wifi_packet.transmitter_address); 596 packet.Write(proxy_packet.local_endpoint.ip);
593 packet.Write(wifi_packet.destination_address); 597 packet.Write(proxy_packet.local_endpoint.portno);
594 packet.Write(wifi_packet.data); 598
599 packet.Write(static_cast<u8>(proxy_packet.remote_endpoint.family));
600 packet.Write(proxy_packet.remote_endpoint.ip);
601 packet.Write(proxy_packet.remote_endpoint.portno);
602
603 packet.Write(static_cast<u8>(proxy_packet.protocol));
604 packet.Write(proxy_packet.broadcast);
605 packet.Write(proxy_packet.data);
606
595 room_member_impl->Send(std::move(packet)); 607 room_member_impl->Send(std::move(packet));
596} 608}
597 609
@@ -611,6 +623,7 @@ void RoomMember::SendGameInfo(const GameInfo& game_info) {
611 packet.Write(static_cast<u8>(IdSetGameInfo)); 623 packet.Write(static_cast<u8>(IdSetGameInfo));
612 packet.Write(game_info.name); 624 packet.Write(game_info.name);
613 packet.Write(game_info.id); 625 packet.Write(game_info.id);
626 packet.Write(game_info.version);
614 room_member_impl->Send(std::move(packet)); 627 room_member_impl->Send(std::move(packet));
615} 628}
616 629
@@ -645,8 +658,8 @@ RoomMember::CallbackHandle<RoomMember::Error> RoomMember::BindOnError(
645 return room_member_impl->Bind(callback); 658 return room_member_impl->Bind(callback);
646} 659}
647 660
648RoomMember::CallbackHandle<WifiPacket> RoomMember::BindOnWifiPacketReceived( 661RoomMember::CallbackHandle<ProxyPacket> RoomMember::BindOnProxyPacketReceived(
649 std::function<void(const WifiPacket&)> callback) { 662 std::function<void(const ProxyPacket&)> callback) {
650 return room_member_impl->Bind(callback); 663 return room_member_impl->Bind(callback);
651} 664}
652 665
@@ -685,7 +698,7 @@ void RoomMember::Leave() {
685 room_member_impl->client = nullptr; 698 room_member_impl->client = nullptr;
686} 699}
687 700
688template void RoomMember::Unbind(CallbackHandle<WifiPacket>); 701template void RoomMember::Unbind(CallbackHandle<ProxyPacket>);
689template void RoomMember::Unbind(CallbackHandle<RoomMember::State>); 702template void RoomMember::Unbind(CallbackHandle<RoomMember::State>);
690template void RoomMember::Unbind(CallbackHandle<RoomMember::Error>); 703template void RoomMember::Unbind(CallbackHandle<RoomMember::Error>);
691template void RoomMember::Unbind(CallbackHandle<RoomInformation>); 704template void RoomMember::Unbind(CallbackHandle<RoomInformation>);
diff --git a/src/network/room_member.h b/src/network/room_member.h
index bbb7d13d4..f578f7f6a 100644
--- a/src/network/room_member.h
+++ b/src/network/room_member.h
@@ -9,6 +9,7 @@
9#include <vector> 9#include <vector>
10#include "common/announce_multiplayer_room.h" 10#include "common/announce_multiplayer_room.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "common/socket_types.h"
12#include "network/room.h" 13#include "network/room.h"
13 14
14namespace Network { 15namespace Network {
@@ -17,22 +18,12 @@ using AnnounceMultiplayerRoom::GameInfo;
17using AnnounceMultiplayerRoom::RoomInformation; 18using AnnounceMultiplayerRoom::RoomInformation;
18 19
19/// Information about the received WiFi packets. 20/// Information about the received WiFi packets.
20/// Acts as our own 802.11 header. 21struct ProxyPacket {
21struct WifiPacket { 22 SockAddrIn local_endpoint;
22 enum class PacketType : u8 { 23 SockAddrIn remote_endpoint;
23 Beacon, 24 Protocol protocol;
24 Data, 25 bool broadcast;
25 Authentication, 26 std::vector<u8> data;
26 AssociationResponse,
27 Deauthentication,
28 NodeMap
29 };
30 PacketType type; ///< The type of 802.11 frame.
31 std::vector<u8> data; ///< Raw 802.11 frame data, starting at the management frame header
32 /// for management frames.
33 MacAddress transmitter_address; ///< Mac address of the transmitter.
34 MacAddress destination_address; ///< Mac address of the receiver.
35 u8 channel; ///< WiFi channel where this frame was transmitted.
36}; 27};
37 28
38/// Represents a chat message. 29/// Represents a chat message.
@@ -72,15 +63,14 @@ public:
72 HostKicked, ///< Kicked by the host 63 HostKicked, ///< Kicked by the host
73 64
74 // Reasons why connection was rejected 65 // Reasons why connection was rejected
75 UnknownError, ///< Some error [permissions to network device missing or something] 66 UnknownError, ///< Some error [permissions to network device missing or something]
76 NameCollision, ///< Somebody is already using this name 67 NameCollision, ///< Somebody is already using this name
77 MacCollision, ///< Somebody is already using that mac-address 68 IpCollision, ///< Somebody is already using that fake-ip-address
78 ConsoleIdCollision, ///< Somebody in the room has the same Console ID 69 WrongVersion, ///< The room version is not the same as for this RoomMember
79 WrongVersion, ///< The room version is not the same as for this RoomMember 70 WrongPassword, ///< The password doesn't match the one from the Room
80 WrongPassword, ///< The password doesn't match the one from the Room 71 CouldNotConnect, ///< The room is not responding to a connection attempt
81 CouldNotConnect, ///< The room is not responding to a connection attempt 72 RoomIsFull, ///< Room is already at the maximum number of players
82 RoomIsFull, ///< Room is already at the maximum number of players 73 HostBanned, ///< The user is banned by the host
83 HostBanned, ///< The user is banned by the host
84 74
85 // Reasons why moderation request failed 75 // Reasons why moderation request failed
86 PermissionDenied, ///< The user does not have mod permissions 76 PermissionDenied, ///< The user does not have mod permissions
@@ -92,9 +82,9 @@ public:
92 std::string username; ///< The web services username of the member. Can be empty. 82 std::string username; ///< The web services username of the member. Can be empty.
93 std::string display_name; ///< The web services display name of the member. Can be empty. 83 std::string display_name; ///< The web services display name of the member. Can be empty.
94 std::string avatar_url; ///< Url to the member's avatar. Can be empty. 84 std::string avatar_url; ///< Url to the member's avatar. Can be empty.
95 GameInfo game_info; ///< Name of the game they're currently playing, or empty if they're 85 GameInfo game_info; ///< Name of the game they're currently playing, or empty if they're
96 /// not playing anything. 86 /// not playing anything.
97 MacAddress mac_address; ///< MAC address associated with this member. 87 IPv4Address fake_ip; ///< Fake Ip address associated with this member.
98 }; 88 };
99 using MemberList = std::vector<MemberInformation>; 89 using MemberList = std::vector<MemberInformation>;
100 90
@@ -135,7 +125,7 @@ public:
135 /** 125 /**
136 * Returns the MAC address of the RoomMember. 126 * Returns the MAC address of the RoomMember.
137 */ 127 */
138 const MacAddress& GetMacAddress() const; 128 const IPv4Address& GetFakeIpAddress() const;
139 129
140 /** 130 /**
141 * Returns information about the room we're currently connected to. 131 * Returns information about the room we're currently connected to.
@@ -149,19 +139,17 @@ public:
149 139
150 /** 140 /**
151 * Attempts to join a room at the specified address and port, using the specified nickname. 141 * Attempts to join a room at the specified address and port, using the specified nickname.
152 * A console ID hash is passed in to check console ID conflicts.
153 * This may fail if the username or console ID is already taken.
154 */ 142 */
155 void Join(const std::string& nickname, const std::string& console_id_hash, 143 void Join(const std::string& nickname, const char* server_addr = "127.0.0.1",
156 const char* server_addr = "127.0.0.1", u16 server_port = DefaultRoomPort, 144 u16 server_port = DefaultRoomPort, u16 client_port = 0,
157 u16 client_port = 0, const MacAddress& preferred_mac = NoPreferredMac, 145 const IPv4Address& preferred_fake_ip = NoPreferredIP,
158 const std::string& password = "", const std::string& token = ""); 146 const std::string& password = "", const std::string& token = "");
159 147
160 /** 148 /**
161 * Sends a WiFi packet to the room. 149 * Sends a Proxy packet to the room.
162 * @param packet The WiFi packet to send. 150 * @param packet The WiFi packet to send.
163 */ 151 */
164 void SendWifiPacket(const WifiPacket& packet); 152 void SendProxyPacket(const ProxyPacket& packet);
165 153
166 /** 154 /**
167 * Sends a chat message to the room. 155 * Sends a chat message to the room.
@@ -207,14 +195,14 @@ public:
207 CallbackHandle<Error> BindOnError(std::function<void(const Error&)> callback); 195 CallbackHandle<Error> BindOnError(std::function<void(const Error&)> callback);
208 196
209 /** 197 /**
210 * Binds a function to an event that will be triggered every time a WifiPacket is received. 198 * Binds a function to an event that will be triggered every time a ProxyPacket is received.
211 * The function wil be called everytime the event is triggered. 199 * The function wil be called everytime the event is triggered.
212 * The callback function must not bind or unbind a function. Doing so will cause a deadlock 200 * The callback function must not bind or unbind a function. Doing so will cause a deadlock
213 * @param callback The function to call 201 * @param callback The function to call
214 * @return A handle used for removing the function from the registered list 202 * @return A handle used for removing the function from the registered list
215 */ 203 */
216 CallbackHandle<WifiPacket> BindOnWifiPacketReceived( 204 CallbackHandle<ProxyPacket> BindOnProxyPacketReceived(
217 std::function<void(const WifiPacket&)> callback); 205 std::function<void(const ProxyPacket&)> callback);
218 206
219 /** 207 /**
220 * Binds a function to an event that will be triggered every time the RoomInformation changes. 208 * Binds a function to an event that will be triggered every time the RoomInformation changes.
@@ -292,10 +280,8 @@ inline const char* GetErrorStr(const RoomMember::Error& e) {
292 return "UnknownError"; 280 return "UnknownError";
293 case RoomMember::Error::NameCollision: 281 case RoomMember::Error::NameCollision:
294 return "NameCollision"; 282 return "NameCollision";
295 case RoomMember::Error::MacCollision: 283 case RoomMember::Error::IpCollision:
296 return "MaxCollision"; 284 return "IpCollision";
297 case RoomMember::Error::ConsoleIdCollision:
298 return "ConsoleIdCollision";
299 case RoomMember::Error::WrongVersion: 285 case RoomMember::Error::WrongVersion:
300 return "WrongVersion"; 286 return "WrongVersion";
301 case RoomMember::Error::WrongPassword: 287 case RoomMember::Error::WrongPassword:
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp
index a97b143e4..e67e80fac 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp
@@ -67,6 +67,7 @@ std::string_view TextureType(IR::TextureInstInfo info) {
67 case TextureType::ColorArray1D: 67 case TextureType::ColorArray1D:
68 return "SHADOWARRAY1D"; 68 return "SHADOWARRAY1D";
69 case TextureType::Color2D: 69 case TextureType::Color2D:
70 case TextureType::Color2DRect:
70 return "SHADOW2D"; 71 return "SHADOW2D";
71 case TextureType::ColorArray2D: 72 case TextureType::ColorArray2D:
72 return "SHADOWARRAY2D"; 73 return "SHADOWARRAY2D";
@@ -86,6 +87,7 @@ std::string_view TextureType(IR::TextureInstInfo info) {
86 case TextureType::ColorArray1D: 87 case TextureType::ColorArray1D:
87 return "ARRAY1D"; 88 return "ARRAY1D";
88 case TextureType::Color2D: 89 case TextureType::Color2D:
90 case TextureType::Color2DRect:
89 return "2D"; 91 return "2D";
90 case TextureType::ColorArray2D: 92 case TextureType::ColorArray2D:
91 return "ARRAY2D"; 93 return "ARRAY2D";
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
index 6af7e3fe6..cecdbb9d6 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
@@ -466,6 +466,7 @@ void EmitImageQueryDimensions(EmitContext& ctx, IR::Inst& inst, const IR::Value&
466 case TextureType::ColorArray1D: 466 case TextureType::ColorArray1D:
467 case TextureType::Color2D: 467 case TextureType::Color2D:
468 case TextureType::ColorCube: 468 case TextureType::ColorCube:
469 case TextureType::Color2DRect:
469 return ctx.AddU32x4( 470 return ctx.AddU32x4(
470 "{}=uvec4(uvec2(textureSize({},int({}))),0u,uint(textureQueryLevels({})));", inst, 471 "{}=uvec4(uvec2(textureSize({},int({}))),0u,uint(textureQueryLevels({})));", inst,
471 texture, lod, texture); 472 texture, lod, texture);
diff --git a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
index 221b06328..c767a9dc3 100644
--- a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
+++ b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
@@ -86,6 +86,7 @@ std::string_view SamplerType(TextureType type, bool is_depth) {
86 case TextureType::ColorArray1D: 86 case TextureType::ColorArray1D:
87 return "sampler1DArray"; 87 return "sampler1DArray";
88 case TextureType::Color2D: 88 case TextureType::Color2D:
89 case TextureType::Color2DRect:
89 return "sampler2D"; 90 return "sampler2D";
90 case TextureType::ColorArray2D: 91 case TextureType::ColorArray2D:
91 return "sampler2DArray"; 92 return "sampler2DArray";
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
index d8d86c91a..fb5799c42 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
@@ -453,6 +453,7 @@ Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& i
453 case TextureType::ColorArray1D: 453 case TextureType::ColorArray1D:
454 case TextureType::Color2D: 454 case TextureType::Color2D:
455 case TextureType::ColorCube: 455 case TextureType::ColorCube:
456 case TextureType::Color2DRect:
456 return ctx.OpCompositeConstruct(ctx.U32[4], ctx.OpImageQuerySizeLod(ctx.U32[2], image, lod), 457 return ctx.OpCompositeConstruct(ctx.U32[4], ctx.OpImageQuerySizeLod(ctx.U32[2], image, lod),
457 zero, mips()); 458 zero, mips());
458 case TextureType::ColorArray2D: 459 case TextureType::ColorArray2D:
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index d4a49ea99..aecc4c612 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -41,6 +41,7 @@ Id ImageType(EmitContext& ctx, const TextureDescriptor& desc) {
41 case TextureType::ColorArray1D: 41 case TextureType::ColorArray1D:
42 return ctx.TypeImage(type, spv::Dim::Dim1D, depth, true, false, 1, format); 42 return ctx.TypeImage(type, spv::Dim::Dim1D, depth, true, false, 1, format);
43 case TextureType::Color2D: 43 case TextureType::Color2D:
44 case TextureType::Color2DRect:
44 return ctx.TypeImage(type, spv::Dim::Dim2D, depth, false, false, 1, format); 45 return ctx.TypeImage(type, spv::Dim::Dim2D, depth, false, false, 1, format);
45 case TextureType::ColorArray2D: 46 case TextureType::ColorArray2D:
46 return ctx.TypeImage(type, spv::Dim::Dim2D, depth, true, false, 1, format); 47 return ctx.TypeImage(type, spv::Dim::Dim2D, depth, true, false, 1, format);
@@ -1306,7 +1307,7 @@ void EmitContext::DefineInputs(const IR::Program& program) {
1306 subgroup_mask_gt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupGtMaskKHR); 1307 subgroup_mask_gt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupGtMaskKHR);
1307 subgroup_mask_ge = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupGeMaskKHR); 1308 subgroup_mask_ge = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupGeMaskKHR);
1308 } 1309 }
1309 if (info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles || 1310 if (info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles ||
1310 (profile.warp_size_potentially_larger_than_guest && 1311 (profile.warp_size_potentially_larger_than_guest &&
1311 (info.uses_subgroup_vote || info.uses_subgroup_mask))) { 1312 (info.uses_subgroup_vote || info.uses_subgroup_mask))) {
1312 subgroup_local_invocation_id = 1313 subgroup_local_invocation_id =
@@ -1411,7 +1412,8 @@ void EmitContext::DefineInputs(const IR::Program& program) {
1411void EmitContext::DefineOutputs(const IR::Program& program) { 1412void EmitContext::DefineOutputs(const IR::Program& program) {
1412 const Info& info{program.info}; 1413 const Info& info{program.info};
1413 const std::optional<u32> invocations{program.invocations}; 1414 const std::optional<u32> invocations{program.invocations};
1414 if (info.stores.AnyComponent(IR::Attribute::PositionX) || stage == Stage::VertexB) { 1415 if (runtime_info.convert_depth_mode || info.stores.AnyComponent(IR::Attribute::PositionX) ||
1416 stage == Stage::VertexB) {
1415 output_position = DefineOutput(*this, F32[4], invocations, spv::BuiltIn::Position); 1417 output_position = DefineOutput(*this, F32[4], invocations, spv::BuiltIn::Position);
1416 } 1418 }
1417 if (info.stores[IR::Attribute::PointSize] || runtime_info.fixed_state_point_size) { 1419 if (info.stores[IR::Attribute::PointSize] || runtime_info.fixed_state_point_size) {
diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.cpp b/src/shader_recompiler/frontend/ir/ir_emitter.cpp
index d2b658bca..11086ed8c 100644
--- a/src/shader_recompiler/frontend/ir/ir_emitter.cpp
+++ b/src/shader_recompiler/frontend/ir/ir_emitter.cpp
@@ -1832,6 +1832,11 @@ Value IREmitter::ImageQueryDimension(const Value& handle, const IR::U32& lod) {
1832 return Inst(op, handle, lod); 1832 return Inst(op, handle, lod);
1833} 1833}
1834 1834
1835Value IREmitter::ImageQueryDimension(const Value& handle, const IR::U32& lod,
1836 TextureInstInfo info) {
1837 return Inst(Opcode::ImageQueryDimensions, Flags{info}, handle, lod);
1838}
1839
1835Value IREmitter::ImageQueryLod(const Value& handle, const Value& coords, TextureInstInfo info) { 1840Value IREmitter::ImageQueryLod(const Value& handle, const Value& coords, TextureInstInfo info) {
1836 const Opcode op{handle.IsImmediate() ? Opcode::BoundImageQueryLod 1841 const Opcode op{handle.IsImmediate() ? Opcode::BoundImageQueryLod
1837 : Opcode::BindlessImageQueryLod}; 1842 : Opcode::BindlessImageQueryLod};
diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.h b/src/shader_recompiler/frontend/ir/ir_emitter.h
index c29bda558..25839a371 100644
--- a/src/shader_recompiler/frontend/ir/ir_emitter.h
+++ b/src/shader_recompiler/frontend/ir/ir_emitter.h
@@ -315,6 +315,8 @@ public:
315 const F32& dref, const F32& lod, 315 const F32& dref, const F32& lod,
316 const Value& offset, TextureInstInfo info); 316 const Value& offset, TextureInstInfo info);
317 [[nodiscard]] Value ImageQueryDimension(const Value& handle, const IR::U32& lod); 317 [[nodiscard]] Value ImageQueryDimension(const Value& handle, const IR::U32& lod);
318 [[nodiscard]] Value ImageQueryDimension(const Value& handle, const IR::U32& lod,
319 TextureInstInfo info);
318 320
319 [[nodiscard]] Value ImageQueryLod(const Value& handle, const Value& coords, 321 [[nodiscard]] Value ImageQueryLod(const Value& handle, const Value& coords,
320 TextureInstInfo info); 322 TextureInstInfo info);
diff --git a/src/shader_recompiler/ir_opt/rescaling_pass.cpp b/src/shader_recompiler/ir_opt/rescaling_pass.cpp
index 0d5f2e4d8..9198fa5f2 100644
--- a/src/shader_recompiler/ir_opt/rescaling_pass.cpp
+++ b/src/shader_recompiler/ir_opt/rescaling_pass.cpp
@@ -16,6 +16,7 @@ namespace {
16 switch (type) { 16 switch (type) {
17 case TextureType::Color2D: 17 case TextureType::Color2D:
18 case TextureType::ColorArray2D: 18 case TextureType::ColorArray2D:
19 case TextureType::Color2DRect:
19 return true; 20 return true;
20 case TextureType::Color1D: 21 case TextureType::Color1D:
21 case TextureType::ColorArray1D: 22 case TextureType::ColorArray1D:
@@ -132,7 +133,8 @@ void PatchImageQueryDimensions(IR::Block& block, IR::Inst& inst) {
132 const IR::U1 is_scaled{ir.IsTextureScaled(ir.Imm32(info.descriptor_index))}; 133 const IR::U1 is_scaled{ir.IsTextureScaled(ir.Imm32(info.descriptor_index))};
133 switch (info.type) { 134 switch (info.type) {
134 case TextureType::Color2D: 135 case TextureType::Color2D:
135 case TextureType::ColorArray2D: { 136 case TextureType::ColorArray2D:
137 case TextureType::Color2DRect: {
136 const IR::Value new_inst{&*block.PrependNewInst(it, inst)}; 138 const IR::Value new_inst{&*block.PrependNewInst(it, inst)};
137 const IR::U32 width{DownScale(ir, is_scaled, IR::U32{ir.CompositeExtract(new_inst, 0)})}; 139 const IR::U32 width{DownScale(ir, is_scaled, IR::U32{ir.CompositeExtract(new_inst, 0)})};
138 const IR::U32 height{DownScale(ir, is_scaled, IR::U32{ir.CompositeExtract(new_inst, 1)})}; 140 const IR::U32 height{DownScale(ir, is_scaled, IR::U32{ir.CompositeExtract(new_inst, 1)})};
@@ -163,6 +165,7 @@ void ScaleIntegerComposite(IR::IREmitter& ir, IR::Inst& inst, const IR::U1& is_s
163 const IR::U32 y{Scale(ir, is_scaled, IR::U32{ir.CompositeExtract(composite, 1)})}; 165 const IR::U32 y{Scale(ir, is_scaled, IR::U32{ir.CompositeExtract(composite, 1)})};
164 switch (info.type) { 166 switch (info.type) {
165 case TextureType::Color2D: 167 case TextureType::Color2D:
168 case TextureType::Color2DRect:
166 inst.SetArg(index, ir.CompositeConstruct(x, y)); 169 inst.SetArg(index, ir.CompositeConstruct(x, y));
167 break; 170 break;
168 case TextureType::ColorArray2D: { 171 case TextureType::ColorArray2D: {
@@ -193,6 +196,7 @@ void ScaleIntegerOffsetComposite(IR::IREmitter& ir, IR::Inst& inst, const IR::U1
193 switch (info.type) { 196 switch (info.type) {
194 case TextureType::ColorArray2D: 197 case TextureType::ColorArray2D:
195 case TextureType::Color2D: 198 case TextureType::Color2D:
199 case TextureType::Color2DRect:
196 inst.SetArg(index, ir.CompositeConstruct(x, y)); 200 inst.SetArg(index, ir.CompositeConstruct(x, y));
197 break; 201 break;
198 case TextureType::Color1D: 202 case TextureType::Color1D:
@@ -216,6 +220,7 @@ void SubScaleCoord(IR::IREmitter& ir, IR::Inst& inst, const IR::U1& is_scaled) {
216 const IR::U32 scaled_y{SubScale(ir, is_scaled, coord_y, IR::Attribute::PositionY)}; 220 const IR::U32 scaled_y{SubScale(ir, is_scaled, coord_y, IR::Attribute::PositionY)};
217 switch (info.type) { 221 switch (info.type) {
218 case TextureType::Color2D: 222 case TextureType::Color2D:
223 case TextureType::Color2DRect:
219 inst.SetArg(1, ir.CompositeConstruct(scaled_x, scaled_y)); 224 inst.SetArg(1, ir.CompositeConstruct(scaled_x, scaled_y));
220 break; 225 break;
221 case TextureType::ColorArray2D: { 226 case TextureType::ColorArray2D: {
diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp
index ca3e306e8..597112ba4 100644
--- a/src/shader_recompiler/ir_opt/texture_pass.cpp
+++ b/src/shader_recompiler/ir_opt/texture_pass.cpp
@@ -362,6 +362,21 @@ private:
362 TextureDescriptors& texture_descriptors; 362 TextureDescriptors& texture_descriptors;
363 ImageDescriptors& image_descriptors; 363 ImageDescriptors& image_descriptors;
364}; 364};
365
366void PatchImageSampleImplicitLod(IR::Block& block, IR::Inst& inst) {
367 IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
368 const auto info{inst.Flags<IR::TextureInstInfo>()};
369 const IR::Value coord(inst.Arg(1));
370 const IR::Value handle(ir.Imm32(0));
371 const IR::U32 lod{ir.Imm32(0)};
372 const IR::Value texture_size = ir.ImageQueryDimension(handle, lod, info);
373 inst.SetArg(
374 1, ir.CompositeConstruct(
375 ir.FPMul(IR::F32(ir.CompositeExtract(coord, 0)),
376 ir.FPRecip(ir.ConvertUToF(32, 32, ir.CompositeExtract(texture_size, 0)))),
377 ir.FPMul(IR::F32(ir.CompositeExtract(coord, 1)),
378 ir.FPRecip(ir.ConvertUToF(32, 32, ir.CompositeExtract(texture_size, 1))))));
379}
365} // Anonymous namespace 380} // Anonymous namespace
366 381
367void TexturePass(Environment& env, IR::Program& program) { 382void TexturePass(Environment& env, IR::Program& program) {
@@ -399,6 +414,14 @@ void TexturePass(Environment& env, IR::Program& program) {
399 flags.type.Assign(ReadTextureType(env, cbuf)); 414 flags.type.Assign(ReadTextureType(env, cbuf));
400 inst->SetFlags(flags); 415 inst->SetFlags(flags);
401 break; 416 break;
417 case IR::Opcode::ImageSampleImplicitLod:
418 if (flags.type != TextureType::Color2D) {
419 break;
420 }
421 if (ReadTextureType(env, cbuf) == TextureType::Color2DRect) {
422 PatchImageSampleImplicitLod(*texture_inst.block, *texture_inst.inst);
423 }
424 break;
402 case IR::Opcode::ImageFetch: 425 case IR::Opcode::ImageFetch:
403 if (flags.type != TextureType::Color1D) { 426 if (flags.type != TextureType::Color1D) {
404 break; 427 break;
diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h
index fd2ef5336..f5690805c 100644
--- a/src/shader_recompiler/shader_info.h
+++ b/src/shader_recompiler/shader_info.h
@@ -24,8 +24,9 @@ enum class TextureType : u32 {
24 ColorCube, 24 ColorCube,
25 ColorArrayCube, 25 ColorArrayCube,
26 Buffer, 26 Buffer,
27 Color2DRect,
27}; 28};
28constexpr u32 NUM_TEXTURE_TYPES = 8; 29constexpr u32 NUM_TEXTURE_TYPES = 9;
29 30
30enum class ImageFormat : u32 { 31enum class ImageFormat : u32 {
31 Typeless, 32 Typeless,
diff --git a/src/tests/video_core/buffer_base.cpp b/src/tests/video_core/buffer_base.cpp
index a1be8dcf1..71121e42a 100644
--- a/src/tests/video_core/buffer_base.cpp
+++ b/src/tests/video_core/buffer_base.cpp
@@ -22,8 +22,9 @@ constexpr VAddr c = 0x1328914000;
22class RasterizerInterface { 22class RasterizerInterface {
23public: 23public:
24 void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) { 24 void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
25 const u64 page_start{addr >> Core::Memory::PAGE_BITS}; 25 const u64 page_start{addr >> Core::Memory::YUZU_PAGEBITS};
26 const u64 page_end{(addr + size + Core::Memory::PAGE_SIZE - 1) >> Core::Memory::PAGE_BITS}; 26 const u64 page_end{(addr + size + Core::Memory::YUZU_PAGESIZE - 1) >>
27 Core::Memory::YUZU_PAGEBITS};
27 for (u64 page = page_start; page < page_end; ++page) { 28 for (u64 page = page_start; page < page_end; ++page) {
28 int& value = page_table[page]; 29 int& value = page_table[page];
29 value += delta; 30 value += delta;
@@ -37,7 +38,7 @@ public:
37 } 38 }
38 39
39 [[nodiscard]] int Count(VAddr addr) const noexcept { 40 [[nodiscard]] int Count(VAddr addr) const noexcept {
40 const auto it = page_table.find(addr >> Core::Memory::PAGE_BITS); 41 const auto it = page_table.find(addr >> Core::Memory::YUZU_PAGEBITS);
41 return it == page_table.end() ? 0 : it->second; 42 return it == page_table.end() ? 0 : it->second;
42 } 43 }
43 44
diff --git a/src/video_core/buffer_cache/buffer_base.h b/src/video_core/buffer_cache/buffer_base.h
index 3e20608ca..f9a6472cf 100644
--- a/src/video_core/buffer_cache/buffer_base.h
+++ b/src/video_core/buffer_cache/buffer_base.h
@@ -12,6 +12,7 @@
12#include "common/common_funcs.h" 12#include "common/common_funcs.h"
13#include "common/common_types.h" 13#include "common/common_types.h"
14#include "common/div_ceil.h" 14#include "common/div_ceil.h"
15#include "common/settings.h"
15#include "core/memory.h" 16#include "core/memory.h"
16 17
17namespace VideoCommon { 18namespace VideoCommon {
@@ -36,7 +37,7 @@ struct NullBufferParams {};
36template <class RasterizerInterface> 37template <class RasterizerInterface>
37class BufferBase { 38class BufferBase {
38 static constexpr u64 PAGES_PER_WORD = 64; 39 static constexpr u64 PAGES_PER_WORD = 64;
39 static constexpr u64 BYTES_PER_PAGE = Core::Memory::PAGE_SIZE; 40 static constexpr u64 BYTES_PER_PAGE = Core::Memory::YUZU_PAGESIZE;
40 static constexpr u64 BYTES_PER_WORD = PAGES_PER_WORD * BYTES_PER_PAGE; 41 static constexpr u64 BYTES_PER_WORD = PAGES_PER_WORD * BYTES_PER_PAGE;
41 42
42 /// Vector tracking modified pages tightly packed with small vector optimization 43 /// Vector tracking modified pages tightly packed with small vector optimization
@@ -219,7 +220,9 @@ public:
219 NotifyRasterizer<false>(word_index, untracked_words[word_index], cached_bits); 220 NotifyRasterizer<false>(word_index, untracked_words[word_index], cached_bits);
220 untracked_words[word_index] |= cached_bits; 221 untracked_words[word_index] |= cached_bits;
221 cpu_words[word_index] |= cached_bits; 222 cpu_words[word_index] |= cached_bits;
222 cached_words[word_index] = 0; 223 if (!Settings::values.use_pessimistic_flushes) {
224 cached_words[word_index] = 0;
225 }
223 } 226 }
224 } 227 }
225 228
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index b74ad7900..f015dae56 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -60,8 +60,8 @@ class BufferCache {
60 60
61 // Page size for caching purposes. 61 // Page size for caching purposes.
62 // This is unrelated to the CPU page size and it can be changed as it seems optimal. 62 // This is unrelated to the CPU page size and it can be changed as it seems optimal.
63 static constexpr u32 PAGE_BITS = 16; 63 static constexpr u32 YUZU_PAGEBITS = 16;
64 static constexpr u64 PAGE_SIZE = u64{1} << PAGE_BITS; 64 static constexpr u64 YUZU_PAGESIZE = u64{1} << YUZU_PAGEBITS;
65 65
66 static constexpr bool IS_OPENGL = P::IS_OPENGL; 66 static constexpr bool IS_OPENGL = P::IS_OPENGL;
67 static constexpr bool HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS = 67 static constexpr bool HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS =
@@ -216,8 +216,8 @@ private:
216 216
217 template <typename Func> 217 template <typename Func>
218 void ForEachBufferInRange(VAddr cpu_addr, u64 size, Func&& func) { 218 void ForEachBufferInRange(VAddr cpu_addr, u64 size, Func&& func) {
219 const u64 page_end = Common::DivCeil(cpu_addr + size, PAGE_SIZE); 219 const u64 page_end = Common::DivCeil(cpu_addr + size, YUZU_PAGESIZE);
220 for (u64 page = cpu_addr >> PAGE_BITS; page < page_end;) { 220 for (u64 page = cpu_addr >> YUZU_PAGEBITS; page < page_end;) {
221 const BufferId buffer_id = page_table[page]; 221 const BufferId buffer_id = page_table[page];
222 if (!buffer_id) { 222 if (!buffer_id) {
223 ++page; 223 ++page;
@@ -227,7 +227,7 @@ private:
227 func(buffer_id, buffer); 227 func(buffer_id, buffer);
228 228
229 const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes(); 229 const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes();
230 page = Common::DivCeil(end_addr, PAGE_SIZE); 230 page = Common::DivCeil(end_addr, YUZU_PAGESIZE);
231 } 231 }
232 } 232 }
233 233
@@ -262,8 +262,8 @@ private:
262 } 262 }
263 263
264 static bool IsRangeGranular(VAddr cpu_addr, size_t size) { 264 static bool IsRangeGranular(VAddr cpu_addr, size_t size) {
265 return (cpu_addr & ~Core::Memory::PAGE_MASK) == 265 return (cpu_addr & ~Core::Memory::YUZU_PAGEMASK) ==
266 ((cpu_addr + size) & ~Core::Memory::PAGE_MASK); 266 ((cpu_addr + size) & ~Core::Memory::YUZU_PAGEMASK);
267 } 267 }
268 268
269 void RunGarbageCollector(); 269 void RunGarbageCollector();
@@ -439,7 +439,7 @@ private:
439 u64 minimum_memory = 0; 439 u64 minimum_memory = 0;
440 u64 critical_memory = 0; 440 u64 critical_memory = 0;
441 441
442 std::array<BufferId, ((1ULL << 39) >> PAGE_BITS)> page_table; 442 std::array<BufferId, ((1ULL << 39) >> YUZU_PAGEBITS)> page_table;
443}; 443};
444 444
445template <class P> 445template <class P>
@@ -926,8 +926,8 @@ void BufferCache<P>::PopAsyncFlushes() {}
926 926
927template <class P> 927template <class P>
928bool BufferCache<P>::IsRegionGpuModified(VAddr addr, size_t size) { 928bool BufferCache<P>::IsRegionGpuModified(VAddr addr, size_t size) {
929 const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE); 929 const u64 page_end = Common::DivCeil(addr + size, YUZU_PAGESIZE);
930 for (u64 page = addr >> PAGE_BITS; page < page_end;) { 930 for (u64 page = addr >> YUZU_PAGEBITS; page < page_end;) {
931 const BufferId image_id = page_table[page]; 931 const BufferId image_id = page_table[page];
932 if (!image_id) { 932 if (!image_id) {
933 ++page; 933 ++page;
@@ -938,7 +938,7 @@ bool BufferCache<P>::IsRegionGpuModified(VAddr addr, size_t size) {
938 return true; 938 return true;
939 } 939 }
940 const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes(); 940 const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes();
941 page = Common::DivCeil(end_addr, PAGE_SIZE); 941 page = Common::DivCeil(end_addr, YUZU_PAGESIZE);
942 } 942 }
943 return false; 943 return false;
944} 944}
@@ -946,8 +946,8 @@ bool BufferCache<P>::IsRegionGpuModified(VAddr addr, size_t size) {
946template <class P> 946template <class P>
947bool BufferCache<P>::IsRegionRegistered(VAddr addr, size_t size) { 947bool BufferCache<P>::IsRegionRegistered(VAddr addr, size_t size) {
948 const VAddr end_addr = addr + size; 948 const VAddr end_addr = addr + size;
949 const u64 page_end = Common::DivCeil(end_addr, PAGE_SIZE); 949 const u64 page_end = Common::DivCeil(end_addr, YUZU_PAGESIZE);
950 for (u64 page = addr >> PAGE_BITS; page < page_end;) { 950 for (u64 page = addr >> YUZU_PAGEBITS; page < page_end;) {
951 const BufferId buffer_id = page_table[page]; 951 const BufferId buffer_id = page_table[page];
952 if (!buffer_id) { 952 if (!buffer_id) {
953 ++page; 953 ++page;
@@ -959,15 +959,15 @@ bool BufferCache<P>::IsRegionRegistered(VAddr addr, size_t size) {
959 if (buf_start_addr < end_addr && addr < buf_end_addr) { 959 if (buf_start_addr < end_addr && addr < buf_end_addr) {
960 return true; 960 return true;
961 } 961 }
962 page = Common::DivCeil(end_addr, PAGE_SIZE); 962 page = Common::DivCeil(end_addr, YUZU_PAGESIZE);
963 } 963 }
964 return false; 964 return false;
965} 965}
966 966
967template <class P> 967template <class P>
968bool BufferCache<P>::IsRegionCpuModified(VAddr addr, size_t size) { 968bool BufferCache<P>::IsRegionCpuModified(VAddr addr, size_t size) {
969 const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE); 969 const u64 page_end = Common::DivCeil(addr + size, YUZU_PAGESIZE);
970 for (u64 page = addr >> PAGE_BITS; page < page_end;) { 970 for (u64 page = addr >> YUZU_PAGEBITS; page < page_end;) {
971 const BufferId image_id = page_table[page]; 971 const BufferId image_id = page_table[page];
972 if (!image_id) { 972 if (!image_id) {
973 ++page; 973 ++page;
@@ -978,7 +978,7 @@ bool BufferCache<P>::IsRegionCpuModified(VAddr addr, size_t size) {
978 return true; 978 return true;
979 } 979 }
980 const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes(); 980 const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes();
981 page = Common::DivCeil(end_addr, PAGE_SIZE); 981 page = Common::DivCeil(end_addr, YUZU_PAGESIZE);
982 } 982 }
983 return false; 983 return false;
984} 984}
@@ -1472,7 +1472,7 @@ BufferId BufferCache<P>::FindBuffer(VAddr cpu_addr, u32 size) {
1472 if (cpu_addr == 0) { 1472 if (cpu_addr == 0) {
1473 return NULL_BUFFER_ID; 1473 return NULL_BUFFER_ID;
1474 } 1474 }
1475 const u64 page = cpu_addr >> PAGE_BITS; 1475 const u64 page = cpu_addr >> YUZU_PAGEBITS;
1476 const BufferId buffer_id = page_table[page]; 1476 const BufferId buffer_id = page_table[page];
1477 if (!buffer_id) { 1477 if (!buffer_id) {
1478 return CreateBuffer(cpu_addr, size); 1478 return CreateBuffer(cpu_addr, size);
@@ -1493,8 +1493,9 @@ typename BufferCache<P>::OverlapResult BufferCache<P>::ResolveOverlaps(VAddr cpu
1493 VAddr end = cpu_addr + wanted_size; 1493 VAddr end = cpu_addr + wanted_size;
1494 int stream_score = 0; 1494 int stream_score = 0;
1495 bool has_stream_leap = false; 1495 bool has_stream_leap = false;
1496 for (; cpu_addr >> PAGE_BITS < Common::DivCeil(end, PAGE_SIZE); cpu_addr += PAGE_SIZE) { 1496 for (; cpu_addr >> YUZU_PAGEBITS < Common::DivCeil(end, YUZU_PAGESIZE);
1497 const BufferId overlap_id = page_table[cpu_addr >> PAGE_BITS]; 1497 cpu_addr += YUZU_PAGESIZE) {
1498 const BufferId overlap_id = page_table[cpu_addr >> YUZU_PAGEBITS];
1498 if (!overlap_id) { 1499 if (!overlap_id) {
1499 continue; 1500 continue;
1500 } 1501 }
@@ -1520,11 +1521,11 @@ typename BufferCache<P>::OverlapResult BufferCache<P>::ResolveOverlaps(VAddr cpu
1520 // as a stream buffer. Increase the size to skip constantly recreating buffers. 1521 // as a stream buffer. Increase the size to skip constantly recreating buffers.
1521 has_stream_leap = true; 1522 has_stream_leap = true;
1522 if (expands_right) { 1523 if (expands_right) {
1523 begin -= PAGE_SIZE * 256; 1524 begin -= YUZU_PAGESIZE * 256;
1524 cpu_addr = begin; 1525 cpu_addr = begin;
1525 } 1526 }
1526 if (expands_left) { 1527 if (expands_left) {
1527 end += PAGE_SIZE * 256; 1528 end += YUZU_PAGESIZE * 256;
1528 } 1529 }
1529 } 1530 }
1530 } 1531 }
@@ -1598,8 +1599,8 @@ void BufferCache<P>::ChangeRegister(BufferId buffer_id) {
1598 } 1599 }
1599 const VAddr cpu_addr_begin = buffer.CpuAddr(); 1600 const VAddr cpu_addr_begin = buffer.CpuAddr();
1600 const VAddr cpu_addr_end = cpu_addr_begin + size; 1601 const VAddr cpu_addr_end = cpu_addr_begin + size;
1601 const u64 page_begin = cpu_addr_begin / PAGE_SIZE; 1602 const u64 page_begin = cpu_addr_begin / YUZU_PAGESIZE;
1602 const u64 page_end = Common::DivCeil(cpu_addr_end, PAGE_SIZE); 1603 const u64 page_end = Common::DivCeil(cpu_addr_end, YUZU_PAGESIZE);
1603 for (u64 page = page_begin; page != page_end; ++page) { 1604 for (u64 page = page_begin; page != page_end; ++page) {
1604 if constexpr (insert) { 1605 if constexpr (insert) {
1605 page_table[page] = buffer_id; 1606 page_table[page] = buffer_id;
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
index d373be0ba..bf9eb735d 100644
--- a/src/video_core/memory_manager.cpp
+++ b/src/video_core/memory_manager.cpp
@@ -369,8 +369,8 @@ bool MemoryManager::IsGranularRange(GPUVAddr gpu_addr, std::size_t size) const {
369 if (!cpu_addr) { 369 if (!cpu_addr) {
370 return false; 370 return false;
371 } 371 }
372 const std::size_t page{(*cpu_addr & Core::Memory::PAGE_MASK) + size}; 372 const std::size_t page{(*cpu_addr & Core::Memory::YUZU_PAGEMASK) + size};
373 return page <= Core::Memory::PAGE_SIZE; 373 return page <= Core::Memory::YUZU_PAGESIZE;
374} 374}
375 375
376bool MemoryManager::IsContinousRange(GPUVAddr gpu_addr, std::size_t size) const { 376bool MemoryManager::IsContinousRange(GPUVAddr gpu_addr, std::size_t size) const {
diff --git a/src/video_core/query_cache.h b/src/video_core/query_cache.h
index fcce87acb..889b606b3 100644
--- a/src/video_core/query_cache.h
+++ b/src/video_core/query_cache.h
@@ -214,8 +214,8 @@ private:
214 return cache_begin < addr_end && addr_begin < cache_end; 214 return cache_begin < addr_end && addr_begin < cache_end;
215 }; 215 };
216 216
217 const u64 page_end = addr_end >> PAGE_BITS; 217 const u64 page_end = addr_end >> YUZU_PAGEBITS;
218 for (u64 page = addr_begin >> PAGE_BITS; page <= page_end; ++page) { 218 for (u64 page = addr_begin >> YUZU_PAGEBITS; page <= page_end; ++page) {
219 const auto& it = cached_queries.find(page); 219 const auto& it = cached_queries.find(page);
220 if (it == std::end(cached_queries)) { 220 if (it == std::end(cached_queries)) {
221 continue; 221 continue;
@@ -235,14 +235,14 @@ private:
235 /// Registers the passed parameters as cached and returns a pointer to the stored cached query. 235 /// Registers the passed parameters as cached and returns a pointer to the stored cached query.
236 CachedQuery* Register(VideoCore::QueryType type, VAddr cpu_addr, u8* host_ptr, bool timestamp) { 236 CachedQuery* Register(VideoCore::QueryType type, VAddr cpu_addr, u8* host_ptr, bool timestamp) {
237 rasterizer.UpdatePagesCachedCount(cpu_addr, CachedQuery::SizeInBytes(timestamp), 1); 237 rasterizer.UpdatePagesCachedCount(cpu_addr, CachedQuery::SizeInBytes(timestamp), 1);
238 const u64 page = static_cast<u64>(cpu_addr) >> PAGE_BITS; 238 const u64 page = static_cast<u64>(cpu_addr) >> YUZU_PAGEBITS;
239 return &cached_queries[page].emplace_back(static_cast<QueryCache&>(*this), type, cpu_addr, 239 return &cached_queries[page].emplace_back(static_cast<QueryCache&>(*this), type, cpu_addr,
240 host_ptr); 240 host_ptr);
241 } 241 }
242 242
243 /// Tries to a get a cached query. Returns nullptr on failure. 243 /// Tries to a get a cached query. Returns nullptr on failure.
244 CachedQuery* TryGet(VAddr addr) { 244 CachedQuery* TryGet(VAddr addr) {
245 const u64 page = static_cast<u64>(addr) >> PAGE_BITS; 245 const u64 page = static_cast<u64>(addr) >> YUZU_PAGEBITS;
246 const auto it = cached_queries.find(page); 246 const auto it = cached_queries.find(page);
247 if (it == std::end(cached_queries)) { 247 if (it == std::end(cached_queries)) {
248 return nullptr; 248 return nullptr;
@@ -260,8 +260,8 @@ private:
260 uncommitted_flushes->push_back(addr); 260 uncommitted_flushes->push_back(addr);
261 } 261 }
262 262
263 static constexpr std::uintptr_t PAGE_SIZE = 4096; 263 static constexpr std::uintptr_t YUZU_PAGESIZE = 4096;
264 static constexpr unsigned PAGE_BITS = 12; 264 static constexpr unsigned YUZU_PAGEBITS = 12;
265 265
266 VideoCore::RasterizerInterface& rasterizer; 266 VideoCore::RasterizerInterface& rasterizer;
267 Tegra::Engines::Maxwell3D& maxwell3d; 267 Tegra::Engines::Maxwell3D& maxwell3d;
diff --git a/src/video_core/rasterizer_accelerated.cpp b/src/video_core/rasterizer_accelerated.cpp
index 87a29e144..4a197d65d 100644
--- a/src/video_core/rasterizer_accelerated.cpp
+++ b/src/video_core/rasterizer_accelerated.cpp
@@ -24,8 +24,8 @@ void RasterizerAccelerated::UpdatePagesCachedCount(VAddr addr, u64 size, int del
24 u64 cache_bytes = 0; 24 u64 cache_bytes = 0;
25 25
26 std::atomic_thread_fence(std::memory_order_acquire); 26 std::atomic_thread_fence(std::memory_order_acquire);
27 const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE); 27 const u64 page_end = Common::DivCeil(addr + size, YUZU_PAGESIZE);
28 for (u64 page = addr >> PAGE_BITS; page != page_end; ++page) { 28 for (u64 page = addr >> YUZU_PAGEBITS; page != page_end; ++page) {
29 std::atomic_uint16_t& count = cached_pages.at(page >> 2).Count(page); 29 std::atomic_uint16_t& count = cached_pages.at(page >> 2).Count(page);
30 30
31 if (delta > 0) { 31 if (delta > 0) {
@@ -44,26 +44,27 @@ void RasterizerAccelerated::UpdatePagesCachedCount(VAddr addr, u64 size, int del
44 if (uncache_bytes == 0) { 44 if (uncache_bytes == 0) {
45 uncache_begin = page; 45 uncache_begin = page;
46 } 46 }
47 uncache_bytes += PAGE_SIZE; 47 uncache_bytes += YUZU_PAGESIZE;
48 } else if (uncache_bytes > 0) { 48 } else if (uncache_bytes > 0) {
49 cpu_memory.RasterizerMarkRegionCached(uncache_begin << PAGE_BITS, uncache_bytes, false); 49 cpu_memory.RasterizerMarkRegionCached(uncache_begin << YUZU_PAGEBITS, uncache_bytes,
50 false);
50 uncache_bytes = 0; 51 uncache_bytes = 0;
51 } 52 }
52 if (count.load(std::memory_order::relaxed) == 1 && delta > 0) { 53 if (count.load(std::memory_order::relaxed) == 1 && delta > 0) {
53 if (cache_bytes == 0) { 54 if (cache_bytes == 0) {
54 cache_begin = page; 55 cache_begin = page;
55 } 56 }
56 cache_bytes += PAGE_SIZE; 57 cache_bytes += YUZU_PAGESIZE;
57 } else if (cache_bytes > 0) { 58 } else if (cache_bytes > 0) {
58 cpu_memory.RasterizerMarkRegionCached(cache_begin << PAGE_BITS, cache_bytes, true); 59 cpu_memory.RasterizerMarkRegionCached(cache_begin << YUZU_PAGEBITS, cache_bytes, true);
59 cache_bytes = 0; 60 cache_bytes = 0;
60 } 61 }
61 } 62 }
62 if (uncache_bytes > 0) { 63 if (uncache_bytes > 0) {
63 cpu_memory.RasterizerMarkRegionCached(uncache_begin << PAGE_BITS, uncache_bytes, false); 64 cpu_memory.RasterizerMarkRegionCached(uncache_begin << YUZU_PAGEBITS, uncache_bytes, false);
64 } 65 }
65 if (cache_bytes > 0) { 66 if (cache_bytes > 0) {
66 cpu_memory.RasterizerMarkRegionCached(cache_begin << PAGE_BITS, cache_bytes, true); 67 cpu_memory.RasterizerMarkRegionCached(cache_begin << YUZU_PAGEBITS, cache_bytes, true);
67 } 68 }
68} 69}
69 70
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 07d4b7cf0..ddb70934c 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -49,7 +49,7 @@ using VideoCommon::LoadPipelines;
49using VideoCommon::SerializePipeline; 49using VideoCommon::SerializePipeline;
50using Context = ShaderContext::Context; 50using Context = ShaderContext::Context;
51 51
52constexpr u32 CACHE_VERSION = 5; 52constexpr u32 CACHE_VERSION = 6;
53 53
54template <typename Container> 54template <typename Container>
55auto MakeSpan(Container& container) { 55auto MakeSpan(Container& container) {
@@ -299,7 +299,7 @@ void ShaderCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading,
299 state.has_loaded = true; 299 state.has_loaded = true;
300 lock.unlock(); 300 lock.unlock();
301 301
302 workers->WaitForRequests(); 302 workers->WaitForRequests(stop_loading);
303 if (!use_asynchronous_shaders) { 303 if (!use_asynchronous_shaders) {
304 workers.reset(); 304 workers.reset();
305 } 305 }
diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp
index f83ad0a5b..a0d9d10ef 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_util.cpp
@@ -17,6 +17,7 @@ static OGLProgram LinkSeparableProgram(GLuint shader) {
17 glProgramParameteri(program.handle, GL_PROGRAM_SEPARABLE, GL_TRUE); 17 glProgramParameteri(program.handle, GL_PROGRAM_SEPARABLE, GL_TRUE);
18 glAttachShader(program.handle, shader); 18 glAttachShader(program.handle, shader);
19 glLinkProgram(program.handle); 19 glLinkProgram(program.handle);
20 glDetachShader(program.handle, shader);
20 if (!Settings::values.renderer_debug) { 21 if (!Settings::values.renderer_debug) {
21 return program; 22 return program;
22 } 23 }
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 8c0fffc67..99cd11d1e 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -93,6 +93,7 @@ GLenum ImageTarget(Shader::TextureType type, int num_samples = 1) {
93 case Shader::TextureType::Color1D: 93 case Shader::TextureType::Color1D:
94 return GL_TEXTURE_1D; 94 return GL_TEXTURE_1D;
95 case Shader::TextureType::Color2D: 95 case Shader::TextureType::Color2D:
96 case Shader::TextureType::Color2DRect:
96 return is_multisampled ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; 97 return is_multisampled ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
97 case Shader::TextureType::ColorCube: 98 case Shader::TextureType::ColorCube:
98 return GL_TEXTURE_CUBE_MAP; 99 return GL_TEXTURE_CUBE_MAP;
@@ -502,6 +503,7 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, ProgramManager&
502 set_view(Shader::TextureType::ColorArray1D, null_image_1d_array.handle); 503 set_view(Shader::TextureType::ColorArray1D, null_image_1d_array.handle);
503 set_view(Shader::TextureType::ColorArray2D, null_image_view_2d_array.handle); 504 set_view(Shader::TextureType::ColorArray2D, null_image_view_2d_array.handle);
504 set_view(Shader::TextureType::ColorArrayCube, null_image_cube_array.handle); 505 set_view(Shader::TextureType::ColorArrayCube, null_image_cube_array.handle);
506 set_view(Shader::TextureType::Color2DRect, null_image_view_2d.handle);
505 507
506 if (resolution.active) { 508 if (resolution.active) {
507 for (size_t i = 0; i < rescale_draw_fbos.size(); ++i) { 509 for (size_t i = 0; i < rescale_draw_fbos.size(); ++i) {
@@ -1110,6 +1112,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
1110 flat_range.extent.layers = 1; 1112 flat_range.extent.layers = 1;
1111 [[fallthrough]]; 1113 [[fallthrough]];
1112 case ImageViewType::e2D: 1114 case ImageViewType::e2D:
1115 case ImageViewType::Rect:
1113 if (True(flags & VideoCommon::ImageViewFlagBits::Slice)) { 1116 if (True(flags & VideoCommon::ImageViewFlagBits::Slice)) {
1114 // 2D and 2D array views on a 3D textures are used exclusively for render targets 1117 // 2D and 2D array views on a 3D textures are used exclusively for render targets
1115 ASSERT(info.range.extent.levels == 1); 1118 ASSERT(info.range.extent.levels == 1);
@@ -1135,9 +1138,6 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
1135 SetupView(Shader::TextureType::ColorCube); 1138 SetupView(Shader::TextureType::ColorCube);
1136 SetupView(Shader::TextureType::ColorArrayCube); 1139 SetupView(Shader::TextureType::ColorArrayCube);
1137 break; 1140 break;
1138 case ImageViewType::Rect:
1139 UNIMPLEMENTED();
1140 break;
1141 case ImageViewType::Buffer: 1141 case ImageViewType::Buffer:
1142 ASSERT(false); 1142 ASSERT(false);
1143 break; 1143 break;
@@ -1150,6 +1150,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
1150 default_handle = Handle(Shader::TextureType::ColorArray1D); 1150 default_handle = Handle(Shader::TextureType::ColorArray1D);
1151 break; 1151 break;
1152 case ImageViewType::e2D: 1152 case ImageViewType::e2D:
1153 case ImageViewType::Rect:
1153 default_handle = Handle(Shader::TextureType::Color2D); 1154 default_handle = Handle(Shader::TextureType::Color2D);
1154 break; 1155 break;
1155 case ImageViewType::e2DArray: 1156 case ImageViewType::e2DArray:
@@ -1210,6 +1211,7 @@ GLuint ImageView::MakeView(Shader::TextureType view_type, GLenum view_format) {
1210 case Shader::TextureType::Color1D: 1211 case Shader::TextureType::Color1D:
1211 case Shader::TextureType::Color2D: 1212 case Shader::TextureType::Color2D:
1212 case Shader::TextureType::ColorCube: 1213 case Shader::TextureType::ColorCube:
1214 case Shader::TextureType::Color2DRect:
1213 view_range = flat_range; 1215 view_range = flat_range;
1214 break; 1216 break;
1215 case Shader::TextureType::ColorArray1D: 1217 case Shader::TextureType::ColorArray1D:
@@ -1250,7 +1252,6 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const TSCEntry& config) {
1250 const GLint seamless = config.cubemap_interface_filtering ? GL_TRUE : GL_FALSE; 1252 const GLint seamless = config.cubemap_interface_filtering ? GL_TRUE : GL_FALSE;
1251 1253
1252 UNIMPLEMENTED_IF(config.cubemap_anisotropy != 1); 1254 UNIMPLEMENTED_IF(config.cubemap_anisotropy != 1);
1253 UNIMPLEMENTED_IF(config.float_coord_normalization != 0);
1254 1255
1255 sampler.Create(); 1256 sampler.Create();
1256 const GLuint handle = sampler.handle; 1257 const GLuint handle = sampler.handle;
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 01028cee0..34f3f7a67 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -478,13 +478,16 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
478 } 478 }
479 } 479 }
480 480
481 ASSERT_MSG(framebuffer_crop_rect.top == 0, "Unimplemented");
482 ASSERT_MSG(framebuffer_crop_rect.left == 0, "Unimplemented"); 481 ASSERT_MSG(framebuffer_crop_rect.left == 0, "Unimplemented");
483 482
483 f32 left_start{};
484 if (framebuffer_crop_rect.Top() > 0) {
485 left_start = static_cast<f32>(framebuffer_crop_rect.Top()) /
486 static_cast<f32>(framebuffer_crop_rect.Bottom());
487 }
484 f32 scale_u = static_cast<f32>(framebuffer_width) / static_cast<f32>(screen_info.texture.width); 488 f32 scale_u = static_cast<f32>(framebuffer_width) / static_cast<f32>(screen_info.texture.width);
485 f32 scale_v = 489 f32 scale_v =
486 static_cast<f32>(framebuffer_height) / static_cast<f32>(screen_info.texture.height); 490 static_cast<f32>(framebuffer_height) / static_cast<f32>(screen_info.texture.height);
487
488 // Scale the output by the crop width/height. This is commonly used with 1280x720 rendering 491 // Scale the output by the crop width/height. This is commonly used with 1280x720 rendering
489 // (e.g. handheld mode) on a 1920x1080 framebuffer. 492 // (e.g. handheld mode) on a 1920x1080 framebuffer.
490 if (framebuffer_crop_rect.GetWidth() > 0) { 493 if (framebuffer_crop_rect.GetWidth() > 0) {
@@ -503,10 +506,14 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
503 506
504 const auto& screen = layout.screen; 507 const auto& screen = layout.screen;
505 const std::array vertices = { 508 const std::array vertices = {
506 ScreenRectVertex(screen.left, screen.top, texcoords.top * scale_u, left * scale_v), 509 ScreenRectVertex(screen.left, screen.top, texcoords.top * scale_u,
507 ScreenRectVertex(screen.right, screen.top, texcoords.bottom * scale_u, left * scale_v), 510 left_start + left * scale_v),
508 ScreenRectVertex(screen.left, screen.bottom, texcoords.top * scale_u, right * scale_v), 511 ScreenRectVertex(screen.right, screen.top, texcoords.bottom * scale_u,
509 ScreenRectVertex(screen.right, screen.bottom, texcoords.bottom * scale_u, right * scale_v), 512 left_start + left * scale_v),
513 ScreenRectVertex(screen.left, screen.bottom, texcoords.top * scale_u,
514 left_start + right * scale_v),
515 ScreenRectVertex(screen.right, screen.bottom, texcoords.bottom * scale_u,
516 left_start + right * scale_v),
510 }; 517 };
511 glNamedBufferSubData(vertex_buffer.handle, 0, sizeof(vertices), std::data(vertices)); 518 glNamedBufferSubData(vertex_buffer.handle, 0, sizeof(vertices), std::data(vertices));
512 519
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
index 689164a6a..bdb71dc53 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
@@ -172,7 +172,7 @@ struct FormatTuple {
172 {VK_FORMAT_R8G8_SINT, Attachable | Storage}, // R8G8_SINT 172 {VK_FORMAT_R8G8_SINT, Attachable | Storage}, // R8G8_SINT
173 {VK_FORMAT_R8G8_UINT, Attachable | Storage}, // R8G8_UINT 173 {VK_FORMAT_R8G8_UINT, Attachable | Storage}, // R8G8_UINT
174 {VK_FORMAT_R32G32_UINT, Attachable | Storage}, // R32G32_UINT 174 {VK_FORMAT_R32G32_UINT, Attachable | Storage}, // R32G32_UINT
175 {VK_FORMAT_UNDEFINED}, // R16G16B16X16_FLOAT 175 {VK_FORMAT_R16G16B16A16_SFLOAT, Attachable | Storage}, // R16G16B16X16_FLOAT
176 {VK_FORMAT_R32_UINT, Attachable | Storage}, // R32_UINT 176 {VK_FORMAT_R32_UINT, Attachable | Storage}, // R32_UINT
177 {VK_FORMAT_R32_SINT, Attachable | Storage}, // R32_SINT 177 {VK_FORMAT_R32_SINT, Attachable | Storage}, // R32_SINT
178 {VK_FORMAT_ASTC_8x8_UNORM_BLOCK}, // ASTC_2D_8X8_UNORM 178 {VK_FORMAT_ASTC_8x8_UNORM_BLOCK}, // ASTC_2D_8X8_UNORM
@@ -317,195 +317,204 @@ VkPrimitiveTopology PrimitiveTopology([[maybe_unused]] const Device& device,
317 } 317 }
318} 318}
319 319
320VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttribute::Size size) { 320VkFormat VertexFormat(const Device& device, Maxwell::VertexAttribute::Type type,
321 switch (type) { 321 Maxwell::VertexAttribute::Size size) {
322 case Maxwell::VertexAttribute::Type::UnsignedNorm: 322 const VkFormat format{([&]() {
323 switch (size) { 323 switch (type) {
324 case Maxwell::VertexAttribute::Size::Size_8: 324 case Maxwell::VertexAttribute::Type::UnsignedNorm:
325 return VK_FORMAT_R8_UNORM; 325 switch (size) {
326 case Maxwell::VertexAttribute::Size::Size_8_8: 326 case Maxwell::VertexAttribute::Size::Size_8:
327 return VK_FORMAT_R8G8_UNORM; 327 return VK_FORMAT_R8_UNORM;
328 case Maxwell::VertexAttribute::Size::Size_8_8_8: 328 case Maxwell::VertexAttribute::Size::Size_8_8:
329 return VK_FORMAT_R8G8B8_UNORM; 329 return VK_FORMAT_R8G8_UNORM;
330 case Maxwell::VertexAttribute::Size::Size_8_8_8_8: 330 case Maxwell::VertexAttribute::Size::Size_8_8_8:
331 return VK_FORMAT_R8G8B8A8_UNORM; 331 return VK_FORMAT_R8G8B8_UNORM;
332 case Maxwell::VertexAttribute::Size::Size_16: 332 case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
333 return VK_FORMAT_R16_UNORM; 333 return VK_FORMAT_R8G8B8A8_UNORM;
334 case Maxwell::VertexAttribute::Size::Size_16_16: 334 case Maxwell::VertexAttribute::Size::Size_16:
335 return VK_FORMAT_R16G16_UNORM; 335 return VK_FORMAT_R16_UNORM;
336 case Maxwell::VertexAttribute::Size::Size_16_16_16: 336 case Maxwell::VertexAttribute::Size::Size_16_16:
337 return VK_FORMAT_R16G16B16_UNORM; 337 return VK_FORMAT_R16G16_UNORM;
338 case Maxwell::VertexAttribute::Size::Size_16_16_16_16: 338 case Maxwell::VertexAttribute::Size::Size_16_16_16:
339 return VK_FORMAT_R16G16B16A16_UNORM; 339 return VK_FORMAT_R16G16B16_UNORM;
340 case Maxwell::VertexAttribute::Size::Size_10_10_10_2: 340 case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
341 return VK_FORMAT_A2B10G10R10_UNORM_PACK32; 341 return VK_FORMAT_R16G16B16A16_UNORM;
342 default: 342 case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
343 return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
344 default:
345 break;
346 }
343 break; 347 break;
344 } 348 case Maxwell::VertexAttribute::Type::SignedNorm:
345 break; 349 switch (size) {
346 case Maxwell::VertexAttribute::Type::SignedNorm: 350 case Maxwell::VertexAttribute::Size::Size_8:
347 switch (size) { 351 return VK_FORMAT_R8_SNORM;
348 case Maxwell::VertexAttribute::Size::Size_8: 352 case Maxwell::VertexAttribute::Size::Size_8_8:
349 return VK_FORMAT_R8_SNORM; 353 return VK_FORMAT_R8G8_SNORM;
350 case Maxwell::VertexAttribute::Size::Size_8_8: 354 case Maxwell::VertexAttribute::Size::Size_8_8_8:
351 return VK_FORMAT_R8G8_SNORM; 355 return VK_FORMAT_R8G8B8_SNORM;
352 case Maxwell::VertexAttribute::Size::Size_8_8_8: 356 case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
353 return VK_FORMAT_R8G8B8_SNORM; 357 return VK_FORMAT_R8G8B8A8_SNORM;
354 case Maxwell::VertexAttribute::Size::Size_8_8_8_8: 358 case Maxwell::VertexAttribute::Size::Size_16:
355 return VK_FORMAT_R8G8B8A8_SNORM; 359 return VK_FORMAT_R16_SNORM;
356 case Maxwell::VertexAttribute::Size::Size_16: 360 case Maxwell::VertexAttribute::Size::Size_16_16:
357 return VK_FORMAT_R16_SNORM; 361 return VK_FORMAT_R16G16_SNORM;
358 case Maxwell::VertexAttribute::Size::Size_16_16: 362 case Maxwell::VertexAttribute::Size::Size_16_16_16:
359 return VK_FORMAT_R16G16_SNORM; 363 return VK_FORMAT_R16G16B16_SNORM;
360 case Maxwell::VertexAttribute::Size::Size_16_16_16: 364 case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
361 return VK_FORMAT_R16G16B16_SNORM; 365 return VK_FORMAT_R16G16B16A16_SNORM;
362 case Maxwell::VertexAttribute::Size::Size_16_16_16_16: 366 case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
363 return VK_FORMAT_R16G16B16A16_SNORM; 367 return VK_FORMAT_A2B10G10R10_SNORM_PACK32;
364 case Maxwell::VertexAttribute::Size::Size_10_10_10_2: 368 default:
365 return VK_FORMAT_A2B10G10R10_SNORM_PACK32; 369 break;
366 default: 370 }
367 break; 371 break;
368 } 372 case Maxwell::VertexAttribute::Type::UnsignedScaled:
369 break; 373 switch (size) {
370 case Maxwell::VertexAttribute::Type::UnsignedScaled: 374 case Maxwell::VertexAttribute::Size::Size_8:
371 switch (size) { 375 return VK_FORMAT_R8_USCALED;
372 case Maxwell::VertexAttribute::Size::Size_8: 376 case Maxwell::VertexAttribute::Size::Size_8_8:
373 return VK_FORMAT_R8_USCALED; 377 return VK_FORMAT_R8G8_USCALED;
374 case Maxwell::VertexAttribute::Size::Size_8_8: 378 case Maxwell::VertexAttribute::Size::Size_8_8_8:
375 return VK_FORMAT_R8G8_USCALED; 379 return VK_FORMAT_R8G8B8_USCALED;
376 case Maxwell::VertexAttribute::Size::Size_8_8_8: 380 case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
377 return VK_FORMAT_R8G8B8_USCALED; 381 return VK_FORMAT_R8G8B8A8_USCALED;
378 case Maxwell::VertexAttribute::Size::Size_8_8_8_8: 382 case Maxwell::VertexAttribute::Size::Size_16:
379 return VK_FORMAT_R8G8B8A8_USCALED; 383 return VK_FORMAT_R16_USCALED;
380 case Maxwell::VertexAttribute::Size::Size_16: 384 case Maxwell::VertexAttribute::Size::Size_16_16:
381 return VK_FORMAT_R16_USCALED; 385 return VK_FORMAT_R16G16_USCALED;
382 case Maxwell::VertexAttribute::Size::Size_16_16: 386 case Maxwell::VertexAttribute::Size::Size_16_16_16:
383 return VK_FORMAT_R16G16_USCALED; 387 return VK_FORMAT_R16G16B16_USCALED;
384 case Maxwell::VertexAttribute::Size::Size_16_16_16: 388 case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
385 return VK_FORMAT_R16G16B16_USCALED; 389 return VK_FORMAT_R16G16B16A16_USCALED;
386 case Maxwell::VertexAttribute::Size::Size_16_16_16_16: 390 case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
387 return VK_FORMAT_R16G16B16A16_USCALED; 391 return VK_FORMAT_A2B10G10R10_USCALED_PACK32;
388 case Maxwell::VertexAttribute::Size::Size_10_10_10_2: 392 default:
389 return VK_FORMAT_A2B10G10R10_USCALED_PACK32; 393 break;
390 default: 394 }
391 break; 395 break;
392 } 396 case Maxwell::VertexAttribute::Type::SignedScaled:
393 break; 397 switch (size) {
394 case Maxwell::VertexAttribute::Type::SignedScaled: 398 case Maxwell::VertexAttribute::Size::Size_8:
395 switch (size) { 399 return VK_FORMAT_R8_SSCALED;
396 case Maxwell::VertexAttribute::Size::Size_8: 400 case Maxwell::VertexAttribute::Size::Size_8_8:
397 return VK_FORMAT_R8_SSCALED; 401 return VK_FORMAT_R8G8_SSCALED;
398 case Maxwell::VertexAttribute::Size::Size_8_8: 402 case Maxwell::VertexAttribute::Size::Size_8_8_8:
399 return VK_FORMAT_R8G8_SSCALED; 403 return VK_FORMAT_R8G8B8_SSCALED;
400 case Maxwell::VertexAttribute::Size::Size_8_8_8: 404 case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
401 return VK_FORMAT_R8G8B8_SSCALED; 405 return VK_FORMAT_R8G8B8A8_SSCALED;
402 case Maxwell::VertexAttribute::Size::Size_8_8_8_8: 406 case Maxwell::VertexAttribute::Size::Size_16:
403 return VK_FORMAT_R8G8B8A8_SSCALED; 407 return VK_FORMAT_R16_SSCALED;
404 case Maxwell::VertexAttribute::Size::Size_16: 408 case Maxwell::VertexAttribute::Size::Size_16_16:
405 return VK_FORMAT_R16_SSCALED; 409 return VK_FORMAT_R16G16_SSCALED;
406 case Maxwell::VertexAttribute::Size::Size_16_16: 410 case Maxwell::VertexAttribute::Size::Size_16_16_16:
407 return VK_FORMAT_R16G16_SSCALED; 411 return VK_FORMAT_R16G16B16_SSCALED;
408 case Maxwell::VertexAttribute::Size::Size_16_16_16: 412 case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
409 return VK_FORMAT_R16G16B16_SSCALED; 413 return VK_FORMAT_R16G16B16A16_SSCALED;
410 case Maxwell::VertexAttribute::Size::Size_16_16_16_16: 414 case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
411 return VK_FORMAT_R16G16B16A16_SSCALED; 415 return VK_FORMAT_A2B10G10R10_SSCALED_PACK32;
412 case Maxwell::VertexAttribute::Size::Size_10_10_10_2: 416 default:
413 return VK_FORMAT_A2B10G10R10_SSCALED_PACK32; 417 break;
414 default: 418 }
415 break; 419 break;
416 } 420 case Maxwell::VertexAttribute::Type::UnsignedInt:
417 break; 421 switch (size) {
418 case Maxwell::VertexAttribute::Type::UnsignedInt: 422 case Maxwell::VertexAttribute::Size::Size_8:
419 switch (size) { 423 return VK_FORMAT_R8_UINT;
420 case Maxwell::VertexAttribute::Size::Size_8: 424 case Maxwell::VertexAttribute::Size::Size_8_8:
421 return VK_FORMAT_R8_UINT; 425 return VK_FORMAT_R8G8_UINT;
422 case Maxwell::VertexAttribute::Size::Size_8_8: 426 case Maxwell::VertexAttribute::Size::Size_8_8_8:
423 return VK_FORMAT_R8G8_UINT; 427 return VK_FORMAT_R8G8B8_UINT;
424 case Maxwell::VertexAttribute::Size::Size_8_8_8: 428 case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
425 return VK_FORMAT_R8G8B8_UINT; 429 return VK_FORMAT_R8G8B8A8_UINT;
426 case Maxwell::VertexAttribute::Size::Size_8_8_8_8: 430 case Maxwell::VertexAttribute::Size::Size_16:
427 return VK_FORMAT_R8G8B8A8_UINT; 431 return VK_FORMAT_R16_UINT;
428 case Maxwell::VertexAttribute::Size::Size_16: 432 case Maxwell::VertexAttribute::Size::Size_16_16:
429 return VK_FORMAT_R16_UINT; 433 return VK_FORMAT_R16G16_UINT;
430 case Maxwell::VertexAttribute::Size::Size_16_16: 434 case Maxwell::VertexAttribute::Size::Size_16_16_16:
431 return VK_FORMAT_R16G16_UINT; 435 return VK_FORMAT_R16G16B16_UINT;
432 case Maxwell::VertexAttribute::Size::Size_16_16_16: 436 case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
433 return VK_FORMAT_R16G16B16_UINT; 437 return VK_FORMAT_R16G16B16A16_UINT;
434 case Maxwell::VertexAttribute::Size::Size_16_16_16_16: 438 case Maxwell::VertexAttribute::Size::Size_32:
435 return VK_FORMAT_R16G16B16A16_UINT; 439 return VK_FORMAT_R32_UINT;
436 case Maxwell::VertexAttribute::Size::Size_32: 440 case Maxwell::VertexAttribute::Size::Size_32_32:
437 return VK_FORMAT_R32_UINT; 441 return VK_FORMAT_R32G32_UINT;
438 case Maxwell::VertexAttribute::Size::Size_32_32: 442 case Maxwell::VertexAttribute::Size::Size_32_32_32:
439 return VK_FORMAT_R32G32_UINT; 443 return VK_FORMAT_R32G32B32_UINT;
440 case Maxwell::VertexAttribute::Size::Size_32_32_32: 444 case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
441 return VK_FORMAT_R32G32B32_UINT; 445 return VK_FORMAT_R32G32B32A32_UINT;
442 case Maxwell::VertexAttribute::Size::Size_32_32_32_32: 446 case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
443 return VK_FORMAT_R32G32B32A32_UINT; 447 return VK_FORMAT_A2B10G10R10_UINT_PACK32;
444 case Maxwell::VertexAttribute::Size::Size_10_10_10_2: 448 default:
445 return VK_FORMAT_A2B10G10R10_UINT_PACK32; 449 break;
446 default: 450 }
447 break; 451 break;
448 } 452 case Maxwell::VertexAttribute::Type::SignedInt:
449 break; 453 switch (size) {
450 case Maxwell::VertexAttribute::Type::SignedInt: 454 case Maxwell::VertexAttribute::Size::Size_8:
451 switch (size) { 455 return VK_FORMAT_R8_SINT;
452 case Maxwell::VertexAttribute::Size::Size_8: 456 case Maxwell::VertexAttribute::Size::Size_8_8:
453 return VK_FORMAT_R8_SINT; 457 return VK_FORMAT_R8G8_SINT;
454 case Maxwell::VertexAttribute::Size::Size_8_8: 458 case Maxwell::VertexAttribute::Size::Size_8_8_8:
455 return VK_FORMAT_R8G8_SINT; 459 return VK_FORMAT_R8G8B8_SINT;
456 case Maxwell::VertexAttribute::Size::Size_8_8_8: 460 case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
457 return VK_FORMAT_R8G8B8_SINT; 461 return VK_FORMAT_R8G8B8A8_SINT;
458 case Maxwell::VertexAttribute::Size::Size_8_8_8_8: 462 case Maxwell::VertexAttribute::Size::Size_16:
459 return VK_FORMAT_R8G8B8A8_SINT; 463 return VK_FORMAT_R16_SINT;
460 case Maxwell::VertexAttribute::Size::Size_16: 464 case Maxwell::VertexAttribute::Size::Size_16_16:
461 return VK_FORMAT_R16_SINT; 465 return VK_FORMAT_R16G16_SINT;
462 case Maxwell::VertexAttribute::Size::Size_16_16: 466 case Maxwell::VertexAttribute::Size::Size_16_16_16:
463 return VK_FORMAT_R16G16_SINT; 467 return VK_FORMAT_R16G16B16_SINT;
464 case Maxwell::VertexAttribute::Size::Size_16_16_16: 468 case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
465 return VK_FORMAT_R16G16B16_SINT; 469 return VK_FORMAT_R16G16B16A16_SINT;
466 case Maxwell::VertexAttribute::Size::Size_16_16_16_16: 470 case Maxwell::VertexAttribute::Size::Size_32:
467 return VK_FORMAT_R16G16B16A16_SINT; 471 return VK_FORMAT_R32_SINT;
468 case Maxwell::VertexAttribute::Size::Size_32: 472 case Maxwell::VertexAttribute::Size::Size_32_32:
469 return VK_FORMAT_R32_SINT; 473 return VK_FORMAT_R32G32_SINT;
470 case Maxwell::VertexAttribute::Size::Size_32_32: 474 case Maxwell::VertexAttribute::Size::Size_32_32_32:
471 return VK_FORMAT_R32G32_SINT; 475 return VK_FORMAT_R32G32B32_SINT;
472 case Maxwell::VertexAttribute::Size::Size_32_32_32: 476 case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
473 return VK_FORMAT_R32G32B32_SINT; 477 return VK_FORMAT_R32G32B32A32_SINT;
474 case Maxwell::VertexAttribute::Size::Size_32_32_32_32: 478 case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
475 return VK_FORMAT_R32G32B32A32_SINT; 479 return VK_FORMAT_A2B10G10R10_SINT_PACK32;
476 case Maxwell::VertexAttribute::Size::Size_10_10_10_2: 480 default:
477 return VK_FORMAT_A2B10G10R10_SINT_PACK32; 481 break;
478 default: 482 }
479 break; 483 break;
480 } 484 case Maxwell::VertexAttribute::Type::Float:
481 break; 485 switch (size) {
482 case Maxwell::VertexAttribute::Type::Float: 486 case Maxwell::VertexAttribute::Size::Size_16:
483 switch (size) { 487 return VK_FORMAT_R16_SFLOAT;
484 case Maxwell::VertexAttribute::Size::Size_16: 488 case Maxwell::VertexAttribute::Size::Size_16_16:
485 return VK_FORMAT_R16_SFLOAT; 489 return VK_FORMAT_R16G16_SFLOAT;
486 case Maxwell::VertexAttribute::Size::Size_16_16: 490 case Maxwell::VertexAttribute::Size::Size_16_16_16:
487 return VK_FORMAT_R16G16_SFLOAT; 491 return VK_FORMAT_R16G16B16_SFLOAT;
488 case Maxwell::VertexAttribute::Size::Size_16_16_16: 492 case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
489 return VK_FORMAT_R16G16B16_SFLOAT; 493 return VK_FORMAT_R16G16B16A16_SFLOAT;
490 case Maxwell::VertexAttribute::Size::Size_16_16_16_16: 494 case Maxwell::VertexAttribute::Size::Size_32:
491 return VK_FORMAT_R16G16B16A16_SFLOAT; 495 return VK_FORMAT_R32_SFLOAT;
492 case Maxwell::VertexAttribute::Size::Size_32: 496 case Maxwell::VertexAttribute::Size::Size_32_32:
493 return VK_FORMAT_R32_SFLOAT; 497 return VK_FORMAT_R32G32_SFLOAT;
494 case Maxwell::VertexAttribute::Size::Size_32_32: 498 case Maxwell::VertexAttribute::Size::Size_32_32_32:
495 return VK_FORMAT_R32G32_SFLOAT; 499 return VK_FORMAT_R32G32B32_SFLOAT;
496 case Maxwell::VertexAttribute::Size::Size_32_32_32: 500 case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
497 return VK_FORMAT_R32G32B32_SFLOAT; 501 return VK_FORMAT_R32G32B32A32_SFLOAT;
498 case Maxwell::VertexAttribute::Size::Size_32_32_32_32: 502 case Maxwell::VertexAttribute::Size::Size_11_11_10:
499 return VK_FORMAT_R32G32B32A32_SFLOAT; 503 return VK_FORMAT_B10G11R11_UFLOAT_PACK32;
500 case Maxwell::VertexAttribute::Size::Size_11_11_10: 504 default:
501 return VK_FORMAT_B10G11R11_UFLOAT_PACK32; 505 break;
502 default: 506 }
503 break; 507 break;
504 } 508 }
505 break; 509 return VK_FORMAT_UNDEFINED;
510 })()};
511
512 if (format == VK_FORMAT_UNDEFINED) {
513 UNIMPLEMENTED_MSG("Unimplemented vertex format of type={} and size={}", type, size);
506 } 514 }
507 UNIMPLEMENTED_MSG("Unimplemented vertex format of type={} and size={}", type, size); 515
508 return {}; 516 return device.GetSupportedFormat(format, VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT,
517 FormatType::Buffer);
509} 518}
510 519
511VkCompareOp ComparisonOp(Maxwell::ComparisonOp comparison) { 520VkCompareOp ComparisonOp(Maxwell::ComparisonOp comparison) {
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.h b/src/video_core/renderer_vulkan/maxwell_to_vk.h
index 9edd6af6a..356d46292 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.h
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.h
@@ -48,7 +48,8 @@ VkShaderStageFlagBits ShaderStage(Shader::Stage stage);
48 48
49VkPrimitiveTopology PrimitiveTopology(const Device& device, Maxwell::PrimitiveTopology topology); 49VkPrimitiveTopology PrimitiveTopology(const Device& device, Maxwell::PrimitiveTopology topology);
50 50
51VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttribute::Size size); 51VkFormat VertexFormat(const Device& device, Maxwell::VertexAttribute::Type type,
52 Maxwell::VertexAttribute::Size size);
52 53
53VkCompareOp ComparisonOp(Maxwell::ComparisonOp comparison); 54VkCompareOp ComparisonOp(Maxwell::ComparisonOp comparison);
54 55
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index 4a1d96322..444c29f68 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -87,12 +87,8 @@ u32 GetBytesPerPixel(const Tegra::FramebufferConfig& framebuffer) {
87} 87}
88 88
89std::size_t GetSizeInBytes(const Tegra::FramebufferConfig& framebuffer) { 89std::size_t GetSizeInBytes(const Tegra::FramebufferConfig& framebuffer) {
90 // TODO(Rodrigo): Read this from HLE 90 return static_cast<std::size_t>(framebuffer.stride) *
91 constexpr u32 block_height_log2 = 4; 91 static_cast<std::size_t>(framebuffer.height) * GetBytesPerPixel(framebuffer);
92 const u32 bytes_per_pixel = GetBytesPerPixel(framebuffer);
93 const u64 size_bytes{Tegra::Texture::CalculateSize(
94 true, bytes_per_pixel, framebuffer.stride, framebuffer.height, 1, block_height_log2, 0)};
95 return size_bytes;
96} 92}
97 93
98VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) { 94VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
@@ -173,10 +169,12 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
173 // TODO(Rodrigo): Read this from HLE 169 // TODO(Rodrigo): Read this from HLE
174 constexpr u32 block_height_log2 = 4; 170 constexpr u32 block_height_log2 = 4;
175 const u32 bytes_per_pixel = GetBytesPerPixel(framebuffer); 171 const u32 bytes_per_pixel = GetBytesPerPixel(framebuffer);
176 const u64 size_bytes{GetSizeInBytes(framebuffer)}; 172 const u64 linear_size{GetSizeInBytes(framebuffer)};
177 173 const u64 tiled_size{Tegra::Texture::CalculateSize(true, bytes_per_pixel,
174 framebuffer.stride, framebuffer.height,
175 1, block_height_log2, 0)};
178 Tegra::Texture::UnswizzleTexture( 176 Tegra::Texture::UnswizzleTexture(
179 mapped_span.subspan(image_offset, size_bytes), std::span(host_ptr, size_bytes), 177 mapped_span.subspan(image_offset, linear_size), std::span(host_ptr, tiled_size),
180 bytes_per_pixel, framebuffer.width, framebuffer.height, 1, block_height_log2, 0); 178 bytes_per_pixel, framebuffer.width, framebuffer.height, 1, block_height_log2, 0);
181 179
182 const VkBufferImageCopy copy{ 180 const VkBufferImageCopy copy{
@@ -1404,12 +1402,15 @@ void BlitScreen::SetVertexData(BufferData& data, const Tegra::FramebufferConfig&
1404 break; 1402 break;
1405 } 1403 }
1406 1404
1407 UNIMPLEMENTED_IF(framebuffer_crop_rect.top != 0);
1408 UNIMPLEMENTED_IF(framebuffer_crop_rect.left != 0); 1405 UNIMPLEMENTED_IF(framebuffer_crop_rect.left != 0);
1409 1406
1407 f32 left_start{};
1408 if (framebuffer_crop_rect.Top() > 0) {
1409 left_start = static_cast<f32>(framebuffer_crop_rect.Top()) /
1410 static_cast<f32>(framebuffer_crop_rect.Bottom());
1411 }
1410 f32 scale_u = static_cast<f32>(framebuffer.width) / static_cast<f32>(screen_info.width); 1412 f32 scale_u = static_cast<f32>(framebuffer.width) / static_cast<f32>(screen_info.width);
1411 f32 scale_v = static_cast<f32>(framebuffer.height) / static_cast<f32>(screen_info.height); 1413 f32 scale_v = static_cast<f32>(framebuffer.height) / static_cast<f32>(screen_info.height);
1412
1413 // Scale the output by the crop width/height. This is commonly used with 1280x720 rendering 1414 // Scale the output by the crop width/height. This is commonly used with 1280x720 rendering
1414 // (e.g. handheld mode) on a 1920x1080 framebuffer. 1415 // (e.g. handheld mode) on a 1920x1080 framebuffer.
1415 if (!fsr) { 1416 if (!fsr) {
@@ -1428,10 +1429,13 @@ void BlitScreen::SetVertexData(BufferData& data, const Tegra::FramebufferConfig&
1428 const auto y = static_cast<f32>(screen.top); 1429 const auto y = static_cast<f32>(screen.top);
1429 const auto w = static_cast<f32>(screen.GetWidth()); 1430 const auto w = static_cast<f32>(screen.GetWidth());
1430 const auto h = static_cast<f32>(screen.GetHeight()); 1431 const auto h = static_cast<f32>(screen.GetHeight());
1431 data.vertices[0] = ScreenRectVertex(x, y, texcoords.top * scale_u, left * scale_v); 1432 data.vertices[0] = ScreenRectVertex(x, y, texcoords.top * scale_u, left_start + left * scale_v);
1432 data.vertices[1] = ScreenRectVertex(x + w, y, texcoords.bottom * scale_u, left * scale_v); 1433 data.vertices[1] =
1433 data.vertices[2] = ScreenRectVertex(x, y + h, texcoords.top * scale_u, right * scale_v); 1434 ScreenRectVertex(x + w, y, texcoords.bottom * scale_u, left_start + left * scale_v);
1434 data.vertices[3] = ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, right * scale_v); 1435 data.vertices[2] =
1436 ScreenRectVertex(x, y + h, texcoords.top * scale_u, left_start + right * scale_v);
1437 data.vertices[3] =
1438 ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, left_start + right * scale_v);
1435} 1439}
1436 1440
1437void BlitScreen::CreateFSR() { 1441void BlitScreen::CreateFSR() {
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index 682f05335..5aca8f038 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -559,7 +559,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
559 vertex_attributes.push_back({ 559 vertex_attributes.push_back({
560 .location = static_cast<u32>(index), 560 .location = static_cast<u32>(index),
561 .binding = attribute.buffer, 561 .binding = attribute.buffer,
562 .format = MaxwellToVK::VertexFormat(attribute.Type(), attribute.Size()), 562 .format = MaxwellToVK::VertexFormat(device, attribute.Type(), attribute.Size()),
563 .offset = attribute.offset, 563 .offset = attribute.offset,
564 }); 564 });
565 } 565 }
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 09e035799..9708dc45e 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -53,7 +53,7 @@ using VideoCommon::FileEnvironment;
53using VideoCommon::GenericEnvironment; 53using VideoCommon::GenericEnvironment;
54using VideoCommon::GraphicsEnvironment; 54using VideoCommon::GraphicsEnvironment;
55 55
56constexpr u32 CACHE_VERSION = 5; 56constexpr u32 CACHE_VERSION = 6;
57 57
58template <typename Container> 58template <typename Container>
59auto MakeSpan(Container& container) { 59auto MakeSpan(Container& container) {
@@ -434,7 +434,9 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
434 state.statistics.get(), false)}; 434 state.statistics.get(), false)};
435 435
436 std::scoped_lock lock{state.mutex}; 436 std::scoped_lock lock{state.mutex};
437 graphics_cache.emplace(key, std::move(pipeline)); 437 if (pipeline) {
438 graphics_cache.emplace(key, std::move(pipeline));
439 }
438 ++state.built; 440 ++state.built;
439 if (state.has_loaded) { 441 if (state.has_loaded) {
440 callback(VideoCore::LoadCallbackStage::Build, state.built, state.total); 442 callback(VideoCore::LoadCallbackStage::Build, state.built, state.total);
@@ -452,7 +454,7 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
452 state.has_loaded = true; 454 state.has_loaded = true;
453 lock.unlock(); 455 lock.unlock();
454 456
455 workers.WaitForRequests(); 457 workers.WaitForRequests(stop_loading);
456 458
457 if (state.statistics) { 459 if (state.statistics) {
458 state.statistics->Report(); 460 state.statistics->Report();
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 10f9fe7fe..7e40c2df1 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -69,10 +69,17 @@ VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t in
69 const float width = conv(src.scale_x * 2.0f); 69 const float width = conv(src.scale_x * 2.0f);
70 float y = conv(src.translate_y - src.scale_y); 70 float y = conv(src.translate_y - src.scale_y);
71 float height = conv(src.scale_y * 2.0f); 71 float height = conv(src.scale_y * 2.0f);
72 if (regs.screen_y_control.y_negate) { 72 bool y_negate = regs.screen_y_control.y_negate;
73
74 if (!device.IsNvViewportSwizzleSupported()) {
75 y_negate = y_negate != (src.swizzle.y == Maxwell::ViewportSwizzle::NegativeY);
76 }
77
78 if (y_negate) {
73 y += height; 79 y += height;
74 height = -height; 80 height = -height;
75 } 81 }
82
76 const float reduce_z = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1.0f : 0.0f; 83 const float reduce_z = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1.0f : 0.0f;
77 VkViewport viewport{ 84 VkViewport viewport{
78 .x = x, 85 .x = x,
@@ -939,7 +946,7 @@ void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs)
939 .pNext = nullptr, 946 .pNext = nullptr,
940 .location = static_cast<u32>(index), 947 .location = static_cast<u32>(index),
941 .binding = binding, 948 .binding = binding,
942 .format = MaxwellToVK::VertexFormat(attribute.type, attribute.size), 949 .format = MaxwellToVK::VertexFormat(device, attribute.type, attribute.size),
943 .offset = attribute.offset, 950 .offset = attribute.offset,
944 }); 951 });
945 } 952 }
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp
index fa8efd22e..a69ae7725 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.cpp
+++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp
@@ -33,9 +33,10 @@ VkSurfaceFormatKHR ChooseSwapSurfaceFormat(vk::Span<VkSurfaceFormatKHR> formats)
33} 33}
34 34
35VkPresentModeKHR ChooseSwapPresentMode(vk::Span<VkPresentModeKHR> modes) { 35VkPresentModeKHR ChooseSwapPresentMode(vk::Span<VkPresentModeKHR> modes) {
36 // Mailbox doesn't lock the application like fifo (vsync), prefer it 36 // Mailbox (triple buffering) doesn't lock the application like fifo (vsync),
37 // prefer it if vsync option is not selected
37 const auto found_mailbox = std::find(modes.begin(), modes.end(), VK_PRESENT_MODE_MAILBOX_KHR); 38 const auto found_mailbox = std::find(modes.begin(), modes.end(), VK_PRESENT_MODE_MAILBOX_KHR);
38 if (found_mailbox != modes.end()) { 39 if (found_mailbox != modes.end() && !Settings::values.use_vsync.GetValue()) {
39 return VK_PRESENT_MODE_MAILBOX_KHR; 40 return VK_PRESENT_MODE_MAILBOX_KHR;
40 } 41 }
41 if (!Settings::values.use_speed_limit.GetValue()) { 42 if (!Settings::values.use_speed_limit.GetValue()) {
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index ba6d81420..caca79d79 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -230,6 +230,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
230 case Shader::TextureType::Color1D: 230 case Shader::TextureType::Color1D:
231 return VK_IMAGE_VIEW_TYPE_1D; 231 return VK_IMAGE_VIEW_TYPE_1D;
232 case Shader::TextureType::Color2D: 232 case Shader::TextureType::Color2D:
233 case Shader::TextureType::Color2DRect:
233 return VK_IMAGE_VIEW_TYPE_2D; 234 return VK_IMAGE_VIEW_TYPE_2D;
234 case Shader::TextureType::ColorCube: 235 case Shader::TextureType::ColorCube:
235 return VK_IMAGE_VIEW_TYPE_CUBE; 236 return VK_IMAGE_VIEW_TYPE_CUBE;
@@ -254,6 +255,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
254 case VideoCommon::ImageViewType::e1D: 255 case VideoCommon::ImageViewType::e1D:
255 return VK_IMAGE_VIEW_TYPE_1D; 256 return VK_IMAGE_VIEW_TYPE_1D;
256 case VideoCommon::ImageViewType::e2D: 257 case VideoCommon::ImageViewType::e2D:
258 case VideoCommon::ImageViewType::Rect:
257 return VK_IMAGE_VIEW_TYPE_2D; 259 return VK_IMAGE_VIEW_TYPE_2D;
258 case VideoCommon::ImageViewType::Cube: 260 case VideoCommon::ImageViewType::Cube:
259 return VK_IMAGE_VIEW_TYPE_CUBE; 261 return VK_IMAGE_VIEW_TYPE_CUBE;
@@ -265,9 +267,6 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
265 return VK_IMAGE_VIEW_TYPE_2D_ARRAY; 267 return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
266 case VideoCommon::ImageViewType::CubeArray: 268 case VideoCommon::ImageViewType::CubeArray:
267 return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY; 269 return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
268 case VideoCommon::ImageViewType::Rect:
269 UNIMPLEMENTED_MSG("Rect image view");
270 return VK_IMAGE_VIEW_TYPE_2D;
271 case VideoCommon::ImageViewType::Buffer: 270 case VideoCommon::ImageViewType::Buffer:
272 ASSERT_MSG(false, "Texture buffers can't be image views"); 271 ASSERT_MSG(false, "Texture buffers can't be image views");
273 return VK_IMAGE_VIEW_TYPE_1D; 272 return VK_IMAGE_VIEW_TYPE_1D;
@@ -1579,6 +1578,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
1579 break; 1578 break;
1580 case VideoCommon::ImageViewType::e2D: 1579 case VideoCommon::ImageViewType::e2D:
1581 case VideoCommon::ImageViewType::e2DArray: 1580 case VideoCommon::ImageViewType::e2DArray:
1581 case VideoCommon::ImageViewType::Rect:
1582 create(TextureType::Color2D, 1); 1582 create(TextureType::Color2D, 1);
1583 create(TextureType::ColorArray2D, std::nullopt); 1583 create(TextureType::ColorArray2D, std::nullopt);
1584 render_target = Handle(Shader::TextureType::ColorArray2D); 1584 render_target = Handle(Shader::TextureType::ColorArray2D);
@@ -1592,9 +1592,6 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
1592 create(TextureType::ColorCube, 6); 1592 create(TextureType::ColorCube, 6);
1593 create(TextureType::ColorArrayCube, std::nullopt); 1593 create(TextureType::ColorArrayCube, std::nullopt);
1594 break; 1594 break;
1595 case VideoCommon::ImageViewType::Rect:
1596 UNIMPLEMENTED();
1597 break;
1598 case VideoCommon::ImageViewType::Buffer: 1595 case VideoCommon::ImageViewType::Buffer:
1599 ASSERT(false); 1596 ASSERT(false);
1600 break; 1597 break;
@@ -1618,6 +1615,9 @@ ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::NullImageViewParam
1618ImageView::~ImageView() = default; 1615ImageView::~ImageView() = default;
1619 1616
1620VkImageView ImageView::DepthView() { 1617VkImageView ImageView::DepthView() {
1618 if (!image_handle) {
1619 return VK_NULL_HANDLE;
1620 }
1621 if (depth_view) { 1621 if (depth_view) {
1622 return *depth_view; 1622 return *depth_view;
1623 } 1623 }
@@ -1627,6 +1627,9 @@ VkImageView ImageView::DepthView() {
1627} 1627}
1628 1628
1629VkImageView ImageView::StencilView() { 1629VkImageView ImageView::StencilView() {
1630 if (!image_handle) {
1631 return VK_NULL_HANDLE;
1632 }
1630 if (stencil_view) { 1633 if (stencil_view) {
1631 return *stencil_view; 1634 return *stencil_view;
1632 } 1635 }
@@ -1636,6 +1639,9 @@ VkImageView ImageView::StencilView() {
1636} 1639}
1637 1640
1638VkImageView ImageView::ColorView() { 1641VkImageView ImageView::ColorView() {
1642 if (!image_handle) {
1643 return VK_NULL_HANDLE;
1644 }
1639 if (color_view) { 1645 if (color_view) {
1640 return *color_view; 1646 return *color_view;
1641 } 1647 }
@@ -1645,6 +1651,9 @@ VkImageView ImageView::ColorView() {
1645 1651
1646VkImageView ImageView::StorageView(Shader::TextureType texture_type, 1652VkImageView ImageView::StorageView(Shader::TextureType texture_type,
1647 Shader::ImageFormat image_format) { 1653 Shader::ImageFormat image_format) {
1654 if (!image_handle) {
1655 return VK_NULL_HANDLE;
1656 }
1648 if (image_format == Shader::ImageFormat::Typeless) { 1657 if (image_format == Shader::ImageFormat::Typeless) {
1649 return Handle(texture_type); 1658 return Handle(texture_type);
1650 } 1659 }
diff --git a/src/video_core/shader_cache.cpp b/src/video_core/shader_cache.cpp
index 4b1101f7c..164e4ee0e 100644
--- a/src/video_core/shader_cache.cpp
+++ b/src/video_core/shader_cache.cpp
@@ -123,8 +123,8 @@ void ShaderCache::Register(std::unique_ptr<ShaderInfo> data, VAddr addr, size_t
123 const VAddr addr_end = addr + size; 123 const VAddr addr_end = addr + size;
124 Entry* const entry = NewEntry(addr, addr_end, data.get()); 124 Entry* const entry = NewEntry(addr, addr_end, data.get());
125 125
126 const u64 page_end = (addr_end + PAGE_SIZE - 1) >> PAGE_BITS; 126 const u64 page_end = (addr_end + YUZU_PAGESIZE - 1) >> YUZU_PAGEBITS;
127 for (u64 page = addr >> PAGE_BITS; page < page_end; ++page) { 127 for (u64 page = addr >> YUZU_PAGEBITS; page < page_end; ++page) {
128 invalidation_cache[page].push_back(entry); 128 invalidation_cache[page].push_back(entry);
129 } 129 }
130 130
@@ -135,8 +135,8 @@ void ShaderCache::Register(std::unique_ptr<ShaderInfo> data, VAddr addr, size_t
135 135
136void ShaderCache::InvalidatePagesInRegion(VAddr addr, size_t size) { 136void ShaderCache::InvalidatePagesInRegion(VAddr addr, size_t size) {
137 const VAddr addr_end = addr + size; 137 const VAddr addr_end = addr + size;
138 const u64 page_end = (addr_end + PAGE_SIZE - 1) >> PAGE_BITS; 138 const u64 page_end = (addr_end + YUZU_PAGESIZE - 1) >> YUZU_PAGEBITS;
139 for (u64 page = addr >> PAGE_BITS; page < page_end; ++page) { 139 for (u64 page = addr >> YUZU_PAGEBITS; page < page_end; ++page) {
140 auto it = invalidation_cache.find(page); 140 auto it = invalidation_cache.find(page);
141 if (it == invalidation_cache.end()) { 141 if (it == invalidation_cache.end()) {
142 continue; 142 continue;
@@ -189,8 +189,8 @@ void ShaderCache::InvalidatePageEntries(std::vector<Entry*>& entries, VAddr addr
189} 189}
190 190
191void ShaderCache::RemoveEntryFromInvalidationCache(const Entry* entry) { 191void ShaderCache::RemoveEntryFromInvalidationCache(const Entry* entry) {
192 const u64 page_end = (entry->addr_end + PAGE_SIZE - 1) >> PAGE_BITS; 192 const u64 page_end = (entry->addr_end + YUZU_PAGESIZE - 1) >> YUZU_PAGEBITS;
193 for (u64 page = entry->addr_start >> PAGE_BITS; page < page_end; ++page) { 193 for (u64 page = entry->addr_start >> YUZU_PAGEBITS; page < page_end; ++page) {
194 const auto entries_it = invalidation_cache.find(page); 194 const auto entries_it = invalidation_cache.find(page);
195 ASSERT(entries_it != invalidation_cache.end()); 195 ASSERT(entries_it != invalidation_cache.end());
196 std::vector<Entry*>& entries = entries_it->second; 196 std::vector<Entry*>& entries = entries_it->second;
diff --git a/src/video_core/shader_cache.h b/src/video_core/shader_cache.h
index 1109cfe83..f67cea8c4 100644
--- a/src/video_core/shader_cache.h
+++ b/src/video_core/shader_cache.h
@@ -29,8 +29,8 @@ struct ShaderInfo {
29}; 29};
30 30
31class ShaderCache { 31class ShaderCache {
32 static constexpr u64 PAGE_BITS = 14; 32 static constexpr u64 YUZU_PAGEBITS = 14;
33 static constexpr u64 PAGE_SIZE = u64(1) << PAGE_BITS; 33 static constexpr u64 YUZU_PAGESIZE = u64(1) << YUZU_PAGEBITS;
34 34
35 static constexpr size_t NUM_PROGRAMS = 6; 35 static constexpr size_t NUM_PROGRAMS = 6;
36 36
diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp
index c4e923bbf..5f7625947 100644
--- a/src/video_core/shader_environment.cpp
+++ b/src/video_core/shader_environment.cpp
@@ -39,7 +39,8 @@ static Shader::TextureType ConvertType(const Tegra::Texture::TICEntry& entry) {
39 return Shader::TextureType::Color1D; 39 return Shader::TextureType::Color1D;
40 case Tegra::Texture::TextureType::Texture2D: 40 case Tegra::Texture::TextureType::Texture2D:
41 case Tegra::Texture::TextureType::Texture2DNoMipmap: 41 case Tegra::Texture::TextureType::Texture2DNoMipmap:
42 return Shader::TextureType::Color2D; 42 return entry.normalized_coords ? Shader::TextureType::Color2D
43 : Shader::TextureType::Color2DRect;
43 case Tegra::Texture::TextureType::Texture3D: 44 case Tegra::Texture::TextureType::Texture3D:
44 return Shader::TextureType::Color3D; 45 return Shader::TextureType::Color3D;
45 case Tegra::Texture::TextureType::TextureCubemap: 46 case Tegra::Texture::TextureType::TextureCubemap:
@@ -53,7 +54,8 @@ static Shader::TextureType ConvertType(const Tegra::Texture::TICEntry& entry) {
53 case Tegra::Texture::TextureType::TextureCubeArray: 54 case Tegra::Texture::TextureType::TextureCubeArray:
54 return Shader::TextureType::ColorArrayCube; 55 return Shader::TextureType::ColorArrayCube;
55 default: 56 default:
56 throw Shader::NotImplementedException("Unknown texture type"); 57 UNIMPLEMENTED();
58 return Shader::TextureType::Color2D;
57 } 59 }
58} 60}
59 61
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index cf3ca06a6..1dbe01bc0 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -589,7 +589,7 @@ void TextureCache<P>::BlitImage(const Tegra::Engines::Fermi2D::Surface& dst,
589template <class P> 589template <class P>
590typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_addr) { 590typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_addr) {
591 // TODO: Properly implement this 591 // TODO: Properly implement this
592 const auto it = page_table.find(cpu_addr >> PAGE_BITS); 592 const auto it = page_table.find(cpu_addr >> YUZU_PAGEBITS);
593 if (it == page_table.end()) { 593 if (it == page_table.end()) {
594 return nullptr; 594 return nullptr;
595 } 595 }
@@ -1485,14 +1485,14 @@ void TextureCache<P>::UnregisterImage(ImageId image_id) {
1485 std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>>& selected_page_table) { 1485 std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>>& selected_page_table) {
1486 const auto page_it = selected_page_table.find(page); 1486 const auto page_it = selected_page_table.find(page);
1487 if (page_it == selected_page_table.end()) { 1487 if (page_it == selected_page_table.end()) {
1488 ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << PAGE_BITS); 1488 ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << YUZU_PAGEBITS);
1489 return; 1489 return;
1490 } 1490 }
1491 std::vector<ImageId>& image_ids = page_it->second; 1491 std::vector<ImageId>& image_ids = page_it->second;
1492 const auto vector_it = std::ranges::find(image_ids, image_id); 1492 const auto vector_it = std::ranges::find(image_ids, image_id);
1493 if (vector_it == image_ids.end()) { 1493 if (vector_it == image_ids.end()) {
1494 ASSERT_MSG(false, "Unregistering unregistered image in page=0x{:x}", 1494 ASSERT_MSG(false, "Unregistering unregistered image in page=0x{:x}",
1495 page << PAGE_BITS); 1495 page << YUZU_PAGEBITS);
1496 return; 1496 return;
1497 } 1497 }
1498 image_ids.erase(vector_it); 1498 image_ids.erase(vector_it);
@@ -1504,14 +1504,14 @@ void TextureCache<P>::UnregisterImage(ImageId image_id) {
1504 ForEachCPUPage(image.cpu_addr, image.guest_size_bytes, [this, map_id](u64 page) { 1504 ForEachCPUPage(image.cpu_addr, image.guest_size_bytes, [this, map_id](u64 page) {
1505 const auto page_it = page_table.find(page); 1505 const auto page_it = page_table.find(page);
1506 if (page_it == page_table.end()) { 1506 if (page_it == page_table.end()) {
1507 ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << PAGE_BITS); 1507 ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << YUZU_PAGEBITS);
1508 return; 1508 return;
1509 } 1509 }
1510 std::vector<ImageMapId>& image_map_ids = page_it->second; 1510 std::vector<ImageMapId>& image_map_ids = page_it->second;
1511 const auto vector_it = std::ranges::find(image_map_ids, map_id); 1511 const auto vector_it = std::ranges::find(image_map_ids, map_id);
1512 if (vector_it == image_map_ids.end()) { 1512 if (vector_it == image_map_ids.end()) {
1513 ASSERT_MSG(false, "Unregistering unregistered image in page=0x{:x}", 1513 ASSERT_MSG(false, "Unregistering unregistered image in page=0x{:x}",
1514 page << PAGE_BITS); 1514 page << YUZU_PAGEBITS);
1515 return; 1515 return;
1516 } 1516 }
1517 image_map_ids.erase(vector_it); 1517 image_map_ids.erase(vector_it);
@@ -1532,7 +1532,7 @@ void TextureCache<P>::UnregisterImage(ImageId image_id) {
1532 ForEachCPUPage(cpu_addr, size, [this, image_id](u64 page) { 1532 ForEachCPUPage(cpu_addr, size, [this, image_id](u64 page) {
1533 const auto page_it = page_table.find(page); 1533 const auto page_it = page_table.find(page);
1534 if (page_it == page_table.end()) { 1534 if (page_it == page_table.end()) {
1535 ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << PAGE_BITS); 1535 ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << YUZU_PAGEBITS);
1536 return; 1536 return;
1537 } 1537 }
1538 std::vector<ImageMapId>& image_map_ids = page_it->second; 1538 std::vector<ImageMapId>& image_map_ids = page_it->second;
diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h
index e2f8f84c9..7e6c6cef2 100644
--- a/src/video_core/texture_cache/texture_cache_base.h
+++ b/src/video_core/texture_cache/texture_cache_base.h
@@ -47,7 +47,7 @@ struct ImageViewInOut {
47template <class P> 47template <class P>
48class TextureCache { 48class TextureCache {
49 /// Address shift for caching images into a hash table 49 /// Address shift for caching images into a hash table
50 static constexpr u64 PAGE_BITS = 20; 50 static constexpr u64 YUZU_PAGEBITS = 20;
51 51
52 /// Enables debugging features to the texture cache 52 /// Enables debugging features to the texture cache
53 static constexpr bool ENABLE_VALIDATION = P::ENABLE_VALIDATION; 53 static constexpr bool ENABLE_VALIDATION = P::ENABLE_VALIDATION;
@@ -178,8 +178,8 @@ private:
178 template <typename Func> 178 template <typename Func>
179 static void ForEachCPUPage(VAddr addr, size_t size, Func&& func) { 179 static void ForEachCPUPage(VAddr addr, size_t size, Func&& func) {
180 static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>; 180 static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>;
181 const u64 page_end = (addr + size - 1) >> PAGE_BITS; 181 const u64 page_end = (addr + size - 1) >> YUZU_PAGEBITS;
182 for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) { 182 for (u64 page = addr >> YUZU_PAGEBITS; page <= page_end; ++page) {
183 if constexpr (RETURNS_BOOL) { 183 if constexpr (RETURNS_BOOL) {
184 if (func(page)) { 184 if (func(page)) {
185 break; 185 break;
@@ -193,8 +193,8 @@ private:
193 template <typename Func> 193 template <typename Func>
194 static void ForEachGPUPage(GPUVAddr addr, size_t size, Func&& func) { 194 static void ForEachGPUPage(GPUVAddr addr, size_t size, Func&& func) {
195 static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>; 195 static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>;
196 const u64 page_end = (addr + size - 1) >> PAGE_BITS; 196 const u64 page_end = (addr + size - 1) >> YUZU_PAGEBITS;
197 for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) { 197 for (u64 page = addr >> YUZU_PAGEBITS; page <= page_end; ++page) {
198 if constexpr (RETURNS_BOOL) { 198 if constexpr (RETURNS_BOOL) {
199 if (func(page)) { 199 if (func(page)) {
200 break; 200 break;
diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp
index 9b6b8527b..913f8ebcb 100644
--- a/src/video_core/textures/decoders.cpp
+++ b/src/video_core/textures/decoders.cpp
@@ -15,6 +15,24 @@
15 15
16namespace Tegra::Texture { 16namespace Tegra::Texture {
17namespace { 17namespace {
18template <u32 mask>
19constexpr u32 pdep(u32 value) {
20 u32 result = 0;
21 u32 m = mask;
22 for (u32 bit = 1; m; bit += bit) {
23 if (value & bit)
24 result |= m & -m;
25 m &= m - 1;
26 }
27 return result;
28}
29
30template <u32 mask, u32 incr_amount>
31void incrpdep(u32& value) {
32 constexpr u32 swizzled_incr = pdep<mask>(incr_amount);
33 value = ((value | ~mask) + swizzled_incr) & mask;
34}
35
18template <bool TO_LINEAR, u32 BYTES_PER_PIXEL> 36template <bool TO_LINEAR, u32 BYTES_PER_PIXEL>
19void SwizzleImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32 height, u32 depth, 37void SwizzleImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32 height, u32 depth,
20 u32 block_height, u32 block_depth, u32 stride_alignment) { 38 u32 block_height, u32 block_depth, u32 stride_alignment) {
@@ -44,18 +62,20 @@ void SwizzleImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32
44 ((z & block_depth_mask) << (GOB_SIZE_SHIFT + block_height)); 62 ((z & block_depth_mask) << (GOB_SIZE_SHIFT + block_height));
45 for (u32 line = 0; line < height; ++line) { 63 for (u32 line = 0; line < height; ++line) {
46 const u32 y = line + origin_y; 64 const u32 y = line + origin_y;
47 const auto& table = SWIZZLE_TABLE[y % GOB_SIZE_Y]; 65 const u32 swizzled_y = pdep<SWIZZLE_Y_BITS>(y);
48 66
49 const u32 block_y = y >> GOB_SIZE_Y_SHIFT; 67 const u32 block_y = y >> GOB_SIZE_Y_SHIFT;
50 const u32 offset_y = (block_y >> block_height) * block_size + 68 const u32 offset_y = (block_y >> block_height) * block_size +
51 ((block_y & block_height_mask) << GOB_SIZE_SHIFT); 69 ((block_y & block_height_mask) << GOB_SIZE_SHIFT);
52 70
53 for (u32 column = 0; column < width; ++column) { 71 u32 swizzled_x = pdep<SWIZZLE_X_BITS>(origin_x * BYTES_PER_PIXEL);
72 for (u32 column = 0; column < width;
73 ++column, incrpdep<SWIZZLE_X_BITS, BYTES_PER_PIXEL>(swizzled_x)) {
54 const u32 x = (column + origin_x) * BYTES_PER_PIXEL; 74 const u32 x = (column + origin_x) * BYTES_PER_PIXEL;
55 const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift; 75 const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift;
56 76
57 const u32 base_swizzled_offset = offset_z + offset_y + offset_x; 77 const u32 base_swizzled_offset = offset_z + offset_y + offset_x;
58 const u32 swizzled_offset = base_swizzled_offset + table[x % GOB_SIZE_X]; 78 const u32 swizzled_offset = base_swizzled_offset + (swizzled_x | swizzled_y);
59 79
60 const u32 unswizzled_offset = 80 const u32 unswizzled_offset =
61 slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL; 81 slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL;
@@ -103,12 +123,15 @@ void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32
103 const u32 gob_address_y = 123 const u32 gob_address_y =
104 (dst_y / (GOB_SIZE_Y * block_height)) * GOB_SIZE * block_height * image_width_in_gobs + 124 (dst_y / (GOB_SIZE_Y * block_height)) * GOB_SIZE * block_height * image_width_in_gobs +
105 ((dst_y % (GOB_SIZE_Y * block_height)) / GOB_SIZE_Y) * GOB_SIZE; 125 ((dst_y % (GOB_SIZE_Y * block_height)) / GOB_SIZE_Y) * GOB_SIZE;
106 const auto& table = SWIZZLE_TABLE[dst_y % GOB_SIZE_Y]; 126
107 for (u32 x = 0; x < subrect_width; ++x) { 127 const u32 swizzled_y = pdep<SWIZZLE_Y_BITS>(dst_y);
128 u32 swizzled_x = pdep<SWIZZLE_X_BITS>(offset_x * BYTES_PER_PIXEL);
129 for (u32 x = 0; x < subrect_width;
130 ++x, incrpdep<SWIZZLE_X_BITS, BYTES_PER_PIXEL>(swizzled_x)) {
108 const u32 dst_x = x + offset_x; 131 const u32 dst_x = x + offset_x;
109 const u32 gob_address = 132 const u32 gob_address =
110 gob_address_y + (dst_x * BYTES_PER_PIXEL / GOB_SIZE_X) * GOB_SIZE * block_height; 133 gob_address_y + (dst_x * BYTES_PER_PIXEL / GOB_SIZE_X) * GOB_SIZE * block_height;
111 const u32 swizzled_offset = gob_address + table[(dst_x * BYTES_PER_PIXEL) % GOB_SIZE_X]; 134 const u32 swizzled_offset = gob_address + (swizzled_x | swizzled_y);
112 const u32 unswizzled_offset = line * source_pitch + x * BYTES_PER_PIXEL; 135 const u32 unswizzled_offset = line * source_pitch + x * BYTES_PER_PIXEL;
113 136
114 const u8* const source_line = unswizzled_data + unswizzled_offset; 137 const u8* const source_line = unswizzled_data + unswizzled_offset;
@@ -130,16 +153,19 @@ void UnswizzleSubrect(u32 line_length_in, u32 line_count, u32 pitch, u32 width,
130 153
131 for (u32 line = 0; line < line_count; ++line) { 154 for (u32 line = 0; line < line_count; ++line) {
132 const u32 src_y = line + origin_y; 155 const u32 src_y = line + origin_y;
133 const auto& table = SWIZZLE_TABLE[src_y % GOB_SIZE_Y]; 156 const u32 swizzled_y = pdep<SWIZZLE_Y_BITS>(src_y);
134 157
135 const u32 block_y = src_y >> GOB_SIZE_Y_SHIFT; 158 const u32 block_y = src_y >> GOB_SIZE_Y_SHIFT;
136 const u32 src_offset_y = (block_y >> block_height) * block_size + 159 const u32 src_offset_y = (block_y >> block_height) * block_size +
137 ((block_y & block_height_mask) << GOB_SIZE_SHIFT); 160 ((block_y & block_height_mask) << GOB_SIZE_SHIFT);
138 for (u32 column = 0; column < line_length_in; ++column) { 161
162 u32 swizzled_x = pdep<SWIZZLE_X_BITS>(origin_x * BYTES_PER_PIXEL);
163 for (u32 column = 0; column < line_length_in;
164 ++column, incrpdep<SWIZZLE_X_BITS, BYTES_PER_PIXEL>(swizzled_x)) {
139 const u32 src_x = (column + origin_x) * BYTES_PER_PIXEL; 165 const u32 src_x = (column + origin_x) * BYTES_PER_PIXEL;
140 const u32 src_offset_x = (src_x >> GOB_SIZE_X_SHIFT) << x_shift; 166 const u32 src_offset_x = (src_x >> GOB_SIZE_X_SHIFT) << x_shift;
141 167
142 const u32 swizzled_offset = src_offset_y + src_offset_x + table[src_x % GOB_SIZE_X]; 168 const u32 swizzled_offset = src_offset_y + src_offset_x + (swizzled_x | swizzled_y);
143 const u32 unswizzled_offset = line * pitch + column * BYTES_PER_PIXEL; 169 const u32 unswizzled_offset = line * pitch + column * BYTES_PER_PIXEL;
144 170
145 std::memcpy(output + unswizzled_offset, input + swizzled_offset, BYTES_PER_PIXEL); 171 std::memcpy(output + unswizzled_offset, input + swizzled_offset, BYTES_PER_PIXEL);
@@ -162,13 +188,15 @@ void SwizzleSliceToVoxel(u32 line_length_in, u32 line_count, u32 pitch, u32 widt
162 const u32 x_shift = static_cast<u32>(GOB_SIZE_SHIFT) + block_height + block_depth; 188 const u32 x_shift = static_cast<u32>(GOB_SIZE_SHIFT) + block_height + block_depth;
163 189
164 for (u32 line = 0; line < line_count; ++line) { 190 for (u32 line = 0; line < line_count; ++line) {
165 const auto& table = SWIZZLE_TABLE[line % GOB_SIZE_Y]; 191 const u32 swizzled_y = pdep<SWIZZLE_Y_BITS>(line);
166 const u32 block_y = line / GOB_SIZE_Y; 192 const u32 block_y = line / GOB_SIZE_Y;
167 const u32 dst_offset_y = 193 const u32 dst_offset_y =
168 (block_y >> block_height) * block_size + (block_y & block_height_mask) * GOB_SIZE; 194 (block_y >> block_height) * block_size + (block_y & block_height_mask) * GOB_SIZE;
169 for (u32 x = 0; x < line_length_in; ++x) { 195
196 u32 swizzled_x = 0;
197 for (u32 x = 0; x < line_length_in; ++x, incrpdep<SWIZZLE_X_BITS, 1>(swizzled_x)) {
170 const u32 dst_offset = 198 const u32 dst_offset =
171 ((x / GOB_SIZE_X) << x_shift) + dst_offset_y + table[x % GOB_SIZE_X]; 199 ((x / GOB_SIZE_X) << x_shift) + dst_offset_y + (swizzled_x | swizzled_y);
172 const u32 src_offset = x * BYTES_PER_PIXEL + line * pitch; 200 const u32 src_offset = x * BYTES_PER_PIXEL + line * pitch;
173 std::memcpy(output + dst_offset, input + src_offset, BYTES_PER_PIXEL); 201 std::memcpy(output + dst_offset, input + src_offset, BYTES_PER_PIXEL);
174 } 202 }
@@ -267,11 +295,13 @@ void SwizzleKepler(const u32 width, const u32 height, const u32 dst_x, const u32
267 const std::size_t gob_address_y = 295 const std::size_t gob_address_y =
268 (y / (GOB_SIZE_Y * block_height)) * GOB_SIZE * block_height * image_width_in_gobs + 296 (y / (GOB_SIZE_Y * block_height)) * GOB_SIZE * block_height * image_width_in_gobs +
269 ((y % (GOB_SIZE_Y * block_height)) / GOB_SIZE_Y) * GOB_SIZE; 297 ((y % (GOB_SIZE_Y * block_height)) / GOB_SIZE_Y) * GOB_SIZE;
270 const auto& table = SWIZZLE_TABLE[y % GOB_SIZE_Y]; 298 const u32 swizzled_y = pdep<SWIZZLE_Y_BITS>(static_cast<u32>(y));
271 for (std::size_t x = dst_x; x < width && count < copy_size; ++x) { 299 u32 swizzled_x = pdep<SWIZZLE_X_BITS>(dst_x);
300 for (std::size_t x = dst_x; x < width && count < copy_size;
301 ++x, incrpdep<SWIZZLE_X_BITS, 1>(swizzled_x)) {
272 const std::size_t gob_address = 302 const std::size_t gob_address =
273 gob_address_y + (x / GOB_SIZE_X) * GOB_SIZE * block_height; 303 gob_address_y + (x / GOB_SIZE_X) * GOB_SIZE * block_height;
274 const std::size_t swizzled_offset = gob_address + table[x % GOB_SIZE_X]; 304 const std::size_t swizzled_offset = gob_address + (swizzled_x | swizzled_y);
275 const u8* source_line = source_data + count; 305 const u8* source_line = source_data + count;
276 u8* dest_addr = swizzle_data + swizzled_offset; 306 u8* dest_addr = swizzle_data + swizzled_offset;
277 count++; 307 count++;
diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h
index 59dfd1621..31a11708f 100644
--- a/src/video_core/textures/decoders.h
+++ b/src/video_core/textures/decoders.h
@@ -20,6 +20,9 @@ constexpr u32 GOB_SIZE_Y_SHIFT = 3;
20constexpr u32 GOB_SIZE_Z_SHIFT = 0; 20constexpr u32 GOB_SIZE_Z_SHIFT = 0;
21constexpr u32 GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHIFT; 21constexpr u32 GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHIFT;
22 22
23constexpr u32 SWIZZLE_X_BITS = 0b100101111;
24constexpr u32 SWIZZLE_Y_BITS = 0b011010000;
25
23using SwizzleTable = std::array<std::array<u32, GOB_SIZE_X>, GOB_SIZE_Y>; 26using SwizzleTable = std::array<std::array<u32, GOB_SIZE_X>, GOB_SIZE_Y>;
24 27
25/** 28/**
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 743ac09f6..ddecfca13 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -50,6 +50,21 @@ constexpr std::array R4G4_UNORM_PACK8{
50 VK_FORMAT_UNDEFINED, 50 VK_FORMAT_UNDEFINED,
51}; 51};
52 52
53constexpr std::array R16G16B16_SFLOAT{
54 VK_FORMAT_R16G16B16A16_SFLOAT,
55 VK_FORMAT_UNDEFINED,
56};
57
58constexpr std::array R16G16B16_SSCALED{
59 VK_FORMAT_R16G16B16A16_SSCALED,
60 VK_FORMAT_UNDEFINED,
61};
62
63constexpr std::array R8G8B8_SSCALED{
64 VK_FORMAT_R8G8B8A8_SSCALED,
65 VK_FORMAT_UNDEFINED,
66};
67
53} // namespace Alternatives 68} // namespace Alternatives
54 69
55enum class NvidiaArchitecture { 70enum class NvidiaArchitecture {
@@ -102,6 +117,12 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) {
102 return Alternatives::B5G6R5_UNORM_PACK16.data(); 117 return Alternatives::B5G6R5_UNORM_PACK16.data();
103 case VK_FORMAT_R4G4_UNORM_PACK8: 118 case VK_FORMAT_R4G4_UNORM_PACK8:
104 return Alternatives::R4G4_UNORM_PACK8.data(); 119 return Alternatives::R4G4_UNORM_PACK8.data();
120 case VK_FORMAT_R16G16B16_SFLOAT:
121 return Alternatives::R16G16B16_SFLOAT.data();
122 case VK_FORMAT_R16G16B16_SSCALED:
123 return Alternatives::R16G16B16_SSCALED.data();
124 case VK_FORMAT_R8G8B8_SSCALED:
125 return Alternatives::R8G8B8_SSCALED.data();
105 default: 126 default:
106 return nullptr; 127 return nullptr;
107 } 128 }
@@ -122,109 +143,142 @@ VkFormatFeatureFlags GetFormatFeatures(VkFormatProperties properties, FormatType
122 143
123std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::PhysicalDevice physical) { 144std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::PhysicalDevice physical) {
124 static constexpr std::array formats{ 145 static constexpr std::array formats{
125 VK_FORMAT_A8B8G8R8_UNORM_PACK32, 146 VK_FORMAT_A1R5G5B5_UNORM_PACK16,
126 VK_FORMAT_A8B8G8R8_UINT_PACK32, 147 VK_FORMAT_A2B10G10R10_SINT_PACK32,
127 VK_FORMAT_A8B8G8R8_SNORM_PACK32, 148 VK_FORMAT_A2B10G10R10_SNORM_PACK32,
149 VK_FORMAT_A2B10G10R10_SSCALED_PACK32,
150 VK_FORMAT_A2B10G10R10_UINT_PACK32,
151 VK_FORMAT_A2B10G10R10_UNORM_PACK32,
152 VK_FORMAT_A2B10G10R10_USCALED_PACK32,
128 VK_FORMAT_A8B8G8R8_SINT_PACK32, 153 VK_FORMAT_A8B8G8R8_SINT_PACK32,
154 VK_FORMAT_A8B8G8R8_SNORM_PACK32,
129 VK_FORMAT_A8B8G8R8_SRGB_PACK32, 155 VK_FORMAT_A8B8G8R8_SRGB_PACK32,
130 VK_FORMAT_R5G6B5_UNORM_PACK16, 156 VK_FORMAT_A8B8G8R8_UINT_PACK32,
131 VK_FORMAT_B5G6R5_UNORM_PACK16, 157 VK_FORMAT_A8B8G8R8_UNORM_PACK32,
132 VK_FORMAT_R5G5B5A1_UNORM_PACK16, 158 VK_FORMAT_ASTC_10x10_SRGB_BLOCK,
159 VK_FORMAT_ASTC_10x10_UNORM_BLOCK,
160 VK_FORMAT_ASTC_10x5_SRGB_BLOCK,
161 VK_FORMAT_ASTC_10x5_UNORM_BLOCK,
162 VK_FORMAT_ASTC_10x6_SRGB_BLOCK,
163 VK_FORMAT_ASTC_10x6_UNORM_BLOCK,
164 VK_FORMAT_ASTC_10x8_SRGB_BLOCK,
165 VK_FORMAT_ASTC_10x8_UNORM_BLOCK,
166 VK_FORMAT_ASTC_12x10_SRGB_BLOCK,
167 VK_FORMAT_ASTC_12x10_UNORM_BLOCK,
168 VK_FORMAT_ASTC_12x12_SRGB_BLOCK,
169 VK_FORMAT_ASTC_12x12_UNORM_BLOCK,
170 VK_FORMAT_ASTC_4x4_SRGB_BLOCK,
171 VK_FORMAT_ASTC_4x4_UNORM_BLOCK,
172 VK_FORMAT_ASTC_5x4_SRGB_BLOCK,
173 VK_FORMAT_ASTC_5x4_UNORM_BLOCK,
174 VK_FORMAT_ASTC_5x5_SRGB_BLOCK,
175 VK_FORMAT_ASTC_5x5_UNORM_BLOCK,
176 VK_FORMAT_ASTC_6x5_SRGB_BLOCK,
177 VK_FORMAT_ASTC_6x5_UNORM_BLOCK,
178 VK_FORMAT_ASTC_6x6_SRGB_BLOCK,
179 VK_FORMAT_ASTC_6x6_UNORM_BLOCK,
180 VK_FORMAT_ASTC_8x5_SRGB_BLOCK,
181 VK_FORMAT_ASTC_8x5_UNORM_BLOCK,
182 VK_FORMAT_ASTC_8x6_SRGB_BLOCK,
183 VK_FORMAT_ASTC_8x6_UNORM_BLOCK,
184 VK_FORMAT_ASTC_8x8_SRGB_BLOCK,
185 VK_FORMAT_ASTC_8x8_UNORM_BLOCK,
186 VK_FORMAT_B10G11R11_UFLOAT_PACK32,
187 VK_FORMAT_B4G4R4A4_UNORM_PACK16,
133 VK_FORMAT_B5G5R5A1_UNORM_PACK16, 188 VK_FORMAT_B5G5R5A1_UNORM_PACK16,
134 VK_FORMAT_A2B10G10R10_UNORM_PACK32, 189 VK_FORMAT_B5G6R5_UNORM_PACK16,
135 VK_FORMAT_A2B10G10R10_UINT_PACK32, 190 VK_FORMAT_B8G8R8A8_SRGB,
136 VK_FORMAT_A1R5G5B5_UNORM_PACK16, 191 VK_FORMAT_B8G8R8A8_UNORM,
137 VK_FORMAT_R32G32B32A32_SFLOAT, 192 VK_FORMAT_BC1_RGBA_SRGB_BLOCK,
138 VK_FORMAT_R32G32B32A32_SINT, 193 VK_FORMAT_BC1_RGBA_UNORM_BLOCK,
139 VK_FORMAT_R32G32B32A32_UINT, 194 VK_FORMAT_BC2_SRGB_BLOCK,
140 VK_FORMAT_R32G32_SFLOAT, 195 VK_FORMAT_BC2_UNORM_BLOCK,
141 VK_FORMAT_R32G32_SINT, 196 VK_FORMAT_BC3_SRGB_BLOCK,
142 VK_FORMAT_R32G32_UINT, 197 VK_FORMAT_BC3_UNORM_BLOCK,
198 VK_FORMAT_BC4_SNORM_BLOCK,
199 VK_FORMAT_BC4_UNORM_BLOCK,
200 VK_FORMAT_BC5_SNORM_BLOCK,
201 VK_FORMAT_BC5_UNORM_BLOCK,
202 VK_FORMAT_BC6H_SFLOAT_BLOCK,
203 VK_FORMAT_BC6H_UFLOAT_BLOCK,
204 VK_FORMAT_BC7_SRGB_BLOCK,
205 VK_FORMAT_BC7_UNORM_BLOCK,
206 VK_FORMAT_D16_UNORM,
207 VK_FORMAT_D16_UNORM_S8_UINT,
208 VK_FORMAT_D24_UNORM_S8_UINT,
209 VK_FORMAT_D32_SFLOAT,
210 VK_FORMAT_D32_SFLOAT_S8_UINT,
211 VK_FORMAT_E5B9G9R9_UFLOAT_PACK32,
212 VK_FORMAT_R16G16B16A16_SFLOAT,
143 VK_FORMAT_R16G16B16A16_SINT, 213 VK_FORMAT_R16G16B16A16_SINT,
144 VK_FORMAT_R16G16B16A16_UINT,
145 VK_FORMAT_R16G16B16A16_SNORM, 214 VK_FORMAT_R16G16B16A16_SNORM,
215 VK_FORMAT_R16G16B16A16_SSCALED,
216 VK_FORMAT_R16G16B16A16_UINT,
146 VK_FORMAT_R16G16B16A16_UNORM, 217 VK_FORMAT_R16G16B16A16_UNORM,
147 VK_FORMAT_R16G16_UNORM, 218 VK_FORMAT_R16G16B16A16_USCALED,
148 VK_FORMAT_R16G16_SNORM, 219 VK_FORMAT_R16G16B16_SFLOAT,
220 VK_FORMAT_R16G16B16_SINT,
221 VK_FORMAT_R16G16B16_SNORM,
222 VK_FORMAT_R16G16B16_SSCALED,
223 VK_FORMAT_R16G16B16_UINT,
224 VK_FORMAT_R16G16B16_UNORM,
225 VK_FORMAT_R16G16B16_USCALED,
149 VK_FORMAT_R16G16_SFLOAT, 226 VK_FORMAT_R16G16_SFLOAT,
150 VK_FORMAT_R16G16_UINT,
151 VK_FORMAT_R16G16_SINT, 227 VK_FORMAT_R16G16_SINT,
152 VK_FORMAT_R16_UNORM, 228 VK_FORMAT_R16G16_SNORM,
229 VK_FORMAT_R16G16_SSCALED,
230 VK_FORMAT_R16G16_UINT,
231 VK_FORMAT_R16G16_UNORM,
232 VK_FORMAT_R16G16_USCALED,
233 VK_FORMAT_R16_SFLOAT,
234 VK_FORMAT_R16_SINT,
153 VK_FORMAT_R16_SNORM, 235 VK_FORMAT_R16_SNORM,
236 VK_FORMAT_R16_SSCALED,
154 VK_FORMAT_R16_UINT, 237 VK_FORMAT_R16_UINT,
238 VK_FORMAT_R16_UNORM,
239 VK_FORMAT_R16_USCALED,
240 VK_FORMAT_R32G32B32A32_SFLOAT,
241 VK_FORMAT_R32G32B32A32_SINT,
242 VK_FORMAT_R32G32B32A32_UINT,
243 VK_FORMAT_R32G32B32_SFLOAT,
244 VK_FORMAT_R32G32B32_SINT,
245 VK_FORMAT_R32G32B32_UINT,
246 VK_FORMAT_R32G32_SFLOAT,
247 VK_FORMAT_R32G32_SINT,
248 VK_FORMAT_R32G32_UINT,
249 VK_FORMAT_R32_SFLOAT,
250 VK_FORMAT_R32_SINT,
251 VK_FORMAT_R32_UINT,
252 VK_FORMAT_R4G4B4A4_UNORM_PACK16,
253 VK_FORMAT_R4G4_UNORM_PACK8,
254 VK_FORMAT_R5G5B5A1_UNORM_PACK16,
255 VK_FORMAT_R5G6B5_UNORM_PACK16,
256 VK_FORMAT_R8G8B8A8_SINT,
257 VK_FORMAT_R8G8B8A8_SNORM,
155 VK_FORMAT_R8G8B8A8_SRGB, 258 VK_FORMAT_R8G8B8A8_SRGB,
156 VK_FORMAT_R8G8_UNORM, 259 VK_FORMAT_R8G8B8A8_SSCALED,
157 VK_FORMAT_R8G8_SNORM, 260 VK_FORMAT_R8G8B8A8_UINT,
261 VK_FORMAT_R8G8B8A8_UNORM,
262 VK_FORMAT_R8G8B8A8_USCALED,
263 VK_FORMAT_R8G8B8_SINT,
264 VK_FORMAT_R8G8B8_SNORM,
265 VK_FORMAT_R8G8B8_SSCALED,
266 VK_FORMAT_R8G8B8_UINT,
267 VK_FORMAT_R8G8B8_UNORM,
268 VK_FORMAT_R8G8B8_USCALED,
158 VK_FORMAT_R8G8_SINT, 269 VK_FORMAT_R8G8_SINT,
270 VK_FORMAT_R8G8_SNORM,
271 VK_FORMAT_R8G8_SSCALED,
159 VK_FORMAT_R8G8_UINT, 272 VK_FORMAT_R8G8_UINT,
160 VK_FORMAT_R8_UNORM, 273 VK_FORMAT_R8G8_UNORM,
161 VK_FORMAT_R8_SNORM, 274 VK_FORMAT_R8G8_USCALED,
162 VK_FORMAT_R8_SINT, 275 VK_FORMAT_R8_SINT,
276 VK_FORMAT_R8_SNORM,
277 VK_FORMAT_R8_SSCALED,
163 VK_FORMAT_R8_UINT, 278 VK_FORMAT_R8_UINT,
164 VK_FORMAT_B10G11R11_UFLOAT_PACK32, 279 VK_FORMAT_R8_UNORM,
165 VK_FORMAT_R32_SFLOAT, 280 VK_FORMAT_R8_USCALED,
166 VK_FORMAT_R32_UINT,
167 VK_FORMAT_R32_SINT,
168 VK_FORMAT_R16_SFLOAT,
169 VK_FORMAT_R16G16B16A16_SFLOAT,
170 VK_FORMAT_B8G8R8A8_UNORM,
171 VK_FORMAT_B8G8R8A8_SRGB,
172 VK_FORMAT_R4G4_UNORM_PACK8,
173 VK_FORMAT_R4G4B4A4_UNORM_PACK16,
174 VK_FORMAT_B4G4R4A4_UNORM_PACK16,
175 VK_FORMAT_D32_SFLOAT,
176 VK_FORMAT_D16_UNORM,
177 VK_FORMAT_S8_UINT, 281 VK_FORMAT_S8_UINT,
178 VK_FORMAT_D16_UNORM_S8_UINT,
179 VK_FORMAT_D24_UNORM_S8_UINT,
180 VK_FORMAT_D32_SFLOAT_S8_UINT,
181 VK_FORMAT_BC1_RGBA_UNORM_BLOCK,
182 VK_FORMAT_BC2_UNORM_BLOCK,
183 VK_FORMAT_BC3_UNORM_BLOCK,
184 VK_FORMAT_BC4_UNORM_BLOCK,
185 VK_FORMAT_BC4_SNORM_BLOCK,
186 VK_FORMAT_BC5_UNORM_BLOCK,
187 VK_FORMAT_BC5_SNORM_BLOCK,
188 VK_FORMAT_BC7_UNORM_BLOCK,
189 VK_FORMAT_BC6H_UFLOAT_BLOCK,
190 VK_FORMAT_BC6H_SFLOAT_BLOCK,
191 VK_FORMAT_BC1_RGBA_SRGB_BLOCK,
192 VK_FORMAT_BC2_SRGB_BLOCK,
193 VK_FORMAT_BC3_SRGB_BLOCK,
194 VK_FORMAT_BC7_SRGB_BLOCK,
195 VK_FORMAT_ASTC_4x4_UNORM_BLOCK,
196 VK_FORMAT_ASTC_4x4_SRGB_BLOCK,
197 VK_FORMAT_ASTC_5x4_UNORM_BLOCK,
198 VK_FORMAT_ASTC_5x4_SRGB_BLOCK,
199 VK_FORMAT_ASTC_5x5_UNORM_BLOCK,
200 VK_FORMAT_ASTC_5x5_SRGB_BLOCK,
201 VK_FORMAT_ASTC_6x5_UNORM_BLOCK,
202 VK_FORMAT_ASTC_6x5_SRGB_BLOCK,
203 VK_FORMAT_ASTC_6x6_UNORM_BLOCK,
204 VK_FORMAT_ASTC_6x6_SRGB_BLOCK,
205 VK_FORMAT_ASTC_8x5_UNORM_BLOCK,
206 VK_FORMAT_ASTC_8x5_SRGB_BLOCK,
207 VK_FORMAT_ASTC_8x6_UNORM_BLOCK,
208 VK_FORMAT_ASTC_8x6_SRGB_BLOCK,
209 VK_FORMAT_ASTC_8x8_UNORM_BLOCK,
210 VK_FORMAT_ASTC_8x8_SRGB_BLOCK,
211 VK_FORMAT_ASTC_10x5_UNORM_BLOCK,
212 VK_FORMAT_ASTC_10x5_SRGB_BLOCK,
213 VK_FORMAT_ASTC_10x6_UNORM_BLOCK,
214 VK_FORMAT_ASTC_10x6_SRGB_BLOCK,
215 VK_FORMAT_ASTC_10x8_UNORM_BLOCK,
216 VK_FORMAT_ASTC_10x8_SRGB_BLOCK,
217 VK_FORMAT_ASTC_10x10_UNORM_BLOCK,
218 VK_FORMAT_ASTC_10x10_SRGB_BLOCK,
219 VK_FORMAT_ASTC_12x10_UNORM_BLOCK,
220 VK_FORMAT_ASTC_12x10_SRGB_BLOCK,
221 VK_FORMAT_ASTC_12x12_UNORM_BLOCK,
222 VK_FORMAT_ASTC_12x12_SRGB_BLOCK,
223 VK_FORMAT_ASTC_8x6_UNORM_BLOCK,
224 VK_FORMAT_ASTC_8x6_SRGB_BLOCK,
225 VK_FORMAT_ASTC_6x5_UNORM_BLOCK,
226 VK_FORMAT_ASTC_6x5_SRGB_BLOCK,
227 VK_FORMAT_E5B9G9R9_UFLOAT_PACK32,
228 }; 282 };
229 std::unordered_map<VkFormat, VkFormatProperties> format_properties; 283 std::unordered_map<VkFormat, VkFormatProperties> format_properties;
230 for (const auto format : formats) { 284 for (const auto format : formats) {
@@ -739,9 +793,9 @@ VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags
739 if (!IsFormatSupported(alternative, wanted_usage, format_type)) { 793 if (!IsFormatSupported(alternative, wanted_usage, format_type)) {
740 continue; 794 continue;
741 } 795 }
742 LOG_WARNING(Render_Vulkan, 796 LOG_DEBUG(Render_Vulkan,
743 "Emulating format={} with alternative format={} with usage={} and type={}", 797 "Emulating format={} with alternative format={} with usage={} and type={}",
744 wanted_format, alternative, wanted_usage, format_type); 798 wanted_format, alternative, wanted_usage, format_type);
745 return alternative; 799 return alternative;
746 } 800 }
747 801
diff --git a/src/web_service/verify_user_jwt.cpp b/src/web_service/verify_user_jwt.cpp
index 3bff46f0a..129eb1968 100644
--- a/src/web_service/verify_user_jwt.cpp
+++ b/src/web_service/verify_user_jwt.cpp
@@ -39,8 +39,10 @@ Network::VerifyUser::UserData VerifyUserJWT::LoadUserData(const std::string& ver
39 const std::string audience = fmt::format("external-{}", verify_uid); 39 const std::string audience = fmt::format("external-{}", verify_uid);
40 using namespace jwt::params; 40 using namespace jwt::params;
41 std::error_code error; 41 std::error_code error;
42
43 // We use the Citra backend so the issuer is citra-core
42 auto decoded = 44 auto decoded =
43 jwt::decode(token, algorithms({"rs256"}), error, secret(pub_key), issuer("yuzu-core"), 45 jwt::decode(token, algorithms({"rs256"}), error, secret(pub_key), issuer("citra-core"),
44 aud(audience), validate_iat(true), validate_jti(true)); 46 aud(audience), validate_iat(true), validate_jti(true));
45 if (error) { 47 if (error) {
46 LOG_INFO(WebService, "Verification failed: category={}, code={}, message={}", 48 LOG_INFO(WebService, "Verification failed: category={}, code={}, message={}",
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index f6b389ede..50007338f 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -221,6 +221,9 @@ if (ENABLE_QT_TRANSLATION)
221 # Update source TS file if enabled 221 # Update source TS file if enabled
222 if (GENERATE_QT_TRANSLATION) 222 if (GENERATE_QT_TRANSLATION)
223 get_target_property(SRCS yuzu SOURCES) 223 get_target_property(SRCS yuzu SOURCES)
224 # these calls to qt_create_translation also creates a rule to generate en.qm which conflicts with providing english plurals
225 # so we have to set a OUTPUT_LOCATION so that we don't have multiple rules to generate en.qm
226 set_source_files_properties(${YUZU_QT_LANGUAGES}/en.ts PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/translations")
224 qt_create_translation(QM_FILES 227 qt_create_translation(QM_FILES
225 ${SRCS} 228 ${SRCS}
226 ${UIS} 229 ${UIS}
@@ -229,7 +232,13 @@ if (ENABLE_QT_TRANSLATION)
229 -source-language en_US 232 -source-language en_US
230 -target-language en_US 233 -target-language en_US
231 ) 234 )
232 add_custom_target(translation ALL DEPENDS ${YUZU_QT_LANGUAGES}/en.ts) 235
236 # Generate plurals into dist/english_plurals/generated_en.ts so it can be used to revise dist/english_plurals/en.ts
237 set(GENERATED_PLURALS_FILE ${PROJECT_SOURCE_DIR}/dist/english_plurals/generated_en.ts)
238 set_source_files_properties(${GENERATED_PLURALS_FILE} PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/plurals")
239 qt_create_translation(QM_FILES ${SRCS} ${UIS} ${GENERATED_PLURALS_FILE} OPTIONS -pluralonly -source-language en_US -target-language en_US)
240
241 add_custom_target(translation ALL DEPENDS ${YUZU_QT_LANGUAGES}/en.ts ${GENERATED_PLURALS_FILE})
233 endif() 242 endif()
234 243
235 # Find all TS files except en.ts 244 # Find all TS files except en.ts
@@ -239,6 +248,9 @@ if (ENABLE_QT_TRANSLATION)
239 # Compile TS files to QM files 248 # Compile TS files to QM files
240 qt_add_translation(LANGUAGES_QM ${LANGUAGES_TS}) 249 qt_add_translation(LANGUAGES_QM ${LANGUAGES_TS})
241 250
251 # Compile english plurals TS file to en.qm
252 qt_add_translation(LANGUAGES_QM ${PROJECT_SOURCE_DIR}/dist/english_plurals/en.ts)
253
242 # Build a QRC file from the QM file list 254 # Build a QRC file from the QM file list
243 set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc) 255 set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc)
244 file(WRITE ${LANGUAGES_QRC} "<RCC><qresource prefix=\"languages\">\n") 256 file(WRITE ${LANGUAGES_QRC} "<RCC><qresource prefix=\"languages\">\n")
diff --git a/src/yuzu/aboutdialog.ui b/src/yuzu/aboutdialog.ui
index c4ffb293e..aea82809d 100644
--- a/src/yuzu/aboutdialog.ui
+++ b/src/yuzu/aboutdialog.ui
@@ -7,7 +7,7 @@
7 <x>0</x> 7 <x>0</x>
8 <y>0</y> 8 <y>0</y>
9 <width>616</width> 9 <width>616</width>
10 <height>261</height> 10 <height>294</height>
11 </rect> 11 </rect>
12 </property> 12 </property>
13 <property name="windowTitle"> 13 <property name="windowTitle">
@@ -165,6 +165,7 @@ p, li { white-space: pre-wrap; }
165 </widget> 165 </widget>
166 <resources> 166 <resources>
167 <include location="../../dist/qt_themes_default/default/default.qrc"/> 167 <include location="../../dist/qt_themes_default/default/default.qrc"/>
168 <include location="../../dist/qt_themes/default/default.qrc"/>
168 </resources> 169 </resources>
169 <connections> 170 <connections>
170 <connection> 171 <connection>
diff --git a/src/yuzu/applets/qt_profile_select.cpp b/src/yuzu/applets/qt_profile_select.cpp
index 826c6c224..c8bcfb223 100644
--- a/src/yuzu/applets/qt_profile_select.cpp
+++ b/src/yuzu/applets/qt_profile_select.cpp
@@ -100,6 +100,7 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(Core::HID::HIDCore& hid_core,
100 } 100 }
101 QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier); 101 QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
102 QCoreApplication::postEvent(tree_view, event); 102 QCoreApplication::postEvent(tree_view, event);
103 SelectUser(tree_view->currentIndex());
103 }); 104 });
104 105
105 const auto& profiles = profile_manager->GetAllUsers(); 106 const auto& profiles = profile_manager->GetAllUsers();
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index ef3bdfb1a..d3fbdb09d 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -815,6 +815,12 @@ void GRenderWindow::InitializeCamera() {
815 if (Settings::values.ir_sensor_device.GetValue() == cameraInfo.deviceName().toStdString() || 815 if (Settings::values.ir_sensor_device.GetValue() == cameraInfo.deviceName().toStdString() ||
816 Settings::values.ir_sensor_device.GetValue() == "Auto") { 816 Settings::values.ir_sensor_device.GetValue() == "Auto") {
817 camera = std::make_unique<QCamera>(cameraInfo); 817 camera = std::make_unique<QCamera>(cameraInfo);
818 if (!camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureViewfinder) &&
819 !camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureStillImage)) {
820 LOG_ERROR(Frontend,
821 "Camera doesn't support CaptureViewfinder or CaptureStillImage");
822 continue;
823 }
818 camera_found = true; 824 camera_found = true;
819 break; 825 break;
820 } 826 }
@@ -825,10 +831,22 @@ void GRenderWindow::InitializeCamera() {
825 } 831 }
826 832
827 camera_capture = std::make_unique<QCameraImageCapture>(camera.get()); 833 camera_capture = std::make_unique<QCameraImageCapture>(camera.get());
834
835 if (!camera_capture->isCaptureDestinationSupported(
836 QCameraImageCapture::CaptureDestination::CaptureToBuffer)) {
837 LOG_ERROR(Frontend, "Camera doesn't support saving to buffer");
838 return;
839 }
840
841 camera_capture->setCaptureDestination(QCameraImageCapture::CaptureDestination::CaptureToBuffer);
828 connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this, 842 connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this,
829 &GRenderWindow::OnCameraCapture); 843 &GRenderWindow::OnCameraCapture);
830 camera->unload(); 844 camera->unload();
831 camera->setCaptureMode(QCamera::CaptureViewfinder); 845 if (camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureViewfinder)) {
846 camera->setCaptureMode(QCamera::CaptureViewfinder);
847 } else if (camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureStillImage)) {
848 camera->setCaptureMode(QCamera::CaptureStillImage);
849 }
832 camera->load(); 850 camera->load();
833 camera->start(); 851 camera->start();
834 852
@@ -1089,8 +1107,8 @@ QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
1089 } 1107 }
1090 1108
1091 if (!unsupported_ext.empty()) { 1109 if (!unsupported_ext.empty()) {
1092 LOG_ERROR(Frontend, "GPU does not support all required extensions: {}", 1110 const std::string gl_renderer{reinterpret_cast<const char*>(glGetString(GL_RENDERER))};
1093 glGetString(GL_RENDERER)); 1111 LOG_ERROR(Frontend, "GPU does not support all required extensions: {}", gl_renderer);
1094 } 1112 }
1095 for (const QString& ext : unsupported_ext) { 1113 for (const QString& ext : unsupported_ext) {
1096 LOG_ERROR(Frontend, "Unsupported GL extension: {}", ext.toStdString()); 1114 LOG_ERROR(Frontend, "Unsupported GL extension: {}", ext.toStdString());
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 58f1239bf..8ecd87150 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -73,7 +73,7 @@ const std::array<int, 2> Config::default_ringcon_analogs{{
73const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{ 73const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{
74 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+M"), QStringLiteral("Home+Dpad_Right"), Qt::WindowShortcut}}, 74 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+M"), QStringLiteral("Home+Dpad_Right"), Qt::WindowShortcut}},
75 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("-"), QStringLiteral("Home+Dpad_Down"), Qt::ApplicationShortcut}}, 75 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("-"), QStringLiteral("Home+Dpad_Down"), Qt::ApplicationShortcut}},
76 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("+"), QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut}}, 76 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("="), QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut}},
77 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Capture Screenshot")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+P"), QStringLiteral("Screenshot"), Qt::WidgetWithChildrenShortcut}}, 77 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Capture Screenshot")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+P"), QStringLiteral("Screenshot"), Qt::WidgetWithChildrenShortcut}},
78 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Adapting Filter")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F8"), QStringLiteral("Home+L"), Qt::ApplicationShortcut}}, 78 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Adapting Filter")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F8"), QStringLiteral("Home+L"), Qt::ApplicationShortcut}},
79 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Docked Mode")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F10"), QStringLiteral("Home+X"), Qt::ApplicationShortcut}}, 79 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Docked Mode")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F10"), QStringLiteral("Home+X"), Qt::ApplicationShortcut}},
@@ -684,6 +684,7 @@ void Config::ReadRendererValues() {
684 ReadGlobalSetting(Settings::values.shader_backend); 684 ReadGlobalSetting(Settings::values.shader_backend);
685 ReadGlobalSetting(Settings::values.use_asynchronous_shaders); 685 ReadGlobalSetting(Settings::values.use_asynchronous_shaders);
686 ReadGlobalSetting(Settings::values.use_fast_gpu_time); 686 ReadGlobalSetting(Settings::values.use_fast_gpu_time);
687 ReadGlobalSetting(Settings::values.use_pessimistic_flushes);
687 ReadGlobalSetting(Settings::values.bg_red); 688 ReadGlobalSetting(Settings::values.bg_red);
688 ReadGlobalSetting(Settings::values.bg_green); 689 ReadGlobalSetting(Settings::values.bg_green);
689 ReadGlobalSetting(Settings::values.bg_blue); 690 ReadGlobalSetting(Settings::values.bg_blue);
@@ -1300,6 +1301,7 @@ void Config::SaveRendererValues() {
1300 Settings::values.shader_backend.UsingGlobal()); 1301 Settings::values.shader_backend.UsingGlobal());
1301 WriteGlobalSetting(Settings::values.use_asynchronous_shaders); 1302 WriteGlobalSetting(Settings::values.use_asynchronous_shaders);
1302 WriteGlobalSetting(Settings::values.use_fast_gpu_time); 1303 WriteGlobalSetting(Settings::values.use_fast_gpu_time);
1304 WriteGlobalSetting(Settings::values.use_pessimistic_flushes);
1303 WriteGlobalSetting(Settings::values.bg_red); 1305 WriteGlobalSetting(Settings::values.bg_red);
1304 WriteGlobalSetting(Settings::values.bg_green); 1306 WriteGlobalSetting(Settings::values.bg_green);
1305 WriteGlobalSetting(Settings::values.bg_blue); 1307 WriteGlobalSetting(Settings::values.bg_blue);
diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui
index a5bcee415..6034d8581 100644
--- a/src/yuzu/configuration/configure_audio.ui
+++ b/src/yuzu/configuration/configure_audio.ui
@@ -120,10 +120,10 @@
120 </sizepolicy> 120 </sizepolicy>
121 </property> 121 </property>
122 <property name="maximum"> 122 <property name="maximum">
123 <number>100</number> 123 <number>200</number>
124 </property> 124 </property>
125 <property name="pageStep"> 125 <property name="pageStep">
126 <number>10</number> 126 <number>5</number>
127 </property> 127 </property>
128 <property name="orientation"> 128 <property name="orientation">
129 <enum>Qt::Horizontal</enum> 129 <enum>Qt::Horizontal</enum>
diff --git a/src/yuzu/configuration/configure_camera.cpp b/src/yuzu/configuration/configure_camera.cpp
index 73cdcf3f2..2a61de2a1 100644
--- a/src/yuzu/configuration/configure_camera.cpp
+++ b/src/yuzu/configuration/configure_camera.cpp
@@ -42,6 +42,12 @@ void ConfigureCamera::PreviewCamera() {
42 LOG_INFO(Frontend, "Selected Camera {} {}", cameraInfo.description().toStdString(), 42 LOG_INFO(Frontend, "Selected Camera {} {}", cameraInfo.description().toStdString(),
43 cameraInfo.deviceName().toStdString()); 43 cameraInfo.deviceName().toStdString());
44 camera = std::make_unique<QCamera>(cameraInfo); 44 camera = std::make_unique<QCamera>(cameraInfo);
45 if (!camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureViewfinder) &&
46 !camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureStillImage)) {
47 LOG_ERROR(Frontend,
48 "Camera doesn't support CaptureViewfinder or CaptureStillImage");
49 continue;
50 }
45 camera_found = true; 51 camera_found = true;
46 break; 52 break;
47 } 53 }
@@ -57,10 +63,22 @@ void ConfigureCamera::PreviewCamera() {
57 } 63 }
58 64
59 camera_capture = std::make_unique<QCameraImageCapture>(camera.get()); 65 camera_capture = std::make_unique<QCameraImageCapture>(camera.get());
66
67 if (!camera_capture->isCaptureDestinationSupported(
68 QCameraImageCapture::CaptureDestination::CaptureToBuffer)) {
69 LOG_ERROR(Frontend, "Camera doesn't support saving to buffer");
70 return;
71 }
72
73 camera_capture->setCaptureDestination(QCameraImageCapture::CaptureDestination::CaptureToBuffer);
60 connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this, 74 connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this,
61 &ConfigureCamera::DisplayCapturedFrame); 75 &ConfigureCamera::DisplayCapturedFrame);
62 camera->unload(); 76 camera->unload();
63 camera->setCaptureMode(QCamera::CaptureViewfinder); 77 if (camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureViewfinder)) {
78 camera->setCaptureMode(QCamera::CaptureViewfinder);
79 } else if (camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureStillImage)) {
80 camera->setCaptureMode(QCamera::CaptureStillImage);
81 }
64 camera->load(); 82 camera->load();
65 camera->start(); 83 camera->start();
66 84
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index e16d127a8..04d397750 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -14,7 +14,7 @@
14#include "yuzu/uisettings.h" 14#include "yuzu/uisettings.h"
15 15
16ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent) 16ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent)
17 : QWidget(parent), ui{std::make_unique<Ui::ConfigureDebug>()}, system{system_} { 17 : QScrollArea(parent), ui{std::make_unique<Ui::ConfigureDebug>()}, system{system_} {
18 ui->setupUi(this); 18 ui->setupUi(this);
19 SetConfiguration(); 19 SetConfiguration();
20 20
diff --git a/src/yuzu/configuration/configure_debug.h b/src/yuzu/configuration/configure_debug.h
index 64d68ab8f..42d30f170 100644
--- a/src/yuzu/configuration/configure_debug.h
+++ b/src/yuzu/configuration/configure_debug.h
@@ -4,7 +4,7 @@
4#pragma once 4#pragma once
5 5
6#include <memory> 6#include <memory>
7#include <QWidget> 7#include <QScrollArea>
8 8
9namespace Core { 9namespace Core {
10class System; 10class System;
@@ -14,7 +14,7 @@ namespace Ui {
14class ConfigureDebug; 14class ConfigureDebug;
15} 15}
16 16
17class ConfigureDebug : public QWidget { 17class ConfigureDebug : public QScrollArea {
18 Q_OBJECT 18 Q_OBJECT
19 19
20public: 20public:
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 4c16274fc..47b8b80f1 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -1,7 +1,11 @@
1<?xml version="1.0" encoding="UTF-8"?> 1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0"> 2<ui version="4.0">
3 <class>ConfigureDebug</class> 3 <class>ConfigureDebug</class>
4 <widget class="QWidget" name="ConfigureDebug"> 4 <widget class="QScrollArea" name="ConfigureDebug">
5 <property name="widgetResizable">
6 <bool>true</bool>
7 </property>
8 <widget class="QWidget">
5 <layout class="QVBoxLayout" name="verticalLayout_1"> 9 <layout class="QVBoxLayout" name="verticalLayout_1">
6 <item> 10 <item>
7 <layout class="QVBoxLayout" name="verticalLayout_2"> 11 <layout class="QVBoxLayout" name="verticalLayout_2">
@@ -322,6 +326,7 @@
322 </item> 326 </item>
323 </layout> 327 </layout>
324 </widget> 328 </widget>
329 </widget>
325 <tabstops> 330 <tabstops>
326 <tabstop>log_filter_edit</tabstop> 331 <tabstop>log_filter_edit</tabstop>
327 <tabstop>toggle_console</tabstop> 332 <tabstop>toggle_console</tabstop>
diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp
index 7c3196c83..01f074699 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.cpp
+++ b/src/yuzu/configuration/configure_graphics_advanced.cpp
@@ -28,6 +28,7 @@ void ConfigureGraphicsAdvanced::SetConfiguration() {
28 ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue()); 28 ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue());
29 ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue()); 29 ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue());
30 ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue()); 30 ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue());
31 ui->use_pessimistic_flushes->setChecked(Settings::values.use_pessimistic_flushes.GetValue());
31 32
32 if (Settings::IsConfiguringGlobal()) { 33 if (Settings::IsConfiguringGlobal()) {
33 ui->gpu_accuracy->setCurrentIndex( 34 ui->gpu_accuracy->setCurrentIndex(
@@ -55,6 +56,8 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() {
55 use_asynchronous_shaders); 56 use_asynchronous_shaders);
56 ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time, 57 ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time,
57 ui->use_fast_gpu_time, use_fast_gpu_time); 58 ui->use_fast_gpu_time, use_fast_gpu_time);
59 ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_pessimistic_flushes,
60 ui->use_pessimistic_flushes, use_pessimistic_flushes);
58} 61}
59 62
60void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) { 63void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) {
@@ -77,6 +80,8 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
77 ui->use_asynchronous_shaders->setEnabled( 80 ui->use_asynchronous_shaders->setEnabled(
78 Settings::values.use_asynchronous_shaders.UsingGlobal()); 81 Settings::values.use_asynchronous_shaders.UsingGlobal());
79 ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal()); 82 ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal());
83 ui->use_pessimistic_flushes->setEnabled(
84 Settings::values.use_pessimistic_flushes.UsingGlobal());
80 ui->anisotropic_filtering_combobox->setEnabled( 85 ui->anisotropic_filtering_combobox->setEnabled(
81 Settings::values.max_anisotropy.UsingGlobal()); 86 Settings::values.max_anisotropy.UsingGlobal());
82 87
@@ -89,6 +94,9 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
89 use_asynchronous_shaders); 94 use_asynchronous_shaders);
90 ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time, 95 ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time,
91 Settings::values.use_fast_gpu_time, use_fast_gpu_time); 96 Settings::values.use_fast_gpu_time, use_fast_gpu_time);
97 ConfigurationShared::SetColoredTristate(ui->use_pessimistic_flushes,
98 Settings::values.use_pessimistic_flushes,
99 use_pessimistic_flushes);
92 ConfigurationShared::SetColoredComboBox( 100 ConfigurationShared::SetColoredComboBox(
93 ui->gpu_accuracy, ui->label_gpu_accuracy, 101 ui->gpu_accuracy, ui->label_gpu_accuracy,
94 static_cast<int>(Settings::values.gpu_accuracy.GetValue(true))); 102 static_cast<int>(Settings::values.gpu_accuracy.GetValue(true)));
diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h
index 1ef7bd916..12e816905 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.h
+++ b/src/yuzu/configuration/configure_graphics_advanced.h
@@ -39,6 +39,7 @@ private:
39 ConfigurationShared::CheckState use_vsync; 39 ConfigurationShared::CheckState use_vsync;
40 ConfigurationShared::CheckState use_asynchronous_shaders; 40 ConfigurationShared::CheckState use_asynchronous_shaders;
41 ConfigurationShared::CheckState use_fast_gpu_time; 41 ConfigurationShared::CheckState use_fast_gpu_time;
42 ConfigurationShared::CheckState use_pessimistic_flushes;
42 43
43 const Core::System& system; 44 const Core::System& system;
44}; 45};
diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui
index 96de0b3d1..87a121471 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.ui
+++ b/src/yuzu/configuration/configure_graphics_advanced.ui
@@ -75,7 +75,7 @@
75 <string>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference.</string> 75 <string>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference.</string>
76 </property> 76 </property>
77 <property name="text"> 77 <property name="text">
78 <string>Use VSync (OpenGL only)</string> 78 <string>Use VSync</string>
79 </property> 79 </property>
80 </widget> 80 </widget>
81 </item> 81 </item>
@@ -100,6 +100,16 @@
100 </widget> 100 </widget>
101 </item> 101 </item>
102 <item> 102 <item>
103 <widget class="QCheckBox" name="use_pessimistic_flushes">
104 <property name="toolTip">
105 <string>Enables pessimistic buffer flushes. This option will force unmodified buffers to be flushed, which can cost performance.</string>
106 </property>
107 <property name="text">
108 <string>Use pessimistic buffer flushes (Hack)</string>
109 </property>
110 </widget>
111 </item>
112 <item>
103 <widget class="QWidget" name="af_layout" native="true"> 113 <widget class="QWidget" name="af_layout" native="true">
104 <layout class="QHBoxLayout" name="horizontalLayout_1"> 114 <layout class="QHBoxLayout" name="horizontalLayout_1">
105 <property name="leftMargin"> 115 <property name="leftMargin">
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 00bee85b2..9e5a40fe7 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -161,6 +161,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) {
161 161
162 const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : ""); 162 const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : "");
163 const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : ""); 163 const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : "");
164 const QString invert = QString::fromStdString(param.Get("invert", "+") == "-" ? "-" : "");
164 const auto common_button_name = input_subsystem->GetButtonName(param); 165 const auto common_button_name = input_subsystem->GetButtonName(param);
165 166
166 // Retrieve the names from Qt 167 // Retrieve the names from Qt
@@ -184,7 +185,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) {
184 } 185 }
185 if (param.Has("axis")) { 186 if (param.Has("axis")) {
186 const QString axis = QString::fromStdString(param.Get("axis", "")); 187 const QString axis = QString::fromStdString(param.Get("axis", ""));
187 return QObject::tr("%1%2Axis %3").arg(toggle, inverted, axis); 188 return QObject::tr("%1%2Axis %3").arg(toggle, invert, axis);
188 } 189 }
189 if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) { 190 if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) {
190 const QString axis_x = QString::fromStdString(param.Get("axis_x", "")); 191 const QString axis_x = QString::fromStdString(param.Get("axis_x", ""));
@@ -362,18 +363,18 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
362 button_map[button_id]->setText(tr("[not set]")); 363 button_map[button_id]->setText(tr("[not set]"));
363 }); 364 });
364 if (param.Has("code") || param.Has("button") || param.Has("hat")) { 365 if (param.Has("code") || param.Has("button") || param.Has("hat")) {
365 context_menu.addAction(tr("Toggle button"), [&] {
366 const bool toggle_value = !param.Get("toggle", false);
367 param.Set("toggle", toggle_value);
368 button_map[button_id]->setText(ButtonToText(param));
369 emulated_controller->SetButtonParam(button_id, param);
370 });
371 context_menu.addAction(tr("Invert button"), [&] { 366 context_menu.addAction(tr("Invert button"), [&] {
372 const bool invert_value = !param.Get("inverted", false); 367 const bool invert_value = !param.Get("inverted", false);
373 param.Set("inverted", invert_value); 368 param.Set("inverted", invert_value);
374 button_map[button_id]->setText(ButtonToText(param)); 369 button_map[button_id]->setText(ButtonToText(param));
375 emulated_controller->SetButtonParam(button_id, param); 370 emulated_controller->SetButtonParam(button_id, param);
376 }); 371 });
372 context_menu.addAction(tr("Toggle button"), [&] {
373 const bool toggle_value = !param.Get("toggle", false);
374 param.Set("toggle", toggle_value);
375 button_map[button_id]->setText(ButtonToText(param));
376 emulated_controller->SetButtonParam(button_id, param);
377 });
377 } 378 }
378 if (param.Has("axis")) { 379 if (param.Has("axis")) {
379 context_menu.addAction(tr("Invert axis"), [&] { 380 context_menu.addAction(tr("Invert axis"), [&] {
@@ -398,6 +399,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
398 } 399 }
399 emulated_controller->SetButtonParam(button_id, param); 400 emulated_controller->SetButtonParam(button_id, param);
400 }); 401 });
402 context_menu.addAction(tr("Toggle axis"), [&] {
403 const bool toggle_value = !param.Get("toggle", false);
404 param.Set("toggle", toggle_value);
405 button_map[button_id]->setText(ButtonToText(param));
406 emulated_controller->SetButtonParam(button_id, param);
407 });
401 } 408 }
402 context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); 409 context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
403 }); 410 });
@@ -1410,7 +1417,7 @@ void ConfigureInputPlayer::HandleClick(
1410 ui->controllerFrame->BeginMappingAnalog(button_id); 1417 ui->controllerFrame->BeginMappingAnalog(button_id);
1411 } 1418 }
1412 1419
1413 timeout_timer->start(2500); // Cancel after 2.5 seconds 1420 timeout_timer->start(4000); // Cancel after 4 seconds
1414 poll_timer->start(25); // Check for new inputs every 25ms 1421 poll_timer->start(25); // Check for new inputs every 25ms
1415} 1422}
1416 1423
@@ -1475,7 +1482,7 @@ void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
1475 1482
1476void ConfigureInputPlayer::CreateProfile() { 1483void ConfigureInputPlayer::CreateProfile() {
1477 const auto profile_name = 1484 const auto profile_name =
1478 LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 20, 1485 LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 30,
1479 LimitableInputDialog::InputLimiter::Filesystem); 1486 LimitableInputDialog::InputLimiter::Filesystem);
1480 1487
1481 if (profile_name.isEmpty()) { 1488 if (profile_name.isEmpty()) {
diff --git a/src/yuzu/configuration/configure_tas.ui b/src/yuzu/configuration/configure_tas.ui
index cf88a5bf0..625af0c89 100644
--- a/src/yuzu/configuration/configure_tas.ui
+++ b/src/yuzu/configuration/configure_tas.ui
@@ -16,6 +16,9 @@
16 <property name="text"> 16 <property name="text">
17 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Reads controller input from scripts in the same format as TAS-nx scripts.&lt;br/&gt;For a more detailed explanation, please consult the &lt;a href=&quot;https://yuzu-emu.org/help/feature/tas/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;help page&lt;/span&gt;&lt;/a&gt; on the yuzu website.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 17 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Reads controller input from scripts in the same format as TAS-nx scripts.&lt;br/&gt;For a more detailed explanation, please consult the &lt;a href=&quot;https://yuzu-emu.org/help/feature/tas/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;help page&lt;/span&gt;&lt;/a&gt; on the yuzu website.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
18 </property> 18 </property>
19 <property name="openExternalLinks">
20 <bool>true</bool>
21 </property>
19 </widget> 22 </widget>
20 </item> 23 </item>
21 <item row="1" column="0" colspan="4"> 24 <item row="1" column="0" colspan="4">
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index 2e98ede8e..48f71b53c 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -219,6 +219,7 @@ void ConfigureUi::InitializeLanguageComboBox() {
219 for (const auto& lang : languages) { 219 for (const auto& lang : languages) {
220 if (QString::fromLatin1(lang.id) == QStringLiteral("en")) { 220 if (QString::fromLatin1(lang.id) == QStringLiteral("en")) {
221 ui->language_combobox->addItem(lang.name, QStringLiteral("en")); 221 ui->language_combobox->addItem(lang.name, QStringLiteral("en"));
222 language_files.removeOne(QStringLiteral("en.qm"));
222 continue; 223 continue;
223 } 224 }
224 for (int i = 0; i < language_files.size(); ++i) { 225 for (int i = 0; i < language_files.size(); ++i) {
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 041e6ac11..b127badc2 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -126,10 +126,8 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {
126 layout_filter = new QHBoxLayout; 126 layout_filter = new QHBoxLayout;
127 layout_filter->setContentsMargins(8, 8, 8, 8); 127 layout_filter->setContentsMargins(8, 8, 8, 8);
128 label_filter = new QLabel; 128 label_filter = new QLabel;
129 label_filter->setText(tr("Filter:"));
130 edit_filter = new QLineEdit; 129 edit_filter = new QLineEdit;
131 edit_filter->clear(); 130 edit_filter->clear();
132 edit_filter->setPlaceholderText(tr("Enter pattern to filter"));
133 edit_filter->installEventFilter(key_release_eater); 131 edit_filter->installEventFilter(key_release_eater);
134 edit_filter->setClearButtonEnabled(true); 132 edit_filter->setClearButtonEnabled(true);
135 connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::OnTextChanged); 133 connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::OnTextChanged);
@@ -149,6 +147,7 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {
149 layout_filter->addWidget(label_filter_result); 147 layout_filter->addWidget(label_filter_result);
150 layout_filter->addWidget(button_filter_close); 148 layout_filter->addWidget(button_filter_close);
151 setLayout(layout_filter); 149 setLayout(layout_filter);
150 RetranslateUI();
152} 151}
153 152
154/** 153/**
@@ -286,7 +285,7 @@ void GameList::OnUpdateThemedIcons() {
286 } 285 }
287 case GameListItemType::AddDir: 286 case GameListItemType::AddDir:
288 child->setData( 287 child->setData(
289 QIcon::fromTheme(QStringLiteral("plus")) 288 QIcon::fromTheme(QStringLiteral("list-add"))
290 .pixmap(icon_size) 289 .pixmap(icon_size)
291 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), 290 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
292 Qt::DecorationRole); 291 Qt::DecorationRole);
@@ -333,13 +332,9 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid
333 tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }")); 332 tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }"));
334 333
335 item_model->insertColumns(0, COLUMN_COUNT); 334 item_model->insertColumns(0, COLUMN_COUNT);
336 item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name")); 335 RetranslateUI();
337 item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, tr("Compatibility"));
338 336
339 item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons"));
340 tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons); 337 tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons);
341 item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type"));
342 item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size"));
343 item_model->setSortRole(GameListItemPath::SortRole); 338 item_model->setSortRole(GameListItemPath::SortRole);
344 339
345 connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons); 340 connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
@@ -753,6 +748,35 @@ void GameList::LoadCompatibilityList() {
753 } 748 }
754} 749}
755 750
751void GameList::changeEvent(QEvent* event) {
752 if (event->type() == QEvent::LanguageChange) {
753 RetranslateUI();
754 }
755
756 QWidget::changeEvent(event);
757}
758
759void GameList::RetranslateUI() {
760 item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name"));
761 item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, tr("Compatibility"));
762 item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons"));
763 item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type"));
764 item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size"));
765}
766
767void GameListSearchField::changeEvent(QEvent* event) {
768 if (event->type() == QEvent::LanguageChange) {
769 RetranslateUI();
770 }
771
772 QWidget::changeEvent(event);
773}
774
775void GameListSearchField::RetranslateUI() {
776 label_filter->setText(tr("Filter:"));
777 edit_filter->setPlaceholderText(tr("Enter pattern to filter"));
778}
779
756QStandardItemModel* GameList::GetModel() const { 780QStandardItemModel* GameList::GetModel() const {
757 return item_model; 781 return item_model;
758} 782}
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index f783283c9..cdf085019 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -140,6 +140,9 @@ private:
140 void AddPermDirPopup(QMenu& context_menu, QModelIndex selected); 140 void AddPermDirPopup(QMenu& context_menu, QModelIndex selected);
141 void AddFavoritesPopup(QMenu& context_menu); 141 void AddFavoritesPopup(QMenu& context_menu);
142 142
143 void changeEvent(QEvent*) override;
144 void RetranslateUI();
145
143 std::shared_ptr<FileSys::VfsFilesystem> vfs; 146 std::shared_ptr<FileSys::VfsFilesystem> vfs;
144 FileSys::ManualContentProvider* provider; 147 FileSys::ManualContentProvider* provider;
145 GameListSearchField* search_field; 148 GameListSearchField* search_field;
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index e7667cf60..6198d1e4e 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -294,7 +294,7 @@ public:
294 294
295 const int icon_size = UISettings::values.folder_icon_size.GetValue(); 295 const int icon_size = UISettings::values.folder_icon_size.GetValue();
296 296
297 setData(QIcon::fromTheme(QStringLiteral("plus")) 297 setData(QIcon::fromTheme(QStringLiteral("list-add"))
298 .pixmap(icon_size) 298 .pixmap(icon_size)
299 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), 299 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
300 Qt::DecorationRole); 300 Qt::DecorationRole);
@@ -353,6 +353,9 @@ public:
353 void setFocus(); 353 void setFocus();
354 354
355private: 355private:
356 void changeEvent(QEvent*) override;
357 void RetranslateUI();
358
356 class KeyReleaseEater : public QObject { 359 class KeyReleaseEater : public QObject {
357 public: 360 public:
358 explicit KeyReleaseEater(GameList* gamelist_, QObject* parent = nullptr); 361 explicit KeyReleaseEater(GameList* gamelist_, QObject* parent = nullptr);
diff --git a/src/yuzu/loading_screen.cpp b/src/yuzu/loading_screen.cpp
index e273744fd..e263a07a7 100644
--- a/src/yuzu/loading_screen.cpp
+++ b/src/yuzu/loading_screen.cpp
@@ -147,6 +147,10 @@ void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size
147 ui->progress_bar->setMaximum(static_cast<int>(total)); 147 ui->progress_bar->setMaximum(static_cast<int>(total));
148 previous_total = total; 148 previous_total = total;
149 } 149 }
150 // Reset the progress bar ranges if compilation is done
151 if (stage == VideoCore::LoadCallbackStage::Complete) {
152 ui->progress_bar->setRange(0, 0);
153 }
150 154
151 QString estimate; 155 QString estimate;
152 // If theres a drastic slowdown in the rate, then display an estimate 156 // If theres a drastic slowdown in the rate, then display an estimate
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index f8c234082..a85adc072 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -257,6 +257,18 @@ static QString PrettyProductName() {
257 return QSysInfo::prettyProductName(); 257 return QSysInfo::prettyProductName();
258} 258}
259 259
260bool GMainWindow::CheckDarkMode() {
261#ifdef __linux__
262 const QPalette test_palette(qApp->palette());
263 const QColor text_color = test_palette.color(QPalette::Active, QPalette::Text);
264 const QColor window_color = test_palette.color(QPalette::Active, QPalette::Window);
265 return (text_color.value() > window_color.value());
266#else
267 // TODO: Windows
268 return false;
269#endif // __linux__
270}
271
260GMainWindow::GMainWindow(bool has_broken_vulkan) 272GMainWindow::GMainWindow(bool has_broken_vulkan)
261 : ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()}, 273 : ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()},
262 input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, 274 input_subsystem{std::make_shared<InputCommon::InputSubsystem>()},
@@ -274,6 +286,13 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
274 ui->setupUi(this); 286 ui->setupUi(this);
275 statusBar()->hide(); 287 statusBar()->hide();
276 288
289 // Check dark mode before a theme is loaded
290 os_dark_mode = CheckDarkMode();
291 startup_icon_theme = QIcon::themeName();
292 // fallback can only be set once, colorful theme icons are okay on both light/dark
293 QIcon::setFallbackThemeName(QStringLiteral("colorful"));
294 QIcon::setFallbackSearchPaths(QStringList(QStringLiteral(":/icons")));
295
277 default_theme_paths = QIcon::themeSearchPaths(); 296 default_theme_paths = QIcon::themeSearchPaths();
278 UpdateUITheme(); 297 UpdateUITheme();
279 298
@@ -473,8 +492,6 @@ GMainWindow::~GMainWindow() {
473 delete render_window; 492 delete render_window;
474 } 493 }
475 494
476 system->GetRoomNetwork().Shutdown();
477
478#ifdef __linux__ 495#ifdef __linux__
479 ::close(sig_interrupt_fds[0]); 496 ::close(sig_interrupt_fds[0]);
480 ::close(sig_interrupt_fds[1]); 497 ::close(sig_interrupt_fds[1]);
@@ -843,7 +860,7 @@ void GMainWindow::InitializeWidgets() {
843 }); 860 });
844 861
845 multiplayer_state = new MultiplayerState(this, game_list->GetModel(), ui->action_Leave_Room, 862 multiplayer_state = new MultiplayerState(this, game_list->GetModel(), ui->action_Leave_Room,
846 ui->action_Show_Room, system->GetRoomNetwork()); 863 ui->action_Show_Room, *system);
847 multiplayer_state->setVisible(false); 864 multiplayer_state->setVisible(false);
848 865
849 // Create status bar 866 // Create status bar
@@ -1075,7 +1092,7 @@ void GMainWindow::InitializeHotkeys() {
1075 connect_shortcut(QStringLiteral("Audio Mute/Unmute"), 1092 connect_shortcut(QStringLiteral("Audio Mute/Unmute"),
1076 [] { Settings::values.audio_muted = !Settings::values.audio_muted; }); 1093 [] { Settings::values.audio_muted = !Settings::values.audio_muted; });
1077 connect_shortcut(QStringLiteral("Audio Volume Down"), [] { 1094 connect_shortcut(QStringLiteral("Audio Volume Down"), [] {
1078 const auto current_volume = static_cast<int>(Settings::values.volume.GetValue()); 1095 const auto current_volume = static_cast<s32>(Settings::values.volume.GetValue());
1079 int step = 5; 1096 int step = 5;
1080 if (current_volume <= 30) { 1097 if (current_volume <= 30) {
1081 step = 2; 1098 step = 2;
@@ -1083,11 +1100,10 @@ void GMainWindow::InitializeHotkeys() {
1083 if (current_volume <= 6) { 1100 if (current_volume <= 6) {
1084 step = 1; 1101 step = 1;
1085 } 1102 }
1086 const auto new_volume = std::max(current_volume - step, 0); 1103 Settings::values.volume.SetValue(std::max(current_volume - step, 0));
1087 Settings::values.volume.SetValue(static_cast<u8>(new_volume));
1088 }); 1104 });
1089 connect_shortcut(QStringLiteral("Audio Volume Up"), [] { 1105 connect_shortcut(QStringLiteral("Audio Volume Up"), [] {
1090 const auto current_volume = static_cast<int>(Settings::values.volume.GetValue()); 1106 const auto current_volume = static_cast<s32>(Settings::values.volume.GetValue());
1091 int step = 5; 1107 int step = 5;
1092 if (current_volume < 30) { 1108 if (current_volume < 30) {
1093 step = 2; 1109 step = 2;
@@ -1095,8 +1111,7 @@ void GMainWindow::InitializeHotkeys() {
1095 if (current_volume < 6) { 1111 if (current_volume < 6) {
1096 step = 1; 1112 step = 1;
1097 } 1113 }
1098 const auto new_volume = std::min(current_volume + step, 100); 1114 Settings::values.volume.SetValue(current_volume + step);
1099 Settings::values.volume.SetValue(static_cast<u8>(new_volume));
1100 }); 1115 });
1101 connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] { 1116 connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] {
1102 Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue()); 1117 Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue());
@@ -1588,17 +1603,18 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p
1588 return true; 1603 return true;
1589} 1604}
1590 1605
1591void GMainWindow::SelectAndSetCurrentUser() { 1606bool GMainWindow::SelectAndSetCurrentUser() {
1592 QtProfileSelectionDialog dialog(system->HIDCore(), this); 1607 QtProfileSelectionDialog dialog(system->HIDCore(), this);
1593 dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | 1608 dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
1594 Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); 1609 Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
1595 dialog.setWindowModality(Qt::WindowModal); 1610 dialog.setWindowModality(Qt::WindowModal);
1596 1611
1597 if (dialog.exec() == QDialog::Rejected) { 1612 if (dialog.exec() == QDialog::Rejected) {
1598 return; 1613 return false;
1599 } 1614 }
1600 1615
1601 Settings::values.current_user = dialog.GetIndex(); 1616 Settings::values.current_user = dialog.GetIndex();
1617 return true;
1602} 1618}
1603 1619
1604void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t program_index, 1620void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t program_index,
@@ -1632,11 +1648,14 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
1632 Settings::LogSettings(); 1648 Settings::LogSettings();
1633 1649
1634 if (UISettings::values.select_user_on_boot) { 1650 if (UISettings::values.select_user_on_boot) {
1635 SelectAndSetCurrentUser(); 1651 if (SelectAndSetCurrentUser() == false) {
1652 return;
1653 }
1636 } 1654 }
1637 1655
1638 if (!LoadROM(filename, program_id, program_index)) 1656 if (!LoadROM(filename, program_id, program_index)) {
1639 return; 1657 return;
1658 }
1640 1659
1641 system->SetShuttingDown(false); 1660 system->SetShuttingDown(false);
1642 1661
@@ -3334,7 +3353,8 @@ void GMainWindow::MigrateConfigFiles() {
3334 } 3353 }
3335 const auto origin = config_dir_fs_path / filename; 3354 const auto origin = config_dir_fs_path / filename;
3336 const auto destination = config_dir_fs_path / "custom" / filename; 3355 const auto destination = config_dir_fs_path / "custom" / filename;
3337 LOG_INFO(Frontend, "Migrating config file from {} to {}", origin, destination); 3356 LOG_INFO(Frontend, "Migrating config file from {} to {}", origin.string(),
3357 destination.string());
3338 if (!Common::FS::RenameFile(origin, destination)) { 3358 if (!Common::FS::RenameFile(origin, destination)) {
3339 // Delete the old config file if one already exists in the new location. 3359 // Delete the old config file if one already exists in the new location.
3340 Common::FS::RemoveFile(origin); 3360 Common::FS::RemoveFile(origin);
@@ -3809,6 +3829,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
3809 3829
3810 render_window->close(); 3830 render_window->close();
3811 multiplayer_state->Close(); 3831 multiplayer_state->Close();
3832 system->GetRoomNetwork().Shutdown();
3812 3833
3813 QWidget::closeEvent(event); 3834 QWidget::closeEvent(event);
3814} 3835}
@@ -3930,8 +3951,21 @@ void GMainWindow::filterBarSetChecked(bool state) {
3930 emit(OnToggleFilterBar()); 3951 emit(OnToggleFilterBar());
3931} 3952}
3932 3953
3954static void AdjustLinkColor() {
3955 QPalette new_pal(qApp->palette());
3956 if (UISettings::IsDarkTheme()) {
3957 new_pal.setColor(QPalette::Link, QColor(0, 190, 255, 255));
3958 } else {
3959 new_pal.setColor(QPalette::Link, QColor(0, 140, 200, 255));
3960 }
3961 if (qApp->palette().color(QPalette::Link) != new_pal.color(QPalette::Link)) {
3962 qApp->setPalette(new_pal);
3963 }
3964}
3965
3933void GMainWindow::UpdateUITheme() { 3966void GMainWindow::UpdateUITheme() {
3934 const QString default_theme = QStringLiteral("default"); 3967 const QString default_theme =
3968 QString::fromUtf8(UISettings::themes[static_cast<size_t>(Config::default_theme)].second);
3935 QString current_theme = UISettings::values.theme; 3969 QString current_theme = UISettings::values.theme;
3936 QStringList theme_paths(default_theme_paths); 3970 QStringList theme_paths(default_theme_paths);
3937 3971
@@ -3939,6 +3973,23 @@ void GMainWindow::UpdateUITheme() {
3939 current_theme = default_theme; 3973 current_theme = default_theme;
3940 } 3974 }
3941 3975
3976#ifdef _WIN32
3977 QIcon::setThemeName(current_theme);
3978 AdjustLinkColor();
3979#else
3980 if (current_theme == QStringLiteral("default") || current_theme == QStringLiteral("colorful")) {
3981 QIcon::setThemeName(current_theme == QStringLiteral("colorful") ? current_theme
3982 : startup_icon_theme);
3983 QIcon::setThemeSearchPaths(theme_paths);
3984 if (CheckDarkMode()) {
3985 current_theme = QStringLiteral("default_dark");
3986 }
3987 } else {
3988 QIcon::setThemeName(current_theme);
3989 QIcon::setThemeSearchPaths(QStringList(QStringLiteral(":/icons")));
3990 AdjustLinkColor();
3991 }
3992#endif
3942 if (current_theme != default_theme) { 3993 if (current_theme != default_theme) {
3943 QString theme_uri{QStringLiteral(":%1/style.qss").arg(current_theme)}; 3994 QString theme_uri{QStringLiteral(":%1/style.qss").arg(current_theme)};
3944 QFile f(theme_uri); 3995 QFile f(theme_uri);
@@ -3961,25 +4012,9 @@ void GMainWindow::UpdateUITheme() {
3961 qApp->setStyleSheet({}); 4012 qApp->setStyleSheet({});
3962 setStyleSheet({}); 4013 setStyleSheet({});
3963 } 4014 }
3964
3965 QPalette new_pal(qApp->palette());
3966 if (UISettings::IsDarkTheme()) {
3967 new_pal.setColor(QPalette::Link, QColor(0, 190, 255, 255));
3968 } else {
3969 new_pal.setColor(QPalette::Link, QColor(0, 140, 200, 255));
3970 }
3971 qApp->setPalette(new_pal);
3972
3973 QIcon::setThemeName(current_theme);
3974 QIcon::setThemeSearchPaths(theme_paths);
3975} 4015}
3976 4016
3977void GMainWindow::LoadTranslation() { 4017void GMainWindow::LoadTranslation() {
3978 // If the selected language is English, no need to install any translation
3979 if (UISettings::values.language == QStringLiteral("en")) {
3980 return;
3981 }
3982
3983 bool loaded; 4018 bool loaded;
3984 4019
3985 if (UISettings::values.language.isEmpty()) { 4020 if (UISettings::values.language.isEmpty()) {
@@ -4022,6 +4057,26 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
4022 discord_rpc->Update(); 4057 discord_rpc->Update();
4023} 4058}
4024 4059
4060void GMainWindow::changeEvent(QEvent* event) {
4061#ifdef __linux__
4062 // PaletteChange event appears to only reach so far into the GUI, explicitly asking to
4063 // UpdateUITheme is a decent work around
4064 if (event->type() == QEvent::PaletteChange) {
4065 const QPalette test_palette(qApp->palette());
4066 const QString current_theme = UISettings::values.theme;
4067 // Keeping eye on QPalette::Window to avoid looping. QPalette::Text might be useful too
4068 static QColor last_window_color;
4069 const QColor window_color = test_palette.color(QPalette::Active, QPalette::Window);
4070 if (last_window_color != window_color && (current_theme == QStringLiteral("default") ||
4071 current_theme == QStringLiteral("colorful"))) {
4072 UpdateUITheme();
4073 }
4074 last_window_color = window_color;
4075 }
4076#endif // __linux__
4077 QWidget::changeEvent(event);
4078}
4079
4025#ifdef main 4080#ifdef main
4026#undef main 4081#undef main
4027#endif 4082#endif
@@ -4067,6 +4122,15 @@ int main(int argc, char* argv[]) {
4067 QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); 4122 QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
4068 QApplication app(argc, argv); 4123 QApplication app(argc, argv);
4069 4124
4125 // Workaround for QTBUG-85409, for Suzhou numerals the number 1 is actually \u3021
4126 // so we can see if we get \u3008 instead
4127 // TL;DR all other number formats are consecutive in unicode code points
4128 // This bug is fixed in Qt6, specifically 6.0.0-alpha1
4129 const QLocale locale = QLocale::system();
4130 if (QStringLiteral("\u3008") == locale.toString(1)) {
4131 QLocale::setDefault(QLocale::system().name());
4132 }
4133
4070 // Qt changes the locale and causes issues in float conversion using std::to_string() when 4134 // Qt changes the locale and causes issues in float conversion using std::to_string() when
4071 // generating shaders 4135 // generating shaders
4072 setlocale(LC_ALL, "C"); 4136 setlocale(LC_ALL, "C");
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 23b67a14e..1ae2b93d9 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -218,7 +218,7 @@ private:
218 void SetDiscordEnabled(bool state); 218 void SetDiscordEnabled(bool state);
219 void LoadAmiibo(const QString& filename); 219 void LoadAmiibo(const QString& filename);
220 220
221 void SelectAndSetCurrentUser(); 221 bool SelectAndSetCurrentUser();
222 222
223 /** 223 /**
224 * Stores the filename in the recently loaded files list. 224 * Stores the filename in the recently loaded files list.
@@ -251,6 +251,7 @@ private:
251 bool ConfirmForceLockedExit(); 251 bool ConfirmForceLockedExit();
252 void RequestGameExit(); 252 void RequestGameExit();
253 void RequestGameResume(); 253 void RequestGameResume();
254 void changeEvent(QEvent* event) override;
254 void closeEvent(QCloseEvent* event) override; 255 void closeEvent(QCloseEvent* event) override;
255 256
256#ifdef __linux__ 257#ifdef __linux__
@@ -347,6 +348,7 @@ private:
347 void OpenURL(const QUrl& url); 348 void OpenURL(const QUrl& url);
348 void LoadTranslation(); 349 void LoadTranslation();
349 void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); 350 void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
351 bool CheckDarkMode();
350 352
351 QString GetTasStateDescription() const; 353 QString GetTasStateDescription() const;
352 354
@@ -392,6 +394,9 @@ private:
392 QTimer mouse_hide_timer; 394 QTimer mouse_hide_timer;
393 QTimer mouse_center_timer; 395 QTimer mouse_center_timer;
394 396
397 QString startup_icon_theme;
398 bool os_dark_mode = false;
399
395 // FS 400 // FS
396 std::shared_ptr<FileSys::VfsFilesystem> vfs; 401 std::shared_ptr<FileSys::VfsFilesystem> vfs;
397 std::unique_ptr<FileSys::ManualContentProvider> provider; 402 std::unique_ptr<FileSys::ManualContentProvider> provider;
diff --git a/src/yuzu/multiplayer/chat_room.cpp b/src/yuzu/multiplayer/chat_room.cpp
index 5837b36ab..9e672f82e 100644
--- a/src/yuzu/multiplayer/chat_room.cpp
+++ b/src/yuzu/multiplayer/chat_room.cpp
@@ -16,7 +16,7 @@
16#include <QUrl> 16#include <QUrl>
17#include <QtConcurrent/QtConcurrentRun> 17#include <QtConcurrent/QtConcurrentRun>
18#include "common/logging/log.h" 18#include "common/logging/log.h"
19#include "core/announce_multiplayer_session.h" 19#include "network/announce_multiplayer_session.h"
20#include "ui_chat_room.h" 20#include "ui_chat_room.h"
21#include "yuzu/game_list_p.h" 21#include "yuzu/game_list_p.h"
22#include "yuzu/multiplayer/chat_room.h" 22#include "yuzu/multiplayer/chat_room.h"
@@ -122,19 +122,22 @@ public:
122 static const int UsernameRole = Qt::UserRole + 2; 122 static const int UsernameRole = Qt::UserRole + 2;
123 static const int AvatarUrlRole = Qt::UserRole + 3; 123 static const int AvatarUrlRole = Qt::UserRole + 3;
124 static const int GameNameRole = Qt::UserRole + 4; 124 static const int GameNameRole = Qt::UserRole + 4;
125 static const int GameVersionRole = Qt::UserRole + 5;
125 126
126 PlayerListItem() = default; 127 PlayerListItem() = default;
127 explicit PlayerListItem(const std::string& nickname, const std::string& username, 128 explicit PlayerListItem(const std::string& nickname, const std::string& username,
128 const std::string& avatar_url, const std::string& game_name) { 129 const std::string& avatar_url,
130 const AnnounceMultiplayerRoom::GameInfo& game_info) {
129 setEditable(false); 131 setEditable(false);
130 setData(QString::fromStdString(nickname), NicknameRole); 132 setData(QString::fromStdString(nickname), NicknameRole);
131 setData(QString::fromStdString(username), UsernameRole); 133 setData(QString::fromStdString(username), UsernameRole);
132 setData(QString::fromStdString(avatar_url), AvatarUrlRole); 134 setData(QString::fromStdString(avatar_url), AvatarUrlRole);
133 if (game_name.empty()) { 135 if (game_info.name.empty()) {
134 setData(QObject::tr("Not playing a game"), GameNameRole); 136 setData(QObject::tr("Not playing a game"), GameNameRole);
135 } else { 137 } else {
136 setData(QString::fromStdString(game_name), GameNameRole); 138 setData(QString::fromStdString(game_info.name), GameNameRole);
137 } 139 }
140 setData(QString::fromStdString(game_info.version), GameVersionRole);
138 } 141 }
139 142
140 QVariant data(int role) const override { 143 QVariant data(int role) const override {
@@ -149,7 +152,13 @@ public:
149 } else { 152 } else {
150 name = QStringLiteral("%1 (%2)").arg(nickname, username); 153 name = QStringLiteral("%1 (%2)").arg(nickname, username);
151 } 154 }
152 return QStringLiteral("%1\n %2").arg(name, data(GameNameRole).toString()); 155 const QString version = data(GameVersionRole).toString();
156 QString version_string;
157 if (!version.isEmpty()) {
158 version_string = QStringLiteral("(%1)").arg(version);
159 }
160 return QStringLiteral("%1\n %2 %3")
161 .arg(name, data(GameNameRole).toString(), version_string);
153 } 162 }
154}; 163};
155 164
@@ -167,6 +176,10 @@ ChatRoom::ChatRoom(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::C
167 176
168 ui->chat_history->document()->setMaximumBlockCount(max_chat_lines); 177 ui->chat_history->document()->setMaximumBlockCount(max_chat_lines);
169 178
179 auto font = ui->chat_history->font();
180 font.setPointSizeF(10);
181 ui->chat_history->setFont(font);
182
170 // register the network structs to use in slots and signals 183 // register the network structs to use in slots and signals
171 qRegisterMetaType<Network::ChatEntry>(); 184 qRegisterMetaType<Network::ChatEntry>();
172 qRegisterMetaType<Network::StatusMessageEntry>(); 185 qRegisterMetaType<Network::StatusMessageEntry>();
@@ -316,21 +329,19 @@ void ChatRoom::OnStatusMessageReceive(const Network::StatusMessageEntry& status_
316} 329}
317 330
318void ChatRoom::OnSendChat() { 331void ChatRoom::OnSendChat() {
319 if (auto room = room_network->GetRoomMember().lock()) { 332 if (auto room_member = room_network->GetRoomMember().lock()) {
320 if (room->GetState() != Network::RoomMember::State::Joined && 333 if (!room_member->IsConnected()) {
321 room->GetState() != Network::RoomMember::State::Moderator) {
322
323 return; 334 return;
324 } 335 }
325 auto message = ui->chat_message->text().toStdString(); 336 auto message = ui->chat_message->text().toStdString();
326 if (!ValidateMessage(message)) { 337 if (!ValidateMessage(message)) {
327 return; 338 return;
328 } 339 }
329 auto nick = room->GetNickname(); 340 auto nick = room_member->GetNickname();
330 auto username = room->GetUsername(); 341 auto username = room_member->GetUsername();
331 Network::ChatEntry chat{nick, username, message}; 342 Network::ChatEntry chat{nick, username, message};
332 343
333 auto members = room->GetMemberInformation(); 344 auto members = room_member->GetMemberInformation();
334 auto it = std::find_if(members.begin(), members.end(), 345 auto it = std::find_if(members.begin(), members.end(),
335 [&chat](const Network::RoomMember::MemberInformation& member) { 346 [&chat](const Network::RoomMember::MemberInformation& member) {
336 return member.nickname == chat.nickname && 347 return member.nickname == chat.nickname &&
@@ -341,7 +352,7 @@ void ChatRoom::OnSendChat() {
341 } 352 }
342 auto player = std::distance(members.begin(), it); 353 auto player = std::distance(members.begin(), it);
343 ChatMessage m(chat, *room_network); 354 ChatMessage m(chat, *room_network);
344 room->SendChatMessage(message); 355 room_member->SendChatMessage(message);
345 AppendChatMessage(m.GetPlayerChatMessage(player)); 356 AppendChatMessage(m.GetPlayerChatMessage(player));
346 ui->chat_message->clear(); 357 ui->chat_message->clear();
347 } 358 }
@@ -368,7 +379,7 @@ void ChatRoom::SetPlayerList(const Network::RoomMember::MemberList& member_list)
368 if (member.nickname.empty()) 379 if (member.nickname.empty())
369 continue; 380 continue;
370 QStandardItem* name_item = new PlayerListItem(member.nickname, member.username, 381 QStandardItem* name_item = new PlayerListItem(member.nickname, member.username,
371 member.avatar_url, member.game_info.name); 382 member.avatar_url, member.game_info);
372 383
373#ifdef ENABLE_WEB_SERVICE 384#ifdef ENABLE_WEB_SERVICE
374 if (!icon_cache.count(member.avatar_url) && !member.avatar_url.empty()) { 385 if (!icon_cache.count(member.avatar_url) && !member.avatar_url.empty()) {
diff --git a/src/yuzu/multiplayer/client_room.cpp b/src/yuzu/multiplayer/client_room.cpp
index a9859ed70..b34a8d004 100644
--- a/src/yuzu/multiplayer/client_room.cpp
+++ b/src/yuzu/multiplayer/client_room.cpp
@@ -10,7 +10,7 @@
10#include <QTime> 10#include <QTime>
11#include <QtConcurrent/QtConcurrentRun> 11#include <QtConcurrent/QtConcurrentRun>
12#include "common/logging/log.h" 12#include "common/logging/log.h"
13#include "core/announce_multiplayer_session.h" 13#include "network/announce_multiplayer_session.h"
14#include "ui_client_room.h" 14#include "ui_client_room.h"
15#include "yuzu/game_list_p.h" 15#include "yuzu/game_list_p.h"
16#include "yuzu/multiplayer/client_room.h" 16#include "yuzu/multiplayer/client_room.h"
@@ -74,7 +74,6 @@ void ClientRoomWindow::OnRoomUpdate(const Network::RoomInformation& info) {
74void ClientRoomWindow::OnStateChange(const Network::RoomMember::State& state) { 74void ClientRoomWindow::OnStateChange(const Network::RoomMember::State& state) {
75 if (state == Network::RoomMember::State::Joined || 75 if (state == Network::RoomMember::State::Joined ||
76 state == Network::RoomMember::State::Moderator) { 76 state == Network::RoomMember::State::Moderator) {
77
78 ui->chat->Clear(); 77 ui->chat->Clear();
79 ui->chat->AppendStatusMessage(tr("Connected")); 78 ui->chat->AppendStatusMessage(tr("Connected"));
80 SetModPerms(state == Network::RoomMember::State::Moderator); 79 SetModPerms(state == Network::RoomMember::State::Moderator);
diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp
index 9000c4531..017063074 100644
--- a/src/yuzu/multiplayer/direct_connect.cpp
+++ b/src/yuzu/multiplayer/direct_connect.cpp
@@ -8,6 +8,8 @@
8#include <QString> 8#include <QString>
9#include <QtConcurrent/QtConcurrentRun> 9#include <QtConcurrent/QtConcurrentRun>
10#include "common/settings.h" 10#include "common/settings.h"
11#include "core/core.h"
12#include "core/internal_network/network_interface.h"
11#include "network/network.h" 13#include "network/network.h"
12#include "ui_direct_connect.h" 14#include "ui_direct_connect.h"
13#include "yuzu/main.h" 15#include "yuzu/main.h"
@@ -20,9 +22,10 @@
20 22
21enum class ConnectionType : u8 { TraversalServer, IP }; 23enum class ConnectionType : u8 { TraversalServer, IP };
22 24
23DirectConnectWindow::DirectConnectWindow(Network::RoomNetwork& room_network_, QWidget* parent) 25DirectConnectWindow::DirectConnectWindow(Core::System& system_, QWidget* parent)
24 : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), 26 : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
25 ui(std::make_unique<Ui::DirectConnect>()), room_network{room_network_} { 27 ui(std::make_unique<Ui::DirectConnect>()), system{system_}, room_network{
28 system.GetRoomNetwork()} {
26 29
27 ui->setupUi(this); 30 ui->setupUi(this);
28 31
@@ -53,10 +56,20 @@ void DirectConnectWindow::RetranslateUi() {
53} 56}
54 57
55void DirectConnectWindow::Connect() { 58void DirectConnectWindow::Connect() {
59 if (!Network::GetSelectedNetworkInterface()) {
60 NetworkMessage::ErrorManager::ShowError(
61 NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED);
62 return;
63 }
56 if (!ui->nickname->hasAcceptableInput()) { 64 if (!ui->nickname->hasAcceptableInput()) {
57 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID); 65 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID);
58 return; 66 return;
59 } 67 }
68 if (system.IsPoweredOn()) {
69 if (!NetworkMessage::WarnGameRunning()) {
70 return;
71 }
72 }
60 if (const auto member = room_network.GetRoomMember().lock()) { 73 if (const auto member = room_network.GetRoomMember().lock()) {
61 // Prevent the user from trying to join a room while they are already joining. 74 // Prevent the user from trying to join a room while they are already joining.
62 if (member->GetState() == Network::RoomMember::State::Joining) { 75 if (member->GetState() == Network::RoomMember::State::Joining) {
@@ -97,9 +110,9 @@ void DirectConnectWindow::Connect() {
97 QFuture<void> f = QtConcurrent::run([&] { 110 QFuture<void> f = QtConcurrent::run([&] {
98 if (auto room_member = room_network.GetRoomMember().lock()) { 111 if (auto room_member = room_network.GetRoomMember().lock()) {
99 auto port = UISettings::values.multiplayer_port.GetValue(); 112 auto port = UISettings::values.multiplayer_port.GetValue();
100 room_member->Join(ui->nickname->text().toStdString(), "", 113 room_member->Join(ui->nickname->text().toStdString(),
101 ui->ip->text().toStdString().c_str(), port, 0, 114 ui->ip->text().toStdString().c_str(), port, 0, Network::NoPreferredIP,
102 Network::NoPreferredMac, ui->password->text().toStdString().c_str()); 115 ui->password->text().toStdString().c_str());
103 } 116 }
104 }); 117 });
105 watcher->setFuture(f); 118 watcher->setFuture(f);
@@ -121,9 +134,7 @@ void DirectConnectWindow::OnConnection() {
121 EndConnecting(); 134 EndConnecting();
122 135
123 if (auto room_member = room_network.GetRoomMember().lock()) { 136 if (auto room_member = room_network.GetRoomMember().lock()) {
124 if (room_member->GetState() == Network::RoomMember::State::Joined || 137 if (room_member->IsConnected()) {
125 room_member->GetState() == Network::RoomMember::State::Moderator) {
126
127 close(); 138 close();
128 } 139 }
129 } 140 }
diff --git a/src/yuzu/multiplayer/direct_connect.h b/src/yuzu/multiplayer/direct_connect.h
index 4e1043053..e39dd1e0d 100644
--- a/src/yuzu/multiplayer/direct_connect.h
+++ b/src/yuzu/multiplayer/direct_connect.h
@@ -12,11 +12,15 @@ namespace Ui {
12class DirectConnect; 12class DirectConnect;
13} 13}
14 14
15namespace Core {
16class System;
17}
18
15class DirectConnectWindow : public QDialog { 19class DirectConnectWindow : public QDialog {
16 Q_OBJECT 20 Q_OBJECT
17 21
18public: 22public:
19 explicit DirectConnectWindow(Network::RoomNetwork& room_network_, QWidget* parent = nullptr); 23 explicit DirectConnectWindow(Core::System& system_, QWidget* parent = nullptr);
20 ~DirectConnectWindow(); 24 ~DirectConnectWindow();
21 25
22 void RetranslateUi(); 26 void RetranslateUi();
@@ -39,5 +43,6 @@ private:
39 QFutureWatcher<void>* watcher; 43 QFutureWatcher<void>* watcher;
40 std::unique_ptr<Ui::DirectConnect> ui; 44 std::unique_ptr<Ui::DirectConnect> ui;
41 Validation validation; 45 Validation validation;
46 Core::System& system;
42 Network::RoomNetwork& room_network; 47 Network::RoomNetwork& room_network;
43}; 48};
diff --git a/src/yuzu/multiplayer/direct_connect.ui b/src/yuzu/multiplayer/direct_connect.ui
index 681b6bf69..57d6ec25a 100644
--- a/src/yuzu/multiplayer/direct_connect.ui
+++ b/src/yuzu/multiplayer/direct_connect.ui
@@ -83,7 +83,7 @@
83 <number>5</number> 83 <number>5</number>
84 </property> 84 </property>
85 <property name="placeholderText"> 85 <property name="placeholderText">
86 <string>24872</string> 86 <string notr="true" extracomment="placeholder string that tells user default port">24872</string>
87 </property> 87 </property>
88 </widget> 88 </widget>
89 </item> 89 </item>
diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp
index cb9464b2b..0c6adfd04 100644
--- a/src/yuzu/multiplayer/host_room.cpp
+++ b/src/yuzu/multiplayer/host_room.cpp
@@ -12,7 +12,9 @@
12#include <QtConcurrent/QtConcurrentRun> 12#include <QtConcurrent/QtConcurrentRun>
13#include "common/logging/log.h" 13#include "common/logging/log.h"
14#include "common/settings.h" 14#include "common/settings.h"
15#include "core/announce_multiplayer_session.h" 15#include "core/core.h"
16#include "core/internal_network/network_interface.h"
17#include "network/announce_multiplayer_session.h"
16#include "ui_host_room.h" 18#include "ui_host_room.h"
17#include "yuzu/game_list_p.h" 19#include "yuzu/game_list_p.h"
18#include "yuzu/main.h" 20#include "yuzu/main.h"
@@ -27,10 +29,11 @@
27 29
28HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list, 30HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list,
29 std::shared_ptr<Core::AnnounceMultiplayerSession> session, 31 std::shared_ptr<Core::AnnounceMultiplayerSession> session,
30 Network::RoomNetwork& room_network_) 32 Core::System& system_)
31 : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), 33 : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
32 ui(std::make_unique<Ui::HostRoom>()), 34 ui(std::make_unique<Ui::HostRoom>()),
33 announce_multiplayer_session(session), room_network{room_network_} { 35 announce_multiplayer_session(session), system{system_}, room_network{
36 system.GetRoomNetwork()} {
34 ui->setupUi(this); 37 ui->setupUi(this);
35 38
36 // set up validation for all of the fields 39 // set up validation for all of the fields
@@ -105,6 +108,11 @@ std::unique_ptr<Network::VerifyUser::Backend> HostRoomWindow::CreateVerifyBacken
105} 108}
106 109
107void HostRoomWindow::Host() { 110void HostRoomWindow::Host() {
111 if (!Network::GetSelectedNetworkInterface()) {
112 NetworkMessage::ErrorManager::ShowError(
113 NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED);
114 return;
115 }
108 if (!ui->username->hasAcceptableInput()) { 116 if (!ui->username->hasAcceptableInput()) {
109 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID); 117 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID);
110 return; 118 return;
@@ -121,6 +129,11 @@ void HostRoomWindow::Host() {
121 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::GAME_NOT_SELECTED); 129 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::GAME_NOT_SELECTED);
122 return; 130 return;
123 } 131 }
132 if (system.IsPoweredOn()) {
133 if (!NetworkMessage::WarnGameRunning()) {
134 return;
135 }
136 }
124 if (auto member = room_network.GetRoomMember().lock()) { 137 if (auto member = room_network.GetRoomMember().lock()) {
125 if (member->GetState() == Network::RoomMember::State::Joining) { 138 if (member->GetState() == Network::RoomMember::State::Joining) {
126 return; 139 return;
@@ -201,8 +214,8 @@ void HostRoomWindow::Host() {
201 } 214 }
202#endif 215#endif
203 // TODO: Check what to do with this 216 // TODO: Check what to do with this
204 member->Join(ui->username->text().toStdString(), "", "127.0.0.1", port, 0, 217 member->Join(ui->username->text().toStdString(), "127.0.0.1", port, 0,
205 Network::NoPreferredMac, password, token); 218 Network::NoPreferredIP, password, token);
206 219
207 // Store settings 220 // Store settings
208 UISettings::values.multiplayer_room_nickname = ui->username->text(); 221 UISettings::values.multiplayer_room_nickname = ui->username->text();
diff --git a/src/yuzu/multiplayer/host_room.h b/src/yuzu/multiplayer/host_room.h
index a968042d0..034cb2eef 100644
--- a/src/yuzu/multiplayer/host_room.h
+++ b/src/yuzu/multiplayer/host_room.h
@@ -17,8 +17,9 @@ class HostRoom;
17} 17}
18 18
19namespace Core { 19namespace Core {
20class System;
20class AnnounceMultiplayerSession; 21class AnnounceMultiplayerSession;
21} 22} // namespace Core
22 23
23class ConnectionError; 24class ConnectionError;
24class ComboBoxProxyModel; 25class ComboBoxProxyModel;
@@ -35,7 +36,7 @@ class HostRoomWindow : public QDialog {
35public: 36public:
36 explicit HostRoomWindow(QWidget* parent, QStandardItemModel* list, 37 explicit HostRoomWindow(QWidget* parent, QStandardItemModel* list,
37 std::shared_ptr<Core::AnnounceMultiplayerSession> session, 38 std::shared_ptr<Core::AnnounceMultiplayerSession> session,
38 Network::RoomNetwork& room_network_); 39 Core::System& system_);
39 ~HostRoomWindow(); 40 ~HostRoomWindow();
40 41
41 /** 42 /**
@@ -54,6 +55,7 @@ private:
54 QStandardItemModel* game_list; 55 QStandardItemModel* game_list;
55 ComboBoxProxyModel* proxy; 56 ComboBoxProxyModel* proxy;
56 Validation validation; 57 Validation validation;
58 Core::System& system;
57 Network::RoomNetwork& room_network; 59 Network::RoomNetwork& room_network;
58}; 60};
59 61
diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp
index 23c2f21ab..107d40547 100644
--- a/src/yuzu/multiplayer/lobby.cpp
+++ b/src/yuzu/multiplayer/lobby.cpp
@@ -6,6 +6,8 @@
6#include <QtConcurrent/QtConcurrentRun> 6#include <QtConcurrent/QtConcurrentRun>
7#include "common/logging/log.h" 7#include "common/logging/log.h"
8#include "common/settings.h" 8#include "common/settings.h"
9#include "core/core.h"
10#include "core/internal_network/network_interface.h"
9#include "network/network.h" 11#include "network/network.h"
10#include "ui_lobby.h" 12#include "ui_lobby.h"
11#include "yuzu/game_list_p.h" 13#include "yuzu/game_list_p.h"
@@ -22,11 +24,11 @@
22#endif 24#endif
23 25
24Lobby::Lobby(QWidget* parent, QStandardItemModel* list, 26Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
25 std::shared_ptr<Core::AnnounceMultiplayerSession> session, 27 std::shared_ptr<Core::AnnounceMultiplayerSession> session, Core::System& system_)
26 Network::RoomNetwork& room_network_)
27 : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), 28 : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
28 ui(std::make_unique<Ui::Lobby>()), 29 ui(std::make_unique<Ui::Lobby>()),
29 announce_multiplayer_session(session), room_network{room_network_} { 30 announce_multiplayer_session(session), system{system_}, room_network{
31 system.GetRoomNetwork()} {
30 ui->setupUi(this); 32 ui->setupUi(this);
31 33
32 // setup the watcher for background connections 34 // setup the watcher for background connections
@@ -114,6 +116,18 @@ void Lobby::OnExpandRoom(const QModelIndex& index) {
114} 116}
115 117
116void Lobby::OnJoinRoom(const QModelIndex& source) { 118void Lobby::OnJoinRoom(const QModelIndex& source) {
119 if (!Network::GetSelectedNetworkInterface()) {
120 NetworkMessage::ErrorManager::ShowError(
121 NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED);
122 return;
123 }
124
125 if (system.IsPoweredOn()) {
126 if (!NetworkMessage::WarnGameRunning()) {
127 return;
128 }
129 }
130
117 if (const auto member = room_network.GetRoomMember().lock()) { 131 if (const auto member = room_network.GetRoomMember().lock()) {
118 // Prevent the user from trying to join a room while they are already joining. 132 // Prevent the user from trying to join a room while they are already joining.
119 if (member->GetState() == Network::RoomMember::State::Joining) { 133 if (member->GetState() == Network::RoomMember::State::Joining) {
@@ -169,7 +183,7 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
169 } 183 }
170#endif 184#endif
171 if (auto room_member = room_network.GetRoomMember().lock()) { 185 if (auto room_member = room_network.GetRoomMember().lock()) {
172 room_member->Join(nickname, "", ip.c_str(), port, 0, Network::NoPreferredMac, password, 186 room_member->Join(nickname, ip.c_str(), port, 0, Network::NoPreferredIP, password,
173 token); 187 token);
174 } 188 }
175 }); 189 });
diff --git a/src/yuzu/multiplayer/lobby.h b/src/yuzu/multiplayer/lobby.h
index 82744ca94..2696aec21 100644
--- a/src/yuzu/multiplayer/lobby.h
+++ b/src/yuzu/multiplayer/lobby.h
@@ -9,7 +9,7 @@
9#include <QSortFilterProxyModel> 9#include <QSortFilterProxyModel>
10#include <QStandardItemModel> 10#include <QStandardItemModel>
11#include "common/announce_multiplayer_room.h" 11#include "common/announce_multiplayer_room.h"
12#include "core/announce_multiplayer_session.h" 12#include "network/announce_multiplayer_session.h"
13#include "network/network.h" 13#include "network/network.h"
14#include "yuzu/multiplayer/validation.h" 14#include "yuzu/multiplayer/validation.h"
15 15
@@ -20,6 +20,10 @@ class Lobby;
20class LobbyModel; 20class LobbyModel;
21class LobbyFilterProxyModel; 21class LobbyFilterProxyModel;
22 22
23namespace Core {
24class System;
25}
26
23/** 27/**
24 * Listing of all public games pulled from services. The lobby should be simple enough for users to 28 * Listing of all public games pulled from services. The lobby should be simple enough for users to
25 * find the game they want to play, and join it. 29 * find the game they want to play, and join it.
@@ -30,7 +34,7 @@ class Lobby : public QDialog {
30public: 34public:
31 explicit Lobby(QWidget* parent, QStandardItemModel* list, 35 explicit Lobby(QWidget* parent, QStandardItemModel* list,
32 std::shared_ptr<Core::AnnounceMultiplayerSession> session, 36 std::shared_ptr<Core::AnnounceMultiplayerSession> session,
33 Network::RoomNetwork& room_network_); 37 Core::System& system_);
34 ~Lobby() override; 38 ~Lobby() override;
35 39
36 /** 40 /**
@@ -94,6 +98,7 @@ private:
94 std::weak_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session; 98 std::weak_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
95 QFutureWatcher<void>* watcher; 99 QFutureWatcher<void>* watcher;
96 Validation validation; 100 Validation validation;
101 Core::System& system;
97 Network::RoomNetwork& room_network; 102 Network::RoomNetwork& room_network;
98}; 103};
99 104
diff --git a/src/yuzu/multiplayer/message.cpp b/src/yuzu/multiplayer/message.cpp
index 76ec276ad..758b5b731 100644
--- a/src/yuzu/multiplayer/message.cpp
+++ b/src/yuzu/multiplayer/message.cpp
@@ -43,15 +43,15 @@ const ConnectionError ErrorManager::LOST_CONNECTION(
43 QT_TR_NOOP("Connection to room lost. Try to reconnect.")); 43 QT_TR_NOOP("Connection to room lost. Try to reconnect."));
44const ConnectionError ErrorManager::HOST_KICKED( 44const ConnectionError ErrorManager::HOST_KICKED(
45 QT_TR_NOOP("You have been kicked by the room host.")); 45 QT_TR_NOOP("You have been kicked by the room host."));
46const ConnectionError ErrorManager::MAC_COLLISION( 46const ConnectionError ErrorManager::IP_COLLISION(
47 QT_TR_NOOP("MAC address is already in use. Please choose another.")); 47 QT_TR_NOOP("IP address is already in use. Please choose another."));
48const ConnectionError ErrorManager::CONSOLE_ID_COLLISION(QT_TR_NOOP(
49 "Your Console ID conflicted with someone else's in the room.\n\nPlease go to Emulation "
50 "> Configure > System to regenerate your Console ID."));
51const ConnectionError ErrorManager::PERMISSION_DENIED( 48const ConnectionError ErrorManager::PERMISSION_DENIED(
52 QT_TR_NOOP("You do not have enough permission to perform this action.")); 49 QT_TR_NOOP("You do not have enough permission to perform this action."));
53const ConnectionError ErrorManager::NO_SUCH_USER(QT_TR_NOOP( 50const ConnectionError ErrorManager::NO_SUCH_USER(QT_TR_NOOP(
54 "The user you are trying to kick/ban could not be found.\nThey may have left the room.")); 51 "The user you are trying to kick/ban could not be found.\nThey may have left the room."));
52const ConnectionError ErrorManager::NO_INTERFACE_SELECTED(
53 QT_TR_NOOP("No network interface is selected.\nPlease go to Configure -> System -> Network and "
54 "make a selection."));
55 55
56static bool WarnMessage(const std::string& title, const std::string& text) { 56static bool WarnMessage(const std::string& title, const std::string& text) {
57 return QMessageBox::Ok == QMessageBox::warning(nullptr, QObject::tr(title.c_str()), 57 return QMessageBox::Ok == QMessageBox::warning(nullptr, QObject::tr(title.c_str()),
@@ -63,6 +63,13 @@ void ErrorManager::ShowError(const ConnectionError& e) {
63 QMessageBox::critical(nullptr, tr("Error"), tr(e.GetString().c_str())); 63 QMessageBox::critical(nullptr, tr("Error"), tr(e.GetString().c_str()));
64} 64}
65 65
66bool WarnGameRunning() {
67 return WarnMessage(
68 QT_TR_NOOP("Game already running"),
69 QT_TR_NOOP("Joining a room when the game is already running is discouraged "
70 "and can cause the room feature not to work correctly.\nProceed anyway?"));
71}
72
66bool WarnCloseRoom() { 73bool WarnCloseRoom() {
67 return WarnMessage( 74 return WarnMessage(
68 QT_TR_NOOP("Leave Room"), 75 QT_TR_NOOP("Leave Room"),
diff --git a/src/yuzu/multiplayer/message.h b/src/yuzu/multiplayer/message.h
index eb5c8d1be..f038b9a1f 100644
--- a/src/yuzu/multiplayer/message.h
+++ b/src/yuzu/multiplayer/message.h
@@ -40,15 +40,23 @@ public:
40 static const ConnectionError GENERIC_ERROR; 40 static const ConnectionError GENERIC_ERROR;
41 static const ConnectionError LOST_CONNECTION; 41 static const ConnectionError LOST_CONNECTION;
42 static const ConnectionError HOST_KICKED; 42 static const ConnectionError HOST_KICKED;
43 static const ConnectionError MAC_COLLISION; 43 static const ConnectionError IP_COLLISION;
44 static const ConnectionError CONSOLE_ID_COLLISION;
45 static const ConnectionError PERMISSION_DENIED; 44 static const ConnectionError PERMISSION_DENIED;
46 static const ConnectionError NO_SUCH_USER; 45 static const ConnectionError NO_SUCH_USER;
46 static const ConnectionError NO_INTERFACE_SELECTED;
47 /** 47 /**
48 * Shows a standard QMessageBox with a error message 48 * Shows a standard QMessageBox with a error message
49 */ 49 */
50 static void ShowError(const ConnectionError& e); 50 static void ShowError(const ConnectionError& e);
51}; 51};
52
53/**
54 * Show a standard QMessageBox with a warning message about joining a room when
55 * the game is already running
56 * return true if the user wants to close the network connection
57 */
58bool WarnGameRunning();
59
52/** 60/**
53 * Show a standard QMessageBox with a warning message about leaving the room 61 * Show a standard QMessageBox with a warning message about leaving the room
54 * return true if the user wants to close the network connection 62 * return true if the user wants to close the network connection
diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp
index 4149b5232..66e098296 100644
--- a/src/yuzu/multiplayer/state.cpp
+++ b/src/yuzu/multiplayer/state.cpp
@@ -8,6 +8,7 @@
8#include <QStandardItemModel> 8#include <QStandardItemModel>
9#include "common/announce_multiplayer_room.h" 9#include "common/announce_multiplayer_room.h"
10#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "core/core.h"
11#include "yuzu/game_list.h" 12#include "yuzu/game_list.h"
12#include "yuzu/multiplayer/client_room.h" 13#include "yuzu/multiplayer/client_room.h"
13#include "yuzu/multiplayer/direct_connect.h" 14#include "yuzu/multiplayer/direct_connect.h"
@@ -19,10 +20,9 @@
19#include "yuzu/util/clickable_label.h" 20#include "yuzu/util/clickable_label.h"
20 21
21MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_list_model_, 22MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_list_model_,
22 QAction* leave_room_, QAction* show_room_, 23 QAction* leave_room_, QAction* show_room_, Core::System& system_)
23 Network::RoomNetwork& room_network_)
24 : QWidget(parent), game_list_model(game_list_model_), leave_room(leave_room_), 24 : QWidget(parent), game_list_model(game_list_model_), leave_room(leave_room_),
25 show_room(show_room_), room_network{room_network_} { 25 show_room(show_room_), system{system_}, room_network{system.GetRoomNetwork()} {
26 if (auto member = room_network.GetRoomMember().lock()) { 26 if (auto member = room_network.GetRoomMember().lock()) {
27 // register the network structs to use in slots and signals 27 // register the network structs to use in slots and signals
28 state_callback_handle = member->BindOnStateChanged( 28 state_callback_handle = member->BindOnStateChanged(
@@ -59,7 +59,9 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis
59 }); 59 });
60} 60}
61 61
62MultiplayerState::~MultiplayerState() { 62MultiplayerState::~MultiplayerState() = default;
63
64void MultiplayerState::Close() {
63 if (state_callback_handle) { 65 if (state_callback_handle) {
64 if (auto member = room_network.GetRoomMember().lock()) { 66 if (auto member = room_network.GetRoomMember().lock()) {
65 member->Unbind(state_callback_handle); 67 member->Unbind(state_callback_handle);
@@ -71,9 +73,6 @@ MultiplayerState::~MultiplayerState() {
71 member->Unbind(error_callback_handle); 73 member->Unbind(error_callback_handle);
72 } 74 }
73 } 75 }
74}
75
76void MultiplayerState::Close() {
77 if (host_room) { 76 if (host_room) {
78 host_room->close(); 77 host_room->close();
79 } 78 }
@@ -95,7 +94,6 @@ void MultiplayerState::retranslateUi() {
95 status_text->setText(tr("Not Connected. Click here to find a room!")); 94 status_text->setText(tr("Not Connected. Click here to find a room!"));
96 } else if (current_state == Network::RoomMember::State::Joined || 95 } else if (current_state == Network::RoomMember::State::Joined ||
97 current_state == Network::RoomMember::State::Moderator) { 96 current_state == Network::RoomMember::State::Moderator) {
98
99 status_text->setText(tr("Connected")); 97 status_text->setText(tr("Connected"));
100 } else { 98 } else {
101 status_text->setText(tr("Not Connected")); 99 status_text->setText(tr("Not Connected"));
@@ -151,11 +149,8 @@ void MultiplayerState::OnNetworkError(const Network::RoomMember::Error& error) {
151 NetworkMessage::ErrorManager::ShowError( 149 NetworkMessage::ErrorManager::ShowError(
152 NetworkMessage::ErrorManager::USERNAME_NOT_VALID_SERVER); 150 NetworkMessage::ErrorManager::USERNAME_NOT_VALID_SERVER);
153 break; 151 break;
154 case Network::RoomMember::Error::MacCollision: 152 case Network::RoomMember::Error::IpCollision:
155 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::MAC_COLLISION); 153 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::IP_COLLISION);
156 break;
157 case Network::RoomMember::Error::ConsoleIdCollision:
158 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::CONSOLE_ID_COLLISION);
159 break; 154 break;
160 case Network::RoomMember::Error::RoomIsFull: 155 case Network::RoomMember::Error::RoomIsFull:
161 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::ROOM_IS_FULL); 156 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::ROOM_IS_FULL);
@@ -213,15 +208,14 @@ static void BringWidgetToFront(QWidget* widget) {
213 208
214void MultiplayerState::OnViewLobby() { 209void MultiplayerState::OnViewLobby() {
215 if (lobby == nullptr) { 210 if (lobby == nullptr) {
216 lobby = new Lobby(this, game_list_model, announce_multiplayer_session, room_network); 211 lobby = new Lobby(this, game_list_model, announce_multiplayer_session, system);
217 } 212 }
218 BringWidgetToFront(lobby); 213 BringWidgetToFront(lobby);
219} 214}
220 215
221void MultiplayerState::OnCreateRoom() { 216void MultiplayerState::OnCreateRoom() {
222 if (host_room == nullptr) { 217 if (host_room == nullptr) {
223 host_room = 218 host_room = new HostRoomWindow(this, game_list_model, announce_multiplayer_session, system);
224 new HostRoomWindow(this, game_list_model, announce_multiplayer_session, room_network);
225 } 219 }
226 BringWidgetToFront(host_room); 220 BringWidgetToFront(host_room);
227} 221}
@@ -284,7 +278,7 @@ void MultiplayerState::OnOpenNetworkRoom() {
284 278
285void MultiplayerState::OnDirectConnectToRoom() { 279void MultiplayerState::OnDirectConnectToRoom() {
286 if (direct_connect == nullptr) { 280 if (direct_connect == nullptr) {
287 direct_connect = new DirectConnectWindow(room_network, this); 281 direct_connect = new DirectConnectWindow(system, this);
288 } 282 }
289 BringWidgetToFront(direct_connect); 283 BringWidgetToFront(direct_connect);
290} 284}
diff --git a/src/yuzu/multiplayer/state.h b/src/yuzu/multiplayer/state.h
index 9c60712d5..c92496413 100644
--- a/src/yuzu/multiplayer/state.h
+++ b/src/yuzu/multiplayer/state.h
@@ -4,7 +4,7 @@
4#pragma once 4#pragma once
5 5
6#include <QWidget> 6#include <QWidget>
7#include "core/announce_multiplayer_session.h" 7#include "network/announce_multiplayer_session.h"
8#include "network/network.h" 8#include "network/network.h"
9 9
10class QStandardItemModel; 10class QStandardItemModel;
@@ -14,12 +14,16 @@ class ClientRoomWindow;
14class DirectConnectWindow; 14class DirectConnectWindow;
15class ClickableLabel; 15class ClickableLabel;
16 16
17namespace Core {
18class System;
19}
20
17class MultiplayerState : public QWidget { 21class MultiplayerState : public QWidget {
18 Q_OBJECT; 22 Q_OBJECT;
19 23
20public: 24public:
21 explicit MultiplayerState(QWidget* parent, QStandardItemModel* game_list, QAction* leave_room, 25 explicit MultiplayerState(QWidget* parent, QStandardItemModel* game_list, QAction* leave_room,
22 QAction* show_room, Network::RoomNetwork& room_network_); 26 QAction* show_room, Core::System& system_);
23 ~MultiplayerState(); 27 ~MultiplayerState();
24 28
25 /** 29 /**
@@ -86,6 +90,7 @@ private:
86 Network::RoomMember::CallbackHandle<Network::RoomMember::Error> error_callback_handle; 90 Network::RoomMember::CallbackHandle<Network::RoomMember::Error> error_callback_handle;
87 91
88 bool show_notification = false; 92 bool show_notification = false;
93 Core::System& system;
89 Network::RoomNetwork& room_network; 94 Network::RoomNetwork& room_network;
90}; 95};
91 96
diff --git a/src/yuzu/multiplayer/validation.h b/src/yuzu/multiplayer/validation.h
index 7d48e589d..dabf860be 100644
--- a/src/yuzu/multiplayer/validation.h
+++ b/src/yuzu/multiplayer/validation.h
@@ -10,7 +10,7 @@
10class Validation { 10class Validation {
11public: 11public:
12 Validation() 12 Validation()
13 : room_name(room_name_regex), nickname(nickname_regex), ip(ip_regex), port(0, 65535) {} 13 : room_name(room_name_regex), nickname(nickname_regex), ip(ip_regex), port(0, UINT16_MAX) {}
14 14
15 ~Validation() = default; 15 ~Validation() = default;
16 16
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 25d1bf1e6..e12d414d9 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -104,11 +104,12 @@ struct Values {
104 // multiplayer settings 104 // multiplayer settings
105 Settings::Setting<QString> multiplayer_nickname{QStringLiteral("yuzu"), "nickname"}; 105 Settings::Setting<QString> multiplayer_nickname{QStringLiteral("yuzu"), "nickname"};
106 Settings::Setting<QString> multiplayer_ip{{}, "ip"}; 106 Settings::Setting<QString> multiplayer_ip{{}, "ip"};
107 Settings::SwitchableSetting<uint, true> multiplayer_port{24872, 0, 65535, "port"}; 107 Settings::SwitchableSetting<uint, true> multiplayer_port{24872, 0, UINT16_MAX, "port"};
108 Settings::Setting<QString> multiplayer_room_nickname{{}, "room_nickname"}; 108 Settings::Setting<QString> multiplayer_room_nickname{{}, "room_nickname"};
109 Settings::Setting<QString> multiplayer_room_name{{}, "room_name"}; 109 Settings::Setting<QString> multiplayer_room_name{{}, "room_name"};
110 Settings::SwitchableSetting<uint, true> multiplayer_max_player{8, 0, 8, "max_player"}; 110 Settings::SwitchableSetting<uint, true> multiplayer_max_player{8, 0, 8, "max_player"};
111 Settings::SwitchableSetting<uint, true> multiplayer_room_port{24872, 0, 65535, "room_port"}; 111 Settings::SwitchableSetting<uint, true> multiplayer_room_port{24872, 0, UINT16_MAX,
112 "room_port"};
112 Settings::SwitchableSetting<uint, true> multiplayer_host_type{0, 0, 1, "host_type"}; 113 Settings::SwitchableSetting<uint, true> multiplayer_host_type{0, 0, 1, "host_type"};
113 Settings::Setting<qulonglong> multiplayer_game_id{{}, "game_id"}; 114 Settings::Setting<qulonglong> multiplayer_game_id{{}, "game_id"};
114 Settings::Setting<QString> multiplayer_room_description{{}, "room_description"}; 115 Settings::Setting<QString> multiplayer_room_description{{}, "room_description"};
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index bd0fb75f8..66dd0dc15 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -314,6 +314,7 @@ void Config::ReadValues() {
314 ReadSetting("Renderer", Settings::values.nvdec_emulation); 314 ReadSetting("Renderer", Settings::values.nvdec_emulation);
315 ReadSetting("Renderer", Settings::values.accelerate_astc); 315 ReadSetting("Renderer", Settings::values.accelerate_astc);
316 ReadSetting("Renderer", Settings::values.use_fast_gpu_time); 316 ReadSetting("Renderer", Settings::values.use_fast_gpu_time);
317 ReadSetting("Renderer", Settings::values.use_pessimistic_flushes);
317 318
318 ReadSetting("Renderer", Settings::values.bg_red); 319 ReadSetting("Renderer", Settings::values.bg_red);
319 ReadSetting("Renderer", Settings::values.bg_green); 320 ReadSetting("Renderer", Settings::values.bg_green);
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index 1168cf136..d214771b0 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -319,6 +319,10 @@ use_asynchronous_gpu_emulation =
319# 0: Off, 1 (default): On 319# 0: Off, 1 (default): On
320use_fast_gpu_time = 320use_fast_gpu_time =
321 321
322# Force unmodified buffers to be flushed, which can cost performance.
323# 0: Off (default), 1: On
324use_pessimistic_flushes =
325
322# Whether to use garbage collection or not for GPU caches. 326# Whether to use garbage collection or not for GPU caches.
323# 0 (default): Off, 1: On 327# 0 (default): Off, 1: On
324use_caches_gc = 328use_caches_gc =
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 003890c07..3a0f33cba 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -108,15 +108,11 @@ static void OnNetworkError(const Network::RoomMember::Error& error) {
108 "You tried to use the same nickname as another user that is connected to the Room"); 108 "You tried to use the same nickname as another user that is connected to the Room");
109 exit(1); 109 exit(1);
110 break; 110 break;
111 case Network::RoomMember::Error::MacCollision: 111 case Network::RoomMember::Error::IpCollision:
112 LOG_ERROR(Network, "You tried to use the same MAC-Address as another user that is " 112 LOG_ERROR(Network, "You tried to use the same fake IP-Address as another user that is "
113 "connected to the Room"); 113 "connected to the Room");
114 exit(1); 114 exit(1);
115 break; 115 break;
116 case Network::RoomMember::Error::ConsoleIdCollision:
117 LOG_ERROR(Network, "Your Console ID conflicted with someone else in the Room");
118 exit(1);
119 break;
120 case Network::RoomMember::Error::WrongPassword: 116 case Network::RoomMember::Error::WrongPassword:
121 LOG_ERROR(Network, "Room replied with: Wrong password"); 117 LOG_ERROR(Network, "Room replied with: Wrong password");
122 exit(1); 118 exit(1);
@@ -365,7 +361,7 @@ int main(int argc, char** argv) {
365 member->BindOnError(OnNetworkError); 361 member->BindOnError(OnNetworkError);
366 LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port, 362 LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port,
367 nickname); 363 nickname);
368 member->Join(nickname, "", address.c_str(), port, 0, Network::NoPreferredMac, password); 364 member->Join(nickname, address.c_str(), port, 0, Network::NoPreferredIP, password);
369 } else { 365 } else {
370 LOG_ERROR(Network, "Could not access RoomMember"); 366 LOG_ERROR(Network, "Could not access RoomMember");
371 return 0; 367 return 0;