summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.clang-format3
-rw-r--r--src/CMakeLists.txt4
-rw-r--r--src/audio_core/CMakeLists.txt3
-rw-r--r--src/common/CMakeLists.txt4
-rw-r--r--src/common/alignment.h3
-rw-r--r--src/common/announce_multiplayer_room.h143
-rw-r--r--src/common/atomic_helpers.h5
-rw-r--r--src/common/detached_tasks.cpp5
-rw-r--r--src/common/detached_tasks.h5
-rw-r--r--src/common/error.cpp6
-rw-r--r--src/common/error.h6
-rw-r--r--src/common/fixed_point.h26
-rw-r--r--src/common/hash.h5
-rw-r--r--src/common/input.h34
-rw-r--r--src/common/logging/backend.cpp5
-rw-r--r--src/common/logging/backend.h5
-rw-r--r--src/common/logging/filter.cpp5
-rw-r--r--src/common/logging/filter.h5
-rw-r--r--src/common/logging/log.h5
-rw-r--r--src/common/logging/text_formatter.cpp5
-rw-r--r--src/common/logging/text_formatter.h5
-rw-r--r--src/common/microprofile.cpp5
-rw-r--r--src/common/microprofile.h5
-rw-r--r--src/common/microprofileui.h5
-rw-r--r--src/common/param_package.cpp5
-rw-r--r--src/common/param_package.h5
-rw-r--r--src/common/quaternion.h5
-rw-r--r--src/common/reader_writer_queue.h5
-rw-r--r--src/common/scm_rev.cpp.in5
-rw-r--r--src/common/scm_rev.h5
-rw-r--r--src/common/scope_exit.h5
-rw-r--r--src/common/settings.h3
-rw-r--r--src/common/telemetry.cpp5
-rw-r--r--src/common/telemetry.h5
-rw-r--r--src/common/x64/xbyak_abi.h5
-rw-r--r--src/common/x64/xbyak_util.h5
-rw-r--r--src/core/CMakeLists.txt32
-rw-r--r--src/core/announce_multiplayer_session.cpp164
-rw-r--r--src/core/announce_multiplayer_session.h98
-rw-r--r--src/core/arm/arm_interface.cpp3
-rw-r--r--src/core/arm/arm_interface.h5
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_cp15.cpp5
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_cp15.h5
-rw-r--r--src/core/core.cpp36
-rw-r--r--src/core/core.h15
-rw-r--r--src/core/cpu_manager.cpp129
-rw-r--r--src/core/cpu_manager.h10
-rw-r--r--src/core/file_sys/errors.h5
-rw-r--r--src/core/frontend/emu_window.cpp5
-rw-r--r--src/core/frontend/emu_window.h5
-rw-r--r--src/core/hid/emulated_controller.cpp59
-rw-r--r--src/core/hid/emulated_controller.h40
-rw-r--r--src/core/hid/input_converter.cpp14
-rw-r--r--src/core/hid/input_converter.h8
-rw-r--r--src/core/hid/irs_types.h301
-rw-r--r--src/core/hle/ipc.h5
-rw-r--r--src/core/hle/ipc_helpers.h5
-rw-r--r--src/core/hle/kernel/global_scheduler_context.cpp7
-rw-r--r--src/core/hle/kernel/k_client_port.cpp5
-rw-r--r--src/core/hle/kernel/k_client_port.h5
-rw-r--r--src/core/hle/kernel/k_interrupt_manager.cpp7
-rw-r--r--src/core/hle/kernel/k_process.cpp5
-rw-r--r--src/core/hle/kernel/k_process.h5
-rw-r--r--src/core/hle/kernel/k_scheduler.cpp735
-rw-r--r--src/core/hle/kernel/k_scheduler.h223
-rw-r--r--src/core/hle/kernel/k_scheduler_lock.h2
-rw-r--r--src/core/hle/kernel/k_shared_memory.cpp5
-rw-r--r--src/core/hle/kernel/k_shared_memory.h5
-rw-r--r--src/core/hle/kernel/k_thread.cpp30
-rw-r--r--src/core/hle/kernel/k_thread.h26
-rw-r--r--src/core/hle/kernel/kernel.cpp27
-rw-r--r--src/core/hle/kernel/physical_core.cpp1
-rw-r--r--src/core/hle/kernel/svc.cpp7
-rw-r--r--src/core/hle/result.h5
-rw-r--r--src/core/hle/service/hid/errors.h7
-rw-r--r--src/core/hle/service/hid/hid.cpp4
-rw-r--r--src/core/hle/service/hid/irs.cpp324
-rw-r--r--src/core/hle/service/hid/irs.h295
-rw-r--r--src/core/hle/service/hid/irsensor/clustering_processor.cpp34
-rw-r--r--src/core/hle/service/hid/irsensor/clustering_processor.h74
-rw-r--r--src/core/hle/service/hid/irsensor/image_transfer_processor.cpp150
-rw-r--r--src/core/hle/service/hid/irsensor/image_transfer_processor.h73
-rw-r--r--src/core/hle/service/hid/irsensor/ir_led_processor.cpp27
-rw-r--r--src/core/hle/service/hid/irsensor/ir_led_processor.h47
-rw-r--r--src/core/hle/service/hid/irsensor/moment_processor.cpp34
-rw-r--r--src/core/hle/service/hid/irsensor/moment_processor.h61
-rw-r--r--src/core/hle/service/hid/irsensor/pointing_processor.cpp26
-rw-r--r--src/core/hle/service/hid/irsensor/pointing_processor.h61
-rw-r--r--src/core/hle/service/hid/irsensor/processor_base.cpp67
-rw-r--r--src/core/hle/service/hid/irsensor/processor_base.h33
-rw-r--r--src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp29
-rw-r--r--src/core/hle/service/hid/irsensor/tera_plugin_processor.h53
-rw-r--r--src/core/hle/service/nifm/nifm.cpp4
-rw-r--r--src/core/hle/service/sockets/bsd.cpp4
-rw-r--r--src/core/hle/service/sockets/bsd.h2
-rw-r--r--src/core/hle/service/sockets/sockets_translate.cpp2
-rw-r--r--src/core/hle/service/sockets/sockets_translate.h2
-rw-r--r--src/core/internal_network/network.cpp (renamed from src/core/network/network.cpp)6
-rw-r--r--src/core/internal_network/network.h (renamed from src/core/network/network.h)0
-rw-r--r--src/core/internal_network/network_interface.cpp (renamed from src/core/network/network_interface.cpp)2
-rw-r--r--src/core/internal_network/network_interface.h (renamed from src/core/network/network_interface.h)0
-rw-r--r--src/core/internal_network/sockets.h (renamed from src/core/network/sockets.h)3
-rw-r--r--src/core/memory.cpp5
-rw-r--r--src/core/memory.h5
-rw-r--r--src/core/perf_stats.cpp5
-rw-r--r--src/core/perf_stats.h5
-rw-r--r--src/core/telemetry_session.cpp5
-rw-r--r--src/core/telemetry_session.h5
-rw-r--r--src/input_common/CMakeLists.txt5
-rw-r--r--src/input_common/drivers/camera.cpp82
-rw-r--r--src/input_common/drivers/camera.h29
-rw-r--r--src/input_common/drivers/sdl_driver.cpp5
-rw-r--r--src/input_common/drivers/sdl_driver.h5
-rw-r--r--src/input_common/drivers/tas_input.cpp2
-rw-r--r--src/input_common/drivers/udp_client.cpp5
-rw-r--r--src/input_common/drivers/udp_client.h5
-rw-r--r--src/input_common/helpers/stick_from_buttons.cpp5
-rw-r--r--src/input_common/helpers/stick_from_buttons.h5
-rw-r--r--src/input_common/helpers/touch_from_buttons.cpp5
-rw-r--r--src/input_common/helpers/touch_from_buttons.h5
-rw-r--r--src/input_common/helpers/udp_protocol.cpp5
-rw-r--r--src/input_common/helpers/udp_protocol.h7
-rw-r--r--src/input_common/input_engine.cpp38
-rw-r--r--src/input_common/input_engine.h19
-rw-r--r--src/input_common/input_poller.cpp60
-rw-r--r--src/input_common/input_poller.h11
-rw-r--r--src/input_common/main.cpp26
-rw-r--r--src/input_common/main.h14
-rw-r--r--src/network/CMakeLists.txt19
-rw-r--r--src/network/network.cpp50
-rw-r--r--src/network/network.h33
-rw-r--r--src/network/packet.cpp262
-rw-r--r--src/network/packet.h165
-rw-r--r--src/network/room.cpp1110
-rw-r--r--src/network/room.h151
-rw-r--r--src/network/room_member.cpp696
-rw-r--r--src/network/room_member.h318
-rw-r--r--src/network/verify_user.cpp17
-rw-r--r--src/network/verify_user.h45
-rw-r--r--src/shader_recompiler/CMakeLists.txt3
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/logic_operation_three_input_lut3.py6
-rw-r--r--src/tests/CMakeLists.txt5
-rw-r--r--src/tests/common/bit_field.cpp5
-rw-r--r--src/tests/common/param_package.cpp5
-rw-r--r--src/tests/core/internal_network/network.cpp (renamed from src/tests/core/network/network.cpp)4
-rw-r--r--src/tests/tests.cpp5
-rw-r--r--src/video_core/CMakeLists.txt3
-rw-r--r--src/video_core/host_shaders/CMakeLists.txt3
-rw-r--r--src/video_core/host_shaders/StringShaderHeader.cmake3
-rw-r--r--src/video_core/host_shaders/source_shader.h.in3
-rw-r--r--src/video_core/host_shaders/vulkan_present_scaleforce_fp16.frag3
-rw-r--r--src/video_core/host_shaders/vulkan_present_scaleforce_fp32.frag3
-rw-r--r--src/video_core/renderer_base.cpp5
-rw-r--r--src/video_core/renderer_base.h5
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp5
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h5
-rw-r--r--src/video_core/renderer_opengl/gl_resource_manager.cpp5
-rw-r--r--src/video_core/renderer_opengl/gl_resource_manager.h5
-rw-r--r--src/video_core/renderer_opengl/gl_shader_util.cpp5
-rw-r--r--src/video_core/renderer_opengl/gl_shader_util.h5
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp5
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h5
-rw-r--r--src/video_core/surface.cpp5
-rw-r--r--src/video_core/surface.h5
-rw-r--r--src/video_core/video_core.cpp5
-rw-r--r--src/video_core/video_core.h5
-rw-r--r--src/web_service/CMakeLists.txt9
-rw-r--r--src/web_service/announce_room_json.cpp145
-rw-r--r--src/web_service/announce_room_json.h41
-rw-r--r--src/web_service/telemetry_json.cpp5
-rw-r--r--src/web_service/telemetry_json.h5
-rw-r--r--src/web_service/verify_login.cpp5
-rw-r--r--src/web_service/verify_login.h5
-rw-r--r--src/web_service/verify_user_jwt.cpp67
-rw-r--r--src/web_service/verify_user_jwt.h26
-rw-r--r--src/web_service/web_backend.cpp5
-rw-r--r--src/web_service/web_backend.h5
-rw-r--r--src/yuzu/CMakeLists.txt44
-rw-r--r--src/yuzu/Info.plist6
-rw-r--r--src/yuzu/aboutdialog.ui2
-rw-r--r--src/yuzu/bootmanager.cpp87
-rw-r--r--src/yuzu/bootmanager.h19
-rw-r--r--src/yuzu/check_vulkan.cpp53
-rw-r--r--src/yuzu/check_vulkan.h6
-rw-r--r--src/yuzu/compatdb.cpp5
-rw-r--r--src/yuzu/compatdb.h5
-rw-r--r--src/yuzu/configuration/config.cpp100
-rw-r--r--src/yuzu/configuration/config.h9
-rw-r--r--src/yuzu/configuration/configuration_shared.cpp5
-rw-r--r--src/yuzu/configuration/configuration_shared.h5
-rw-r--r--src/yuzu/configuration/configure_camera.cpp138
-rw-r--r--src/yuzu/configuration/configure_camera.h54
-rw-r--r--src/yuzu/configuration/configure_camera.ui170
-rw-r--r--src/yuzu/configuration/configure_debug.cpp5
-rw-r--r--src/yuzu/configuration/configure_debug.h5
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp13
-rw-r--r--src/yuzu/configuration/configure_dialog.h8
-rw-r--r--src/yuzu/configuration/configure_general.cpp5
-rw-r--r--src/yuzu/configuration/configure_general.h5
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp42
-rw-r--r--src/yuzu/configuration/configure_graphics.h7
-rw-r--r--src/yuzu/configuration/configure_graphics.ui9
-rw-r--r--src/yuzu/configuration/configure_hotkeys.cpp5
-rw-r--r--src/yuzu/configuration/configure_hotkeys.h5
-rw-r--r--src/yuzu/configuration/configure_input.cpp10
-rw-r--r--src/yuzu/configuration/configure_input.h5
-rw-r--r--src/yuzu/configuration/configure_input_advanced.cpp3
-rw-r--r--src/yuzu/configuration/configure_input_advanced.h1
-rw-r--r--src/yuzu/configuration/configure_input_advanced.ui14
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp5
-rw-r--r--src/yuzu/configuration/configure_input_player.h5
-rw-r--r--src/yuzu/configuration/configure_motion_touch.cpp5
-rw-r--r--src/yuzu/configuration/configure_motion_touch.h5
-rw-r--r--src/yuzu/configuration/configure_network.cpp2
-rw-r--r--src/yuzu/configuration/configure_per_game_addons.cpp5
-rw-r--r--src/yuzu/configuration/configure_per_game_addons.h5
-rw-r--r--src/yuzu/configuration/configure_profile_manager.cpp5
-rw-r--r--src/yuzu/configuration/configure_profile_manager.h5
-rw-r--r--src/yuzu/configuration/configure_system.cpp5
-rw-r--r--src/yuzu/configuration/configure_system.h5
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.cpp5
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.h5
-rw-r--r--src/yuzu/configuration/configure_touch_widget.h5
-rw-r--r--src/yuzu/configuration/configure_touchscreen_advanced.cpp5
-rw-r--r--src/yuzu/configuration/configure_touchscreen_advanced.h5
-rw-r--r--src/yuzu/configuration/configure_ui.cpp5
-rw-r--r--src/yuzu/configuration/configure_ui.h5
-rw-r--r--src/yuzu/configuration/configure_web.cpp10
-rw-r--r--src/yuzu/configuration/configure_web.h6
-rw-r--r--src/yuzu/configuration/configure_web.ui10
-rw-r--r--src/yuzu/debugger/controller.cpp5
-rw-r--r--src/yuzu/debugger/controller.h5
-rw-r--r--src/yuzu/debugger/profiler.cpp5
-rw-r--r--src/yuzu/debugger/profiler.h5
-rw-r--r--src/yuzu/debugger/wait_tree.cpp5
-rw-r--r--src/yuzu/debugger/wait_tree.h5
-rw-r--r--src/yuzu/discord.h5
-rw-r--r--src/yuzu/discord_impl.cpp5
-rw-r--r--src/yuzu/discord_impl.h5
-rw-r--r--src/yuzu/game_list.cpp11
-rw-r--r--src/yuzu/game_list.h13
-rw-r--r--src/yuzu/game_list_p.h5
-rw-r--r--src/yuzu/hotkeys.cpp5
-rw-r--r--src/yuzu/hotkeys.h5
-rw-r--r--src/yuzu/main.cpp143
-rw-r--r--src/yuzu/main.h23
-rw-r--r--src/yuzu/main.ui38
-rw-r--r--src/yuzu/multiplayer/chat_room.cpp491
-rw-r--r--src/yuzu/multiplayer/chat_room.h75
-rw-r--r--src/yuzu/multiplayer/chat_room.ui59
-rw-r--r--src/yuzu/multiplayer/client_room.cpp115
-rw-r--r--src/yuzu/multiplayer/client_room.h39
-rw-r--r--src/yuzu/multiplayer/client_room.ui80
-rw-r--r--src/yuzu/multiplayer/direct_connect.cpp130
-rw-r--r--src/yuzu/multiplayer/direct_connect.h43
-rw-r--r--src/yuzu/multiplayer/direct_connect.ui168
-rw-r--r--src/yuzu/multiplayer/host_room.cpp246
-rw-r--r--src/yuzu/multiplayer/host_room.h75
-rw-r--r--src/yuzu/multiplayer/host_room.ui207
-rw-r--r--src/yuzu/multiplayer/lobby.cpp367
-rw-r--r--src/yuzu/multiplayer/lobby.h128
-rw-r--r--src/yuzu/multiplayer/lobby.ui123
-rw-r--r--src/yuzu/multiplayer/lobby_p.h238
-rw-r--r--src/yuzu/multiplayer/message.cpp78
-rw-r--r--src/yuzu/multiplayer/message.h64
-rw-r--r--src/yuzu/multiplayer/moderation_dialog.cpp112
-rw-r--r--src/yuzu/multiplayer/moderation_dialog.h43
-rw-r--r--src/yuzu/multiplayer/moderation_dialog.ui84
-rw-r--r--src/yuzu/multiplayer/state.cpp308
-rw-r--r--src/yuzu/multiplayer/state.h92
-rw-r--r--src/yuzu/multiplayer/validation.h48
-rw-r--r--src/yuzu/startup_checks.cpp136
-rw-r--r--src/yuzu/startup_checks.h17
-rw-r--r--src/yuzu/uisettings.cpp5
-rw-r--r--src/yuzu/uisettings.h20
-rw-r--r--src/yuzu/util/clickable_label.cpp11
-rw-r--r--src/yuzu/util/clickable_label.h21
-rw-r--r--src/yuzu/util/sequence_dialog/sequence_dialog.cpp5
-rw-r--r--src/yuzu/util/sequence_dialog/sequence_dialog.h5
-rw-r--r--src/yuzu/util/util.cpp5
-rw-r--r--src/yuzu/util/util.h5
-rw-r--r--src/yuzu/yuzu.qrc5
-rw-r--r--src/yuzu/yuzu.rc3
-rw-r--r--src/yuzu_cmd/CMakeLists.txt3
-rw-r--r--src/yuzu_cmd/config.cpp5
-rw-r--r--src/yuzu_cmd/config.h5
-rw-r--r--src/yuzu_cmd/default_ini.h5
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp5
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.h5
-rw-r--r--src/yuzu_cmd/yuzu.cpp163
-rw-r--r--src/yuzu_cmd/yuzu.rc3
291 files changed, 11040 insertions, 1562 deletions
diff --git a/src/.clang-format b/src/.clang-format
index 1c6b71b2e..f92771ec3 100644
--- a/src/.clang-format
+++ b/src/.clang-format
@@ -1,3 +1,6 @@
1# SPDX-FileCopyrightText: 2016 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
2# SPDX-License-Identifier: GPL-2.0-or-later
3
1--- 4---
2Language: Cpp 5Language: Cpp
3# BasedOnStyle: LLVM 6# BasedOnStyle: LLVM
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 39ae573b2..fc177fa52 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,3 +1,6 @@
1# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-2.0-or-later
3
1# Enable modules to include each other's files 4# Enable modules to include each other's files
2include_directories(.) 5include_directories(.)
3 6
@@ -156,6 +159,7 @@ add_subdirectory(common)
156add_subdirectory(core) 159add_subdirectory(core)
157add_subdirectory(audio_core) 160add_subdirectory(audio_core)
158add_subdirectory(video_core) 161add_subdirectory(video_core)
162add_subdirectory(network)
159add_subdirectory(input_common) 163add_subdirectory(input_common)
160add_subdirectory(shader_recompiler) 164add_subdirectory(shader_recompiler)
161 165
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 2971c42a2..5fe1d5fa5 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -1,3 +1,6 @@
1# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-2.0-or-later
3
1add_library(audio_core STATIC 4add_library(audio_core STATIC
2 audio_core.cpp 5 audio_core.cpp
3 audio_core.h 6 audio_core.h
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index d574e4b79..a6dc31b53 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -1,3 +1,6 @@
1# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-2.0-or-later
3
1if (DEFINED ENV{AZURECIREPO}) 4if (DEFINED ENV{AZURECIREPO})
2 set(BUILD_REPOSITORY $ENV{AZURECIREPO}) 5 set(BUILD_REPOSITORY $ENV{AZURECIREPO})
3endif() 6endif()
@@ -41,6 +44,7 @@ add_custom_command(OUTPUT scm_rev.cpp
41add_library(common STATIC 44add_library(common STATIC
42 algorithm.h 45 algorithm.h
43 alignment.h 46 alignment.h
47 announce_multiplayer_room.h
44 assert.cpp 48 assert.cpp
45 assert.h 49 assert.h
46 atomic_helpers.h 50 atomic_helpers.h
diff --git a/src/common/alignment.h b/src/common/alignment.h
index 8570c7d3c..7e897334b 100644
--- a/src/common/alignment.h
+++ b/src/common/alignment.h
@@ -1,4 +1,5 @@
1// This file is under the public domain. 1// SPDX-FileCopyrightText: 2014 Jannik Vogel <email@jannikvogel.de>
2// SPDX-License-Identifier: CC0-1.0
2 3
3#pragma once 4#pragma once
4 5
diff --git a/src/common/announce_multiplayer_room.h b/src/common/announce_multiplayer_room.h
new file mode 100644
index 000000000..0ad9da2be
--- /dev/null
+++ b/src/common/announce_multiplayer_room.h
@@ -0,0 +1,143 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <functional>
8#include <string>
9#include <vector>
10#include "common/common_types.h"
11#include "web_service/web_result.h"
12
13namespace AnnounceMultiplayerRoom {
14
15using MacAddress = std::array<u8, 6>;
16
17struct GameInfo {
18 std::string name{""};
19 u64 id{0};
20};
21
22struct Member {
23 std::string username;
24 std::string nickname;
25 std::string display_name;
26 std::string avatar_url;
27 MacAddress mac_address;
28 GameInfo game;
29};
30
31struct RoomInformation {
32 std::string name; ///< Name of the server
33 std::string description; ///< Server description
34 u32 member_slots; ///< Maximum number of members in this room
35 u16 port; ///< The port of this room
36 GameInfo preferred_game; ///< Game to advertise that you want to play
37 std::string host_username; ///< Forum username of the host
38 bool enable_yuzu_mods; ///< Allow yuzu Moderators to moderate on this room
39};
40
41struct Room {
42 RoomInformation information;
43
44 std::string id;
45 std::string verify_uid; ///< UID used for verification
46 std::string ip;
47 u32 net_version;
48 bool has_password;
49
50 std::vector<Member> members;
51};
52using RoomList = std::vector<Room>;
53
54/**
55 * A AnnounceMultiplayerRoom interface class. A backend to submit/get to/from a web service should
56 * implement this interface.
57 */
58class Backend {
59public:
60 virtual ~Backend() = default;
61
62 /**
63 * Sets the Information that gets used for the announce
64 * @param uid The Id of the room
65 * @param name The name of the room
66 * @param description The room description
67 * @param port The port of the room
68 * @param net_version The version of the libNetwork that gets used
69 * @param has_password True if the room is passowrd protected
70 * @param preferred_game The preferred game of the room
71 * @param preferred_game_id The title id of the preferred game
72 */
73 virtual void SetRoomInformation(const std::string& name, const std::string& description,
74 const u16 port, const u32 max_player, const u32 net_version,
75 const bool has_password, const GameInfo& preferred_game) = 0;
76 /**
77 * Adds a player information to the data that gets announced
78 * @param nickname The nickname of the player
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 */
83 virtual void AddPlayer(const Member& member) = 0;
84
85 /**
86 * Updates the data in the announce service. Re-register the room when required.
87 * @result The result of the update attempt
88 */
89 virtual WebService::WebResult Update() = 0;
90
91 /**
92 * Registers the data in the announce service
93 * @result The result of the register attempt. When the result code is Success, A global Guid of
94 * the room which may be used for verification will be in the result's returned_data.
95 */
96 virtual WebService::WebResult Register() = 0;
97
98 /**
99 * Empties the stored players
100 */
101 virtual void ClearPlayers() = 0;
102
103 /**
104 * Get the room information from the announce service
105 * @result A list of all rooms the announce service has
106 */
107 virtual RoomList GetRoomList() = 0;
108
109 /**
110 * Sends a delete message to the announce service
111 */
112 virtual void Delete() = 0;
113};
114
115/**
116 * Empty implementation of AnnounceMultiplayerRoom interface that drops all data. Used when a
117 * functional backend implementation is not available.
118 */
119class NullBackend : public Backend {
120public:
121 ~NullBackend() = default;
122 void SetRoomInformation(const std::string& /*name*/, const std::string& /*description*/,
123 const u16 /*port*/, const u32 /*max_player*/, const u32 /*net_version*/,
124 const bool /*has_password*/,
125 const GameInfo& /*preferred_game*/) override {}
126 void AddPlayer(const Member& /*member*/) override {}
127 WebService::WebResult Update() override {
128 return WebService::WebResult{WebService::WebResult::Code::NoWebservice,
129 "WebService is missing", ""};
130 }
131 WebService::WebResult Register() override {
132 return WebService::WebResult{WebService::WebResult::Code::NoWebservice,
133 "WebService is missing", ""};
134 }
135 void ClearPlayers() override {}
136 RoomList GetRoomList() override {
137 return RoomList{};
138 }
139
140 void Delete() override {}
141};
142
143} // namespace AnnounceMultiplayerRoom
diff --git a/src/common/atomic_helpers.h b/src/common/atomic_helpers.h
index 6d912b52e..bef5015c1 100644
--- a/src/common/atomic_helpers.h
+++ b/src/common/atomic_helpers.h
@@ -1,4 +1,7 @@
1// ©2013-2016 Cameron Desrochers. 1// SPDX-FileCopyrightText: 2013-2016 Cameron Desrochers
2// SPDX-FileCopyrightText: 2015 Jeff Preshing
3// SPDX-License-Identifier: BSD-2-Clause AND Zlib
4
2// Distributed under the simplified BSD license (see the license file that 5// Distributed under the simplified BSD license (see the license file that
3// should have come with this header). 6// should have come with this header).
4// Uses Jeff Preshing's semaphore implementation (under the terms of its 7// Uses Jeff Preshing's semaphore implementation (under the terms of its
diff --git a/src/common/detached_tasks.cpp b/src/common/detached_tasks.cpp
index ec31d0b88..da64848da 100644
--- a/src/common/detached_tasks.cpp
+++ b/src/common/detached_tasks.cpp
@@ -1,6 +1,5 @@
1// Copyright 2018 Citra Emulator Project 1// SPDX-FileCopyrightText: 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <thread> 4#include <thread>
6#include "common/assert.h" 5#include "common/assert.h"
diff --git a/src/common/detached_tasks.h b/src/common/detached_tasks.h
index 5dd8fc27b..416a2d7f3 100644
--- a/src/common/detached_tasks.h
+++ b/src/common/detached_tasks.h
@@ -1,6 +1,5 @@
1// Copyright 2018 Citra Emulator Project 1// SPDX-FileCopyrightText: 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/common/error.cpp b/src/common/error.cpp
index d4455e310..ddb03bd45 100644
--- a/src/common/error.cpp
+++ b/src/common/error.cpp
@@ -1,6 +1,6 @@
1// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-FileCopyrightText: 2014 Citra Emulator Project
3// Refer to the license.txt file included. 3// SPDX-License-Identifier: GPL-2.0-or-later
4 4
5#include <cstddef> 5#include <cstddef>
6#ifdef _WIN32 6#ifdef _WIN32
diff --git a/src/common/error.h b/src/common/error.h
index e084d4b0f..62a3bd835 100644
--- a/src/common/error.h
+++ b/src/common/error.h
@@ -1,6 +1,6 @@
1// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-FileCopyrightText: 2014 Citra Emulator Project
3// Refer to the license.txt file included. 3// SPDX-License-Identifier: GPL-2.0-or-later
4 4
5#pragma once 5#pragma once
6 6
diff --git a/src/common/fixed_point.h b/src/common/fixed_point.h
index 1d45e51b3..4a0f72cc9 100644
--- a/src/common/fixed_point.h
+++ b/src/common/fixed_point.h
@@ -1,28 +1,8 @@
1// SPDX-FileCopyrightText: 2015 Evan Teran
2// SPDX-License-Identifier: MIT
3
1// From: https://github.com/eteran/cpp-utilities/blob/master/fixed/include/cpp-utilities/fixed.h 4// From: https://github.com/eteran/cpp-utilities/blob/master/fixed/include/cpp-utilities/fixed.h
2// See also: http://stackoverflow.com/questions/79677/whats-the-best-way-to-do-fixed-point-math 5// See also: http://stackoverflow.com/questions/79677/whats-the-best-way-to-do-fixed-point-math
3/*
4 * The MIT License (MIT)
5 *
6 * Copyright (c) 2015 Evan Teran
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in all
16 * copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 * SOFTWARE.
25 */
26 6
27#ifndef FIXED_H_ 7#ifndef FIXED_H_
28#define FIXED_H_ 8#define FIXED_H_
diff --git a/src/common/hash.h b/src/common/hash.h
index 298930702..b6f3e6d6f 100644
--- a/src/common/hash.h
+++ b/src/common/hash.h
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/common/input.h b/src/common/input.h
index bb42aaacc..213aa2384 100644
--- a/src/common/input.h
+++ b/src/common/input.h
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
@@ -28,7 +27,7 @@ enum class InputType {
28 Color, 27 Color,
29 Vibration, 28 Vibration,
30 Nfc, 29 Nfc,
31 Ir, 30 IrSensor,
32}; 31};
33 32
34// Internal battery charge level 33// Internal battery charge level
@@ -53,6 +52,15 @@ enum class PollingMode {
53 IR, 52 IR,
54}; 53};
55 54
55enum class CameraFormat {
56 Size320x240,
57 Size160x120,
58 Size80x60,
59 Size40x30,
60 Size20x15,
61 None,
62};
63
56// Vibration reply from the controller 64// Vibration reply from the controller
57enum class VibrationError { 65enum class VibrationError {
58 None, 66 None,
@@ -68,6 +76,13 @@ enum class PollingError {
68 Unknown, 76 Unknown,
69}; 77};
70 78
79// Ir camera reply from the controller
80enum class CameraError {
81 None,
82 NotSupported,
83 Unknown,
84};
85
71// Hint for amplification curve to be used 86// Hint for amplification curve to be used
72enum class VibrationAmplificationType { 87enum class VibrationAmplificationType {
73 Linear, 88 Linear,
@@ -176,6 +191,12 @@ struct LedStatus {
176 bool led_4{}; 191 bool led_4{};
177}; 192};
178 193
194// Raw data fom camera
195struct CameraStatus {
196 CameraFormat format{CameraFormat::None};
197 std::vector<u8> data{};
198};
199
179// List of buttons to be passed to Qt that can be translated 200// List of buttons to be passed to Qt that can be translated
180enum class ButtonNames { 201enum class ButtonNames {
181 Undefined, 202 Undefined,
@@ -233,6 +254,7 @@ struct CallbackStatus {
233 BodyColorStatus color_status{}; 254 BodyColorStatus color_status{};
234 BatteryStatus battery_status{}; 255 BatteryStatus battery_status{};
235 VibrationStatus vibration_status{}; 256 VibrationStatus vibration_status{};
257 CameraStatus camera_status{};
236}; 258};
237 259
238// Triggered once every input change 260// Triggered once every input change
@@ -281,6 +303,10 @@ public:
281 virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) { 303 virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) {
282 return PollingError::NotSupported; 304 return PollingError::NotSupported;
283 } 305 }
306
307 virtual CameraError SetCameraFormat([[maybe_unused]] CameraFormat camera_format) {
308 return CameraError::NotSupported;
309 }
284}; 310};
285 311
286/// An abstract class template for a factory that can create input devices. 312/// An abstract class template for a factory that can create input devices.
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index b3793106d..8ce1c2fd1 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <atomic> 4#include <atomic>
6#include <chrono> 5#include <chrono>
diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h
index a0e80fe3c..12e5e2498 100644
--- a/src/common/logging/backend.h
+++ b/src/common/logging/backend.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp
index 6de9bacbf..a959acb74 100644
--- a/src/common/logging/filter.cpp
+++ b/src/common/logging/filter.cpp
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <algorithm> 4#include <algorithm>
6#include "common/logging/filter.h" 5#include "common/logging/filter.h"
diff --git a/src/common/logging/filter.h b/src/common/logging/filter.h
index 29419f051..54d172cc0 100644
--- a/src/common/logging/filter.h
+++ b/src/common/logging/filter.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 0c80d01ee..c00c01a9e 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp
index b2cad58d8..09398ea64 100644
--- a/src/common/logging/text_formatter.cpp
+++ b/src/common/logging/text_formatter.cpp
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <array> 4#include <array>
6#include <cstdio> 5#include <cstdio>
diff --git a/src/common/logging/text_formatter.h b/src/common/logging/text_formatter.h
index 92c0bf0c5..0d0ec4370 100644
--- a/src/common/logging/text_formatter.h
+++ b/src/common/logging/text_formatter.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/common/microprofile.cpp b/src/common/microprofile.cpp
index ee25dd37f..e6657c82f 100644
--- a/src/common/microprofile.cpp
+++ b/src/common/microprofile.cpp
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5// Includes the MicroProfile implementation in this file for compilation 4// Includes the MicroProfile implementation in this file for compilation
6#define MICROPROFILE_IMPL 1 5#define MICROPROFILE_IMPL 1
diff --git a/src/common/microprofile.h b/src/common/microprofile.h
index 54e7f3cc4..91d14d5e1 100644
--- a/src/common/microprofile.h
+++ b/src/common/microprofile.h
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/common/microprofileui.h b/src/common/microprofileui.h
index 41abe6b75..39ed18ffa 100644
--- a/src/common/microprofileui.h
+++ b/src/common/microprofileui.h
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/common/param_package.cpp b/src/common/param_package.cpp
index 462502e34..629babb81 100644
--- a/src/common/param_package.cpp
+++ b/src/common/param_package.cpp
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <array> 4#include <array>
6#include <stdexcept> 5#include <stdexcept>
diff --git a/src/common/param_package.h b/src/common/param_package.h
index c13e45479..d7c13cb1f 100644
--- a/src/common/param_package.h
+++ b/src/common/param_package.h
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/common/quaternion.h b/src/common/quaternion.h
index 4d0871eb4..5bb5f2af0 100644
--- a/src/common/quaternion.h
+++ b/src/common/quaternion.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/common/reader_writer_queue.h b/src/common/reader_writer_queue.h
index 8d2c9408c..60c41a8cb 100644
--- a/src/common/reader_writer_queue.h
+++ b/src/common/reader_writer_queue.h
@@ -1,6 +1,5 @@
1// ©2013-2020 Cameron Desrochers. 1// SPDX-FileCopyrightText: 2013-2020 Cameron Desrochers
2// Distributed under the simplified BSD license (see the license file that 2// SPDX-License-Identifier: BSD-2-Clause
3// should have come with this header).
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/common/scm_rev.cpp.in b/src/common/scm_rev.cpp.in
index cc88994c6..f0c124d69 100644
--- a/src/common/scm_rev.cpp.in
+++ b/src/common/scm_rev.cpp.in
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include "common/scm_rev.h" 4#include "common/scm_rev.h"
6 5
diff --git a/src/common/scm_rev.h b/src/common/scm_rev.h
index 563015ec9..88404316a 100644
--- a/src/common/scm_rev.h
+++ b/src/common/scm_rev.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/common/scope_exit.h b/src/common/scope_exit.h
index 35dac3a8f..e9c789c88 100644
--- a/src/common/scope_exit.h
+++ b/src/common/scope_exit.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/common/settings.h b/src/common/settings.h
index 06d72c8bf..1079cf8cb 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -503,6 +503,9 @@ struct Values {
503 Setting<bool> enable_ring_controller{true, "enable_ring_controller"}; 503 Setting<bool> enable_ring_controller{true, "enable_ring_controller"};
504 RingconRaw ringcon_analogs; 504 RingconRaw ringcon_analogs;
505 505
506 Setting<bool> enable_ir_sensor{false, "enable_ir_sensor"};
507 Setting<std::string> ir_sensor_device{"auto", "ir_sensor_device"};
508
506 // Data Storage 509 // Data Storage
507 Setting<bool> use_virtual_sd{true, "use_virtual_sd"}; 510 Setting<bool> use_virtual_sd{true, "use_virtual_sd"};
508 Setting<bool> gamecard_inserted{false, "gamecard_inserted"}; 511 Setting<bool> gamecard_inserted{false, "gamecard_inserted"};
diff --git a/src/common/telemetry.cpp b/src/common/telemetry.cpp
index 67261c55b..d26394359 100644
--- a/src/common/telemetry.cpp
+++ b/src/common/telemetry.cpp
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <algorithm> 4#include <algorithm>
6#include <cstring> 5#include <cstring>
diff --git a/src/common/telemetry.h b/src/common/telemetry.h
index f9a824a7d..ba633d5a5 100644
--- a/src/common/telemetry.h
+++ b/src/common/telemetry.h
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/common/x64/xbyak_abi.h b/src/common/x64/xbyak_abi.h
index 87b3d63a4..67e6e63c8 100644
--- a/src/common/x64/xbyak_abi.h
+++ b/src/common/x64/xbyak_abi.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/common/x64/xbyak_util.h b/src/common/x64/xbyak_util.h
index 44d2558f1..250e5cddb 100644
--- a/src/common/x64/xbyak_util.h
+++ b/src/common/x64/xbyak_util.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 11d554bad..a614b176b 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -1,4 +1,9 @@
1# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-2.0-or-later
3
1add_library(core STATIC 4add_library(core STATIC
5 announce_multiplayer_session.cpp
6 announce_multiplayer_session.h
2 arm/arm_interface.h 7 arm/arm_interface.h
3 arm/arm_interface.cpp 8 arm/arm_interface.cpp
4 arm/cpu_interrupt_handler.cpp 9 arm/cpu_interrupt_handler.cpp
@@ -158,6 +163,7 @@ add_library(core STATIC
158 hid/input_converter.h 163 hid/input_converter.h
159 hid/input_interpreter.cpp 164 hid/input_interpreter.cpp
160 hid/input_interpreter.h 165 hid/input_interpreter.h
166 hid/irs_types.h
161 hid/motion_input.cpp 167 hid/motion_input.cpp
162 hid/motion_input.h 168 hid/motion_input.h
163 hle/api_version.h 169 hle/api_version.h
@@ -477,6 +483,20 @@ add_library(core STATIC
477 hle/service/hid/hidbus/starlink.h 483 hle/service/hid/hidbus/starlink.h
478 hle/service/hid/hidbus/stubbed.cpp 484 hle/service/hid/hidbus/stubbed.cpp
479 hle/service/hid/hidbus/stubbed.h 485 hle/service/hid/hidbus/stubbed.h
486 hle/service/hid/irsensor/clustering_processor.cpp
487 hle/service/hid/irsensor/clustering_processor.h
488 hle/service/hid/irsensor/image_transfer_processor.cpp
489 hle/service/hid/irsensor/image_transfer_processor.h
490 hle/service/hid/irsensor/ir_led_processor.cpp
491 hle/service/hid/irsensor/ir_led_processor.h
492 hle/service/hid/irsensor/moment_processor.cpp
493 hle/service/hid/irsensor/moment_processor.h
494 hle/service/hid/irsensor/pointing_processor.cpp
495 hle/service/hid/irsensor/pointing_processor.h
496 hle/service/hid/irsensor/processor_base.cpp
497 hle/service/hid/irsensor/processor_base.h
498 hle/service/hid/irsensor/tera_plugin_processor.cpp
499 hle/service/hid/irsensor/tera_plugin_processor.h
480 hle/service/jit/jit_context.cpp 500 hle/service/jit/jit_context.cpp
481 hle/service/jit/jit_context.h 501 hle/service/jit/jit_context.h
482 hle/service/jit/jit.cpp 502 hle/service/jit/jit.cpp
@@ -699,6 +719,11 @@ add_library(core STATIC
699 hle/service/vi/vi_u.h 719 hle/service/vi/vi_u.h
700 hle/service/wlan/wlan.cpp 720 hle/service/wlan/wlan.cpp
701 hle/service/wlan/wlan.h 721 hle/service/wlan/wlan.h
722 internal_network/network.cpp
723 internal_network/network.h
724 internal_network/network_interface.cpp
725 internal_network/network_interface.h
726 internal_network/sockets.h
702 loader/deconstructed_rom_directory.cpp 727 loader/deconstructed_rom_directory.cpp
703 loader/deconstructed_rom_directory.h 728 loader/deconstructed_rom_directory.h
704 loader/elf.cpp 729 loader/elf.cpp
@@ -726,11 +751,6 @@ add_library(core STATIC
726 memory/dmnt_cheat_vm.h 751 memory/dmnt_cheat_vm.h
727 memory.cpp 752 memory.cpp
728 memory.h 753 memory.h
729 network/network.cpp
730 network/network.h
731 network/network_interface.cpp
732 network/network_interface.h
733 network/sockets.h
734 perf_stats.cpp 754 perf_stats.cpp
735 perf_stats.h 755 perf_stats.h
736 reporter.cpp 756 reporter.cpp
@@ -765,7 +785,7 @@ endif()
765 785
766create_target_directory_groups(core) 786create_target_directory_groups(core)
767 787
768target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) 788target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core)
769target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::Opus) 789target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::Opus)
770if (MINGW) 790if (MINGW)
771 target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) 791 target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})
diff --git a/src/core/announce_multiplayer_session.cpp b/src/core/announce_multiplayer_session.cpp
new file mode 100644
index 000000000..d73a488cf
--- /dev/null
+++ b/src/core/announce_multiplayer_session.cpp
@@ -0,0 +1,164 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <chrono>
5#include <future>
6#include <vector>
7#include "announce_multiplayer_session.h"
8#include "common/announce_multiplayer_room.h"
9#include "common/assert.h"
10#include "common/settings.h"
11#include "network/network.h"
12
13#ifdef ENABLE_WEB_SERVICE
14#include "web_service/announce_room_json.h"
15#endif
16
17namespace Core {
18
19// Time between room is announced to web_service
20static constexpr std::chrono::seconds announce_time_interval(15);
21
22AnnounceMultiplayerSession::AnnounceMultiplayerSession(Network::RoomNetwork& room_network_)
23 : room_network{room_network_} {
24#ifdef ENABLE_WEB_SERVICE
25 backend = std::make_unique<WebService::RoomJson>(Settings::values.web_api_url.GetValue(),
26 Settings::values.yuzu_username.GetValue(),
27 Settings::values.yuzu_token.GetValue());
28#else
29 backend = std::make_unique<AnnounceMultiplayerRoom::NullBackend>();
30#endif
31}
32
33WebService::WebResult AnnounceMultiplayerSession::Register() {
34 std::shared_ptr<Network::Room> room = room_network.GetRoom().lock();
35 if (!room) {
36 return WebService::WebResult{WebService::WebResult::Code::LibError,
37 "Network is not initialized", ""};
38 }
39 if (room->GetState() != Network::Room::State::Open) {
40 return WebService::WebResult{WebService::WebResult::Code::LibError, "Room is not open", ""};
41 }
42 UpdateBackendData(room);
43 WebService::WebResult result = backend->Register();
44 if (result.result_code != WebService::WebResult::Code::Success) {
45 return result;
46 }
47 LOG_INFO(WebService, "Room has been registered");
48 room->SetVerifyUID(result.returned_data);
49 registered = true;
50 return WebService::WebResult{WebService::WebResult::Code::Success, "", ""};
51}
52
53void AnnounceMultiplayerSession::Start() {
54 if (announce_multiplayer_thread) {
55 Stop();
56 }
57 shutdown_event.Reset();
58 announce_multiplayer_thread =
59 std::make_unique<std::thread>(&AnnounceMultiplayerSession::AnnounceMultiplayerLoop, this);
60}
61
62void AnnounceMultiplayerSession::Stop() {
63 if (announce_multiplayer_thread) {
64 shutdown_event.Set();
65 announce_multiplayer_thread->join();
66 announce_multiplayer_thread.reset();
67 backend->Delete();
68 registered = false;
69 }
70}
71
72AnnounceMultiplayerSession::CallbackHandle AnnounceMultiplayerSession::BindErrorCallback(
73 std::function<void(const WebService::WebResult&)> function) {
74 std::lock_guard lock(callback_mutex);
75 auto handle = std::make_shared<std::function<void(const WebService::WebResult&)>>(function);
76 error_callbacks.insert(handle);
77 return handle;
78}
79
80void AnnounceMultiplayerSession::UnbindErrorCallback(CallbackHandle handle) {
81 std::lock_guard lock(callback_mutex);
82 error_callbacks.erase(handle);
83}
84
85AnnounceMultiplayerSession::~AnnounceMultiplayerSession() {
86 Stop();
87}
88
89void AnnounceMultiplayerSession::UpdateBackendData(std::shared_ptr<Network::Room> room) {
90 Network::RoomInformation room_information = room->GetRoomInformation();
91 std::vector<AnnounceMultiplayerRoom::Member> memberlist = room->GetRoomMemberList();
92 backend->SetRoomInformation(room_information.name, room_information.description,
93 room_information.port, room_information.member_slots,
94 Network::network_version, room->HasPassword(),
95 room_information.preferred_game);
96 backend->ClearPlayers();
97 for (const auto& member : memberlist) {
98 backend->AddPlayer(member);
99 }
100}
101
102void AnnounceMultiplayerSession::AnnounceMultiplayerLoop() {
103 // Invokes all current bound error callbacks.
104 const auto ErrorCallback = [this](WebService::WebResult result) {
105 std::lock_guard<std::mutex> lock(callback_mutex);
106 for (auto callback : error_callbacks) {
107 (*callback)(result);
108 }
109 };
110
111 if (!registered) {
112 WebService::WebResult result = Register();
113 if (result.result_code != WebService::WebResult::Code::Success) {
114 ErrorCallback(result);
115 return;
116 }
117 }
118
119 auto update_time = std::chrono::steady_clock::now();
120 std::future<WebService::WebResult> future;
121 while (!shutdown_event.WaitUntil(update_time)) {
122 update_time += announce_time_interval;
123 std::shared_ptr<Network::Room> room = room_network.GetRoom().lock();
124 if (!room) {
125 break;
126 }
127 if (room->GetState() != Network::Room::State::Open) {
128 break;
129 }
130 UpdateBackendData(room);
131 WebService::WebResult result = backend->Update();
132 if (result.result_code != WebService::WebResult::Code::Success) {
133 ErrorCallback(result);
134 }
135 if (result.result_string == "404") {
136 registered = false;
137 // Needs to register the room again
138 WebService::WebResult register_result = Register();
139 if (register_result.result_code != WebService::WebResult::Code::Success) {
140 ErrorCallback(register_result);
141 }
142 }
143 }
144}
145
146AnnounceMultiplayerRoom::RoomList AnnounceMultiplayerSession::GetRoomList() {
147 return backend->GetRoomList();
148}
149
150bool AnnounceMultiplayerSession::IsRunning() const {
151 return announce_multiplayer_thread != nullptr;
152}
153
154void AnnounceMultiplayerSession::UpdateCredentials() {
155 ASSERT_MSG(!IsRunning(), "Credentials can only be updated when session is not running");
156
157#ifdef ENABLE_WEB_SERVICE
158 backend = std::make_unique<WebService::RoomJson>(Settings::values.web_api_url.GetValue(),
159 Settings::values.yuzu_username.GetValue(),
160 Settings::values.yuzu_token.GetValue());
161#endif
162}
163
164} // namespace Core
diff --git a/src/core/announce_multiplayer_session.h b/src/core/announce_multiplayer_session.h
new file mode 100644
index 000000000..db790f7d2
--- /dev/null
+++ b/src/core/announce_multiplayer_session.h
@@ -0,0 +1,98 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <atomic>
7#include <functional>
8#include <memory>
9#include <mutex>
10#include <set>
11#include <thread>
12#include "common/announce_multiplayer_room.h"
13#include "common/common_types.h"
14#include "common/thread.h"
15
16namespace Network {
17class Room;
18class RoomNetwork;
19} // namespace Network
20
21namespace Core {
22
23/**
24 * Instruments AnnounceMultiplayerRoom::Backend.
25 * Creates a thread that regularly updates the room information and submits them
26 * An async get of room information is also possible
27 */
28class AnnounceMultiplayerSession {
29public:
30 using CallbackHandle = std::shared_ptr<std::function<void(const WebService::WebResult&)>>;
31 AnnounceMultiplayerSession(Network::RoomNetwork& room_network_);
32 ~AnnounceMultiplayerSession();
33
34 /**
35 * Allows to bind a function that will get called if the announce encounters an error
36 * @param function The function that gets called
37 * @return A handle that can be used the unbind the function
38 */
39 CallbackHandle BindErrorCallback(std::function<void(const WebService::WebResult&)> function);
40
41 /**
42 * Unbind a function from the error callbacks
43 * @param handle The handle for the function that should get unbind
44 */
45 void UnbindErrorCallback(CallbackHandle handle);
46
47 /**
48 * Registers a room to web services
49 * @return The result of the registration attempt.
50 */
51 WebService::WebResult Register();
52
53 /**
54 * Starts the announce of a room to web services
55 */
56 void Start();
57
58 /**
59 * Stops the announce to web services
60 */
61 void Stop();
62
63 /**
64 * Returns a list of all room information the backend got
65 * @param func A function that gets executed when the async get finished, e.g. a signal
66 * @return a list of rooms received from the web service
67 */
68 AnnounceMultiplayerRoom::RoomList GetRoomList();
69
70 /**
71 * Whether the announce session is still running
72 */
73 bool IsRunning() const;
74
75 /**
76 * Recreates the backend, updating the credentials.
77 * This can only be used when the announce session is not running.
78 */
79 void UpdateCredentials();
80
81private:
82 void UpdateBackendData(std::shared_ptr<Network::Room> room);
83 void AnnounceMultiplayerLoop();
84
85 Common::Event shutdown_event;
86 std::mutex callback_mutex;
87 std::set<CallbackHandle> error_callbacks;
88 std::unique_ptr<std::thread> announce_multiplayer_thread;
89
90 /// Backend interface that logs fields
91 std::unique_ptr<AnnounceMultiplayerRoom::Backend> backend;
92
93 std::atomic_bool registered = false; ///< Whether the room has been registered
94
95 Network::RoomNetwork& room_network;
96};
97
98} // namespace Core
diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp
index e72b250be..953d96439 100644
--- a/src/core/arm/arm_interface.cpp
+++ b/src/core/arm/arm_interface.cpp
@@ -154,9 +154,10 @@ void ARM_Interface::Run() {
154 break; 154 break;
155 } 155 }
156 156
157 // Handle syscalls and scheduling (this may change the current thread) 157 // Handle syscalls and scheduling (this may change the current thread/core)
158 if (Has(hr, svc_call)) { 158 if (Has(hr, svc_call)) {
159 Kernel::Svc::Call(system, GetSvcNumber()); 159 Kernel::Svc::Call(system, GetSvcNumber());
160 break;
160 } 161 }
161 if (Has(hr, break_loop) || !uses_wall_clock) { 162 if (Has(hr, break_loop) || !uses_wall_clock) {
162 break; 163 break;
diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h
index c092db9ff..73f259525 100644
--- a/src/core/arm/arm_interface.h
+++ b/src/core/arm/arm_interface.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
index 6aae79c48..e9123c13d 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <fmt/format.h> 4#include <fmt/format.h>
6#include "common/logging/log.h" 5#include "common/logging/log.h"
diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.h b/src/core/arm/dynarmic/arm_dynarmic_cp15.h
index f271b2070..5b2a51636 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_cp15.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.h
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 0ede0d85c..ea32a4a8d 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <array> 4#include <array>
6#include <atomic> 5#include <atomic>
@@ -43,14 +42,15 @@
43#include "core/hle/service/service.h" 42#include "core/hle/service/service.h"
44#include "core/hle/service/sm/sm.h" 43#include "core/hle/service/sm/sm.h"
45#include "core/hle/service/time/time_manager.h" 44#include "core/hle/service/time/time_manager.h"
45#include "core/internal_network/network.h"
46#include "core/loader/loader.h" 46#include "core/loader/loader.h"
47#include "core/memory.h" 47#include "core/memory.h"
48#include "core/memory/cheat_engine.h" 48#include "core/memory/cheat_engine.h"
49#include "core/network/network.h"
50#include "core/perf_stats.h" 49#include "core/perf_stats.h"
51#include "core/reporter.h" 50#include "core/reporter.h"
52#include "core/telemetry_session.h" 51#include "core/telemetry_session.h"
53#include "core/tools/freezer.h" 52#include "core/tools/freezer.h"
53#include "network/network.h"
54#include "video_core/renderer_base.h" 54#include "video_core/renderer_base.h"
55#include "video_core/video_core.h" 55#include "video_core/video_core.h"
56 56
@@ -130,7 +130,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
130 130
131struct System::Impl { 131struct System::Impl {
132 explicit Impl(System& system) 132 explicit Impl(System& system)
133 : kernel{system}, fs_controller{system}, memory{system}, hid_core{}, 133 : kernel{system}, fs_controller{system}, memory{system}, hid_core{}, room_network{},
134 cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system} {} 134 cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system} {}
135 135
136 SystemResultStatus Run() { 136 SystemResultStatus Run() {
@@ -315,6 +315,17 @@ struct System::Impl {
315 GetAndResetPerfStats(); 315 GetAndResetPerfStats();
316 perf_stats->BeginSystemFrame(); 316 perf_stats->BeginSystemFrame();
317 317
318 std::string name = "Unknown Game";
319 if (app_loader->ReadTitle(name) != Loader::ResultStatus::Success) {
320 LOG_ERROR(Core, "Failed to read title for ROM (Error {})", load_result);
321 }
322 if (auto room_member = room_network.GetRoomMember().lock()) {
323 Network::GameInfo game_info;
324 game_info.name = name;
325 game_info.id = program_id;
326 room_member->SendGameInfo(game_info);
327 }
328
318 status = SystemResultStatus::Success; 329 status = SystemResultStatus::Success;
319 return status; 330 return status;
320 } 331 }
@@ -362,6 +373,11 @@ struct System::Impl {
362 memory.Reset(); 373 memory.Reset();
363 applet_manager.ClearAll(); 374 applet_manager.ClearAll();
364 375
376 if (auto room_member = room_network.GetRoomMember().lock()) {
377 Network::GameInfo game_info{};
378 room_member->SendGameInfo(game_info);
379 }
380
365 LOG_DEBUG(Core, "Shutdown OK"); 381 LOG_DEBUG(Core, "Shutdown OK");
366 } 382 }
367 383
@@ -434,6 +450,8 @@ struct System::Impl {
434 std::unique_ptr<AudioCore::AudioCore> audio_core; 450 std::unique_ptr<AudioCore::AudioCore> audio_core;
435 Core::Memory::Memory memory; 451 Core::Memory::Memory memory;
436 Core::HID::HIDCore hid_core; 452 Core::HID::HIDCore hid_core;
453 Network::RoomNetwork room_network;
454
437 CpuManager cpu_manager; 455 CpuManager cpu_manager;
438 std::atomic_bool is_powered_on{}; 456 std::atomic_bool is_powered_on{};
439 bool exit_lock = false; 457 bool exit_lock = false;
@@ -879,6 +897,14 @@ const Core::Debugger& System::GetDebugger() const {
879 return *impl->debugger; 897 return *impl->debugger;
880} 898}
881 899
900Network::RoomNetwork& System::GetRoomNetwork() {
901 return impl->room_network;
902}
903
904const Network::RoomNetwork& System::GetRoomNetwork() const {
905 return impl->room_network;
906}
907
882void System::RegisterExecuteProgramCallback(ExecuteProgramCallback&& callback) { 908void System::RegisterExecuteProgramCallback(ExecuteProgramCallback&& callback) {
883 impl->execute_program_callback = std::move(callback); 909 impl->execute_program_callback = std::move(callback);
884} 910}
diff --git a/src/core/core.h b/src/core/core.h
index a49d1214b..0ce3b1d60 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
@@ -97,6 +96,10 @@ namespace Core::HID {
97class HIDCore; 96class HIDCore;
98} 97}
99 98
99namespace Network {
100class RoomNetwork;
101}
102
100namespace Core { 103namespace Core {
101 104
102class ARM_Interface; 105class ARM_Interface;
@@ -379,6 +382,12 @@ public:
379 [[nodiscard]] Core::Debugger& GetDebugger(); 382 [[nodiscard]] Core::Debugger& GetDebugger();
380 [[nodiscard]] const Core::Debugger& GetDebugger() const; 383 [[nodiscard]] const Core::Debugger& GetDebugger() const;
381 384
385 /// Gets a mutable reference to the Room Network.
386 [[nodiscard]] Network::RoomNetwork& GetRoomNetwork();
387
388 /// Gets an immutable reference to the Room Network.
389 [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const;
390
382 void SetExitLock(bool locked); 391 void SetExitLock(bool locked);
383 [[nodiscard]] bool GetExitLock() const; 392 [[nodiscard]] bool GetExitLock() const;
384 393
diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp
index 37d3d83b9..9b1565ae1 100644
--- a/src/core/cpu_manager.cpp
+++ b/src/core/cpu_manager.cpp
@@ -8,6 +8,7 @@
8#include "core/core.h" 8#include "core/core.h"
9#include "core/core_timing.h" 9#include "core/core_timing.h"
10#include "core/cpu_manager.h" 10#include "core/cpu_manager.h"
11#include "core/hle/kernel/k_interrupt_manager.h"
11#include "core/hle/kernel/k_scheduler.h" 12#include "core/hle/kernel/k_scheduler.h"
12#include "core/hle/kernel/k_thread.h" 13#include "core/hle/kernel/k_thread.h"
13#include "core/hle/kernel/kernel.h" 14#include "core/hle/kernel/kernel.h"
@@ -49,14 +50,6 @@ void CpuManager::GuestThreadFunction() {
49 } 50 }
50} 51}
51 52
52void CpuManager::GuestRewindFunction() {
53 if (is_multicore) {
54 MultiCoreRunGuestLoop();
55 } else {
56 SingleCoreRunGuestLoop();
57 }
58}
59
60void CpuManager::IdleThreadFunction() { 53void CpuManager::IdleThreadFunction() {
61 if (is_multicore) { 54 if (is_multicore) {
62 MultiCoreRunIdleThread(); 55 MultiCoreRunIdleThread();
@@ -69,21 +62,21 @@ void CpuManager::ShutdownThreadFunction() {
69 ShutdownThread(); 62 ShutdownThread();
70} 63}
71 64
65void CpuManager::HandleInterrupt() {
66 auto& kernel = system.Kernel();
67 auto core_index = kernel.CurrentPhysicalCoreIndex();
68
69 Kernel::KInterruptManager::HandleInterrupt(kernel, static_cast<s32>(core_index));
70}
71
72/////////////////////////////////////////////////////////////////////////////// 72///////////////////////////////////////////////////////////////////////////////
73/// MultiCore /// 73/// MultiCore ///
74/////////////////////////////////////////////////////////////////////////////// 74///////////////////////////////////////////////////////////////////////////////
75 75
76void CpuManager::MultiCoreRunGuestThread() { 76void CpuManager::MultiCoreRunGuestThread() {
77 // Similar to UserModeThreadStarter in HOS
77 auto& kernel = system.Kernel(); 78 auto& kernel = system.Kernel();
78 kernel.CurrentScheduler()->OnThreadStart(); 79 kernel.CurrentScheduler()->OnThreadStart();
79 auto* thread = kernel.CurrentScheduler()->GetSchedulerCurrentThread();
80 auto& host_context = thread->GetHostContext();
81 host_context->SetRewindPoint([this] { GuestRewindFunction(); });
82 MultiCoreRunGuestLoop();
83}
84
85void CpuManager::MultiCoreRunGuestLoop() {
86 auto& kernel = system.Kernel();
87 80
88 while (true) { 81 while (true) {
89 auto* physical_core = &kernel.CurrentPhysicalCore(); 82 auto* physical_core = &kernel.CurrentPhysicalCore();
@@ -91,18 +84,26 @@ void CpuManager::MultiCoreRunGuestLoop() {
91 physical_core->Run(); 84 physical_core->Run();
92 physical_core = &kernel.CurrentPhysicalCore(); 85 physical_core = &kernel.CurrentPhysicalCore();
93 } 86 }
94 { 87
95 Kernel::KScopedDisableDispatch dd(kernel); 88 HandleInterrupt();
96 physical_core->ArmInterface().ClearExclusiveState();
97 }
98 } 89 }
99} 90}
100 91
101void CpuManager::MultiCoreRunIdleThread() { 92void CpuManager::MultiCoreRunIdleThread() {
93 // Not accurate to HOS. Remove this entire method when singlecore is removed.
94 // See notes in KScheduler::ScheduleImpl for more information about why this
95 // is inaccurate.
96
102 auto& kernel = system.Kernel(); 97 auto& kernel = system.Kernel();
98 kernel.CurrentScheduler()->OnThreadStart();
99
103 while (true) { 100 while (true) {
104 Kernel::KScopedDisableDispatch dd(kernel); 101 auto& physical_core = kernel.CurrentPhysicalCore();
105 kernel.CurrentPhysicalCore().Idle(); 102 if (!physical_core.IsInterrupted()) {
103 physical_core.Idle();
104 }
105
106 HandleInterrupt();
106 } 107 }
107} 108}
108 109
@@ -113,80 +114,73 @@ void CpuManager::MultiCoreRunIdleThread() {
113void CpuManager::SingleCoreRunGuestThread() { 114void CpuManager::SingleCoreRunGuestThread() {
114 auto& kernel = system.Kernel(); 115 auto& kernel = system.Kernel();
115 kernel.CurrentScheduler()->OnThreadStart(); 116 kernel.CurrentScheduler()->OnThreadStart();
116 auto* thread = kernel.CurrentScheduler()->GetSchedulerCurrentThread();
117 auto& host_context = thread->GetHostContext();
118 host_context->SetRewindPoint([this] { GuestRewindFunction(); });
119 SingleCoreRunGuestLoop();
120}
121 117
122void CpuManager::SingleCoreRunGuestLoop() {
123 auto& kernel = system.Kernel();
124 while (true) { 118 while (true) {
125 auto* physical_core = &kernel.CurrentPhysicalCore(); 119 auto* physical_core = &kernel.CurrentPhysicalCore();
126 if (!physical_core->IsInterrupted()) { 120 if (!physical_core->IsInterrupted()) {
127 physical_core->Run(); 121 physical_core->Run();
128 physical_core = &kernel.CurrentPhysicalCore(); 122 physical_core = &kernel.CurrentPhysicalCore();
129 } 123 }
124
130 kernel.SetIsPhantomModeForSingleCore(true); 125 kernel.SetIsPhantomModeForSingleCore(true);
131 system.CoreTiming().Advance(); 126 system.CoreTiming().Advance();
132 kernel.SetIsPhantomModeForSingleCore(false); 127 kernel.SetIsPhantomModeForSingleCore(false);
133 physical_core->ArmInterface().ClearExclusiveState(); 128
134 PreemptSingleCore(); 129 PreemptSingleCore();
135 auto& scheduler = kernel.Scheduler(current_core); 130 HandleInterrupt();
136 scheduler.RescheduleCurrentCore();
137 } 131 }
138} 132}
139 133
140void CpuManager::SingleCoreRunIdleThread() { 134void CpuManager::SingleCoreRunIdleThread() {
141 auto& kernel = system.Kernel(); 135 auto& kernel = system.Kernel();
136 kernel.CurrentScheduler()->OnThreadStart();
137
142 while (true) { 138 while (true) {
143 auto& physical_core = kernel.CurrentPhysicalCore();
144 PreemptSingleCore(false); 139 PreemptSingleCore(false);
145 system.CoreTiming().AddTicks(1000U); 140 system.CoreTiming().AddTicks(1000U);
146 idle_count++; 141 idle_count++;
147 auto& scheduler = physical_core.Scheduler(); 142 HandleInterrupt();
148 scheduler.RescheduleCurrentCore();
149 } 143 }
150} 144}
151 145
152void CpuManager::PreemptSingleCore(bool from_running_enviroment) { 146void CpuManager::PreemptSingleCore(bool from_running_environment) {
153 { 147 auto& kernel = system.Kernel();
154 auto& kernel = system.Kernel();
155 auto& scheduler = kernel.Scheduler(current_core);
156 Kernel::KThread* current_thread = scheduler.GetSchedulerCurrentThread();
157 if (idle_count >= 4 || from_running_enviroment) {
158 if (!from_running_enviroment) {
159 system.CoreTiming().Idle();
160 idle_count = 0;
161 }
162 kernel.SetIsPhantomModeForSingleCore(true);
163 system.CoreTiming().Advance();
164 kernel.SetIsPhantomModeForSingleCore(false);
165 }
166 current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES);
167 system.CoreTiming().ResetTicks();
168 scheduler.Unload(scheduler.GetSchedulerCurrentThread());
169
170 auto& next_scheduler = kernel.Scheduler(current_core);
171 Common::Fiber::YieldTo(current_thread->GetHostContext(), *next_scheduler.ControlContext());
172 }
173 148
174 // May have changed scheduler 149 if (idle_count >= 4 || from_running_environment) {
175 { 150 if (!from_running_environment) {
176 auto& scheduler = system.Kernel().Scheduler(current_core); 151 system.CoreTiming().Idle();
177 scheduler.Reload(scheduler.GetSchedulerCurrentThread());
178 if (!scheduler.IsIdle()) {
179 idle_count = 0; 152 idle_count = 0;
180 } 153 }
154 kernel.SetIsPhantomModeForSingleCore(true);
155 system.CoreTiming().Advance();
156 kernel.SetIsPhantomModeForSingleCore(false);
157 }
158 current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES);
159 system.CoreTiming().ResetTicks();
160 kernel.Scheduler(current_core).PreemptSingleCore();
161
162 // We've now been scheduled again, and we may have exchanged schedulers.
163 // Reload the scheduler in case it's different.
164 if (!kernel.Scheduler(current_core).IsIdle()) {
165 idle_count = 0;
181 } 166 }
182} 167}
183 168
169void CpuManager::GuestActivate() {
170 // Similar to the HorizonKernelMain callback in HOS
171 auto& kernel = system.Kernel();
172 auto* scheduler = kernel.CurrentScheduler();
173
174 scheduler->Activate();
175 UNREACHABLE();
176}
177
184void CpuManager::ShutdownThread() { 178void CpuManager::ShutdownThread() {
185 auto& kernel = system.Kernel(); 179 auto& kernel = system.Kernel();
180 auto* thread = kernel.GetCurrentEmuThread();
186 auto core = is_multicore ? kernel.CurrentPhysicalCoreIndex() : 0; 181 auto core = is_multicore ? kernel.CurrentPhysicalCoreIndex() : 0;
187 auto* current_thread = kernel.GetCurrentEmuThread();
188 182
189 Common::Fiber::YieldTo(current_thread->GetHostContext(), *core_data[core].host_context); 183 Common::Fiber::YieldTo(thread->GetHostContext(), *core_data[core].host_context);
190 UNREACHABLE(); 184 UNREACHABLE();
191} 185}
192 186
@@ -218,9 +212,12 @@ void CpuManager::RunThread(std::size_t core) {
218 system.GPU().ObtainContext(); 212 system.GPU().ObtainContext();
219 } 213 }
220 214
221 auto* current_thread = system.Kernel().CurrentScheduler()->GetIdleThread(); 215 auto& kernel = system.Kernel();
222 Kernel::SetCurrentThread(system.Kernel(), current_thread); 216 auto& scheduler = *kernel.CurrentScheduler();
223 Common::Fiber::YieldTo(data.host_context, *current_thread->GetHostContext()); 217 auto* thread = scheduler.GetSchedulerCurrentThread();
218 Kernel::SetCurrentThread(kernel, thread);
219
220 Common::Fiber::YieldTo(data.host_context, *thread->GetHostContext());
224} 221}
225 222
226} // namespace Core 223} // namespace Core
diff --git a/src/core/cpu_manager.h b/src/core/cpu_manager.h
index 76dc58ee1..95ea3ef39 100644
--- a/src/core/cpu_manager.h
+++ b/src/core/cpu_manager.h
@@ -50,7 +50,10 @@ public:
50 void Initialize(); 50 void Initialize();
51 void Shutdown(); 51 void Shutdown();
52 52
53 std::function<void()> GetGuestThreadStartFunc() { 53 std::function<void()> GetGuestActivateFunc() {
54 return [this] { GuestActivate(); };
55 }
56 std::function<void()> GetGuestThreadFunc() {
54 return [this] { GuestThreadFunction(); }; 57 return [this] { GuestThreadFunction(); };
55 } 58 }
56 std::function<void()> GetIdleThreadStartFunc() { 59 std::function<void()> GetIdleThreadStartFunc() {
@@ -68,20 +71,19 @@ public:
68 71
69private: 72private:
70 void GuestThreadFunction(); 73 void GuestThreadFunction();
71 void GuestRewindFunction();
72 void IdleThreadFunction(); 74 void IdleThreadFunction();
73 void ShutdownThreadFunction(); 75 void ShutdownThreadFunction();
74 76
75 void MultiCoreRunGuestThread(); 77 void MultiCoreRunGuestThread();
76 void MultiCoreRunGuestLoop();
77 void MultiCoreRunIdleThread(); 78 void MultiCoreRunIdleThread();
78 79
79 void SingleCoreRunGuestThread(); 80 void SingleCoreRunGuestThread();
80 void SingleCoreRunGuestLoop();
81 void SingleCoreRunIdleThread(); 81 void SingleCoreRunIdleThread();
82 82
83 static void ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager, std::size_t core); 83 static void ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager, std::size_t core);
84 84
85 void GuestActivate();
86 void HandleInterrupt();
85 void ShutdownThread(); 87 void ShutdownThread();
86 void RunThread(std::size_t core); 88 void RunThread(std::size_t core);
87 89
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h
index ff15b3e23..7cee0c7df 100644
--- a/src/core/file_sys/errors.h
+++ b/src/core/file_sys/errors.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp
index 57c6ffc43..1be2dccb0 100644
--- a/src/core/frontend/emu_window.cpp
+++ b/src/core/frontend/emu_window.cpp
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <mutex> 4#include <mutex>
6#include "core/frontend/emu_window.h" 5#include "core/frontend/emu_window.h"
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index b3bffecb2..ac1906d5e 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index bd2384515..8c3895937 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -126,10 +126,14 @@ void EmulatedController::LoadDevices() {
126 battery_params[LeftIndex].Set("battery", true); 126 battery_params[LeftIndex].Set("battery", true);
127 battery_params[RightIndex].Set("battery", true); 127 battery_params[RightIndex].Set("battery", true);
128 128
129 camera_params = Common::ParamPackage{"engine:camera,camera:1"};
130
129 output_params[LeftIndex] = left_joycon; 131 output_params[LeftIndex] = left_joycon;
130 output_params[RightIndex] = right_joycon; 132 output_params[RightIndex] = right_joycon;
133 output_params[2] = camera_params;
131 output_params[LeftIndex].Set("output", true); 134 output_params[LeftIndex].Set("output", true);
132 output_params[RightIndex].Set("output", true); 135 output_params[RightIndex].Set("output", true);
136 output_params[2].Set("output", true);
133 137
134 LoadTASParams(); 138 LoadTASParams();
135 139
@@ -146,6 +150,7 @@ void EmulatedController::LoadDevices() {
146 Common::Input::CreateDevice<Common::Input::InputDevice>); 150 Common::Input::CreateDevice<Common::Input::InputDevice>);
147 std::transform(battery_params.begin(), battery_params.end(), battery_devices.begin(), 151 std::transform(battery_params.begin(), battery_params.end(), battery_devices.begin(),
148 Common::Input::CreateDevice<Common::Input::InputDevice>); 152 Common::Input::CreateDevice<Common::Input::InputDevice>);
153 camera_devices = Common::Input::CreateDevice<Common::Input::InputDevice>(camera_params);
149 std::transform(output_params.begin(), output_params.end(), output_devices.begin(), 154 std::transform(output_params.begin(), output_params.end(), output_devices.begin(),
150 Common::Input::CreateDevice<Common::Input::OutputDevice>); 155 Common::Input::CreateDevice<Common::Input::OutputDevice>);
151 156
@@ -267,6 +272,14 @@ void EmulatedController::ReloadInput() {
267 motion_devices[index]->ForceUpdate(); 272 motion_devices[index]->ForceUpdate();
268 } 273 }
269 274
275 if (camera_devices) {
276 camera_devices->SetCallback({
277 .on_change =
278 [this](const Common::Input::CallbackStatus& callback) { SetCamera(callback); },
279 });
280 camera_devices->ForceUpdate();
281 }
282
270 // Use a common UUID for TAS 283 // Use a common UUID for TAS
271 static constexpr Common::UUID TAS_UUID = Common::UUID{ 284 static constexpr Common::UUID TAS_UUID = Common::UUID{
272 {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xA5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; 285 {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xA5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
@@ -851,6 +864,25 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac
851 TriggerOnChange(ControllerTriggerType::Battery, true); 864 TriggerOnChange(ControllerTriggerType::Battery, true);
852} 865}
853 866
867void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback) {
868 std::unique_lock lock{mutex};
869 controller.camera_values = TransformToCamera(callback);
870
871 if (is_configuring) {
872 lock.unlock();
873 TriggerOnChange(ControllerTriggerType::IrSensor, false);
874 return;
875 }
876
877 controller.camera_state.sample++;
878 controller.camera_state.format =
879 static_cast<Core::IrSensor::ImageTransferProcessorFormat>(controller.camera_values.format);
880 controller.camera_state.data = controller.camera_values.data;
881
882 lock.unlock();
883 TriggerOnChange(ControllerTriggerType::IrSensor, true);
884}
885
854bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) { 886bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
855 if (device_index >= output_devices.size()) { 887 if (device_index >= output_devices.size()) {
856 return false; 888 return false;
@@ -928,6 +960,23 @@ bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode)
928 return output_device->SetPollingMode(polling_mode) == Common::Input::PollingError::None; 960 return output_device->SetPollingMode(polling_mode) == Common::Input::PollingError::None;
929} 961}
930 962
963bool EmulatedController::SetCameraFormat(
964 Core::IrSensor::ImageTransferProcessorFormat camera_format) {
965 LOG_INFO(Service_HID, "Set camera format {}", camera_format);
966
967 auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
968 auto& camera_output_device = output_devices[2];
969
970 if (right_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>(
971 camera_format)) == Common::Input::CameraError::None) {
972 return true;
973 }
974
975 // Fallback to Qt camera if native device doesn't have support
976 return camera_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>(
977 camera_format)) == Common::Input::CameraError::None;
978}
979
931void EmulatedController::SetLedPattern() { 980void EmulatedController::SetLedPattern() {
932 for (auto& device : output_devices) { 981 for (auto& device : output_devices) {
933 if (!device) { 982 if (!device) {
@@ -1163,6 +1212,11 @@ BatteryValues EmulatedController::GetBatteryValues() const {
1163 return controller.battery_values; 1212 return controller.battery_values;
1164} 1213}
1165 1214
1215CameraValues EmulatedController::GetCameraValues() const {
1216 std::scoped_lock lock{mutex};
1217 return controller.camera_values;
1218}
1219
1166HomeButtonState EmulatedController::GetHomeButtons() const { 1220HomeButtonState EmulatedController::GetHomeButtons() const {
1167 std::scoped_lock lock{mutex}; 1221 std::scoped_lock lock{mutex};
1168 if (is_configuring) { 1222 if (is_configuring) {
@@ -1251,6 +1305,11 @@ BatteryLevelState EmulatedController::GetBattery() const {
1251 return controller.battery_state; 1305 return controller.battery_state;
1252} 1306}
1253 1307
1308const CameraState& EmulatedController::GetCamera() const {
1309 std::scoped_lock lock{mutex};
1310 return controller.camera_state;
1311}
1312
1254void EmulatedController::TriggerOnChange(ControllerTriggerType type, bool is_npad_service_update) { 1313void EmulatedController::TriggerOnChange(ControllerTriggerType type, bool is_npad_service_update) {
1255 std::scoped_lock lock{callback_mutex}; 1314 std::scoped_lock lock{callback_mutex};
1256 for (const auto& poller_pair : callback_list) { 1315 for (const auto& poller_pair : callback_list) {
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index 3f02ed3c0..823c1700c 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -15,10 +15,12 @@
15#include "common/settings.h" 15#include "common/settings.h"
16#include "common/vector_math.h" 16#include "common/vector_math.h"
17#include "core/hid/hid_types.h" 17#include "core/hid/hid_types.h"
18#include "core/hid/irs_types.h"
18#include "core/hid/motion_input.h" 19#include "core/hid/motion_input.h"
19 20
20namespace Core::HID { 21namespace Core::HID {
21const std::size_t max_emulated_controllers = 2; 22const std::size_t max_emulated_controllers = 2;
23const std::size_t output_devices = 3;
22struct ControllerMotionInfo { 24struct ControllerMotionInfo {
23 Common::Input::MotionStatus raw_status{}; 25 Common::Input::MotionStatus raw_status{};
24 MotionInput emulated{}; 26 MotionInput emulated{};
@@ -34,15 +36,16 @@ using TriggerDevices =
34 std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>; 36 std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>;
35using BatteryDevices = 37using BatteryDevices =
36 std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>; 38 std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
37using OutputDevices = 39using CameraDevices = std::unique_ptr<Common::Input::InputDevice>;
38 std::array<std::unique_ptr<Common::Input::OutputDevice>, max_emulated_controllers>; 40using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices>;
39 41
40using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>; 42using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;
41using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>; 43using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;
42using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>; 44using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>;
43using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>; 45using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
44using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>; 46using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>;
45using OutputParams = std::array<Common::ParamPackage, max_emulated_controllers>; 47using CameraParams = Common::ParamPackage;
48using OutputParams = std::array<Common::ParamPackage, output_devices>;
46 49
47using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>; 50using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
48using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>; 51using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>;
@@ -51,6 +54,7 @@ using TriggerValues =
51using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::NativeMotion::NumMotions>; 54using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::NativeMotion::NumMotions>;
52using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>; 55using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>;
53using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>; 56using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>;
57using CameraValues = Common::Input::CameraStatus;
54using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>; 58using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>;
55 59
56struct AnalogSticks { 60struct AnalogSticks {
@@ -70,6 +74,12 @@ struct BatteryLevelState {
70 NpadPowerInfo right{}; 74 NpadPowerInfo right{};
71}; 75};
72 76
77struct CameraState {
78 Core::IrSensor::ImageTransferProcessorFormat format{};
79 std::vector<u8> data{};
80 std::size_t sample{};
81};
82
73struct ControllerMotion { 83struct ControllerMotion {
74 Common::Vec3f accel{}; 84 Common::Vec3f accel{};
75 Common::Vec3f gyro{}; 85 Common::Vec3f gyro{};
@@ -96,6 +106,7 @@ struct ControllerStatus {
96 ColorValues color_values{}; 106 ColorValues color_values{};
97 BatteryValues battery_values{}; 107 BatteryValues battery_values{};
98 VibrationValues vibration_values{}; 108 VibrationValues vibration_values{};
109 CameraValues camera_values{};
99 110
100 // Data for HID serices 111 // Data for HID serices
101 HomeButtonState home_button_state{}; 112 HomeButtonState home_button_state{};
@@ -107,6 +118,7 @@ struct ControllerStatus {
107 NpadGcTriggerState gc_trigger_state{}; 118 NpadGcTriggerState gc_trigger_state{};
108 ControllerColors colors_state{}; 119 ControllerColors colors_state{};
109 BatteryLevelState battery_state{}; 120 BatteryLevelState battery_state{};
121 CameraState camera_state{};
110}; 122};
111 123
112enum class ControllerTriggerType { 124enum class ControllerTriggerType {
@@ -117,6 +129,7 @@ enum class ControllerTriggerType {
117 Color, 129 Color,
118 Battery, 130 Battery,
119 Vibration, 131 Vibration,
132 IrSensor,
120 Connected, 133 Connected,
121 Disconnected, 134 Disconnected,
122 Type, 135 Type,
@@ -269,6 +282,9 @@ public:
269 /// Returns the latest battery status from the controller with parameters 282 /// Returns the latest battery status from the controller with parameters
270 BatteryValues GetBatteryValues() const; 283 BatteryValues GetBatteryValues() const;
271 284
285 /// Returns the latest camera status from the controller with parameters
286 CameraValues GetCameraValues() const;
287
272 /// Returns the latest status of button input for the hid::HomeButton service 288 /// Returns the latest status of button input for the hid::HomeButton service
273 HomeButtonState GetHomeButtons() const; 289 HomeButtonState GetHomeButtons() const;
274 290
@@ -296,6 +312,9 @@ public:
296 /// Returns the latest battery status from the controller 312 /// Returns the latest battery status from the controller
297 BatteryLevelState GetBattery() const; 313 BatteryLevelState GetBattery() const;
298 314
315 /// Returns the latest camera status from the controller
316 const CameraState& GetCamera() const;
317
299 /** 318 /**
300 * Sends a specific vibration to the output device 319 * Sends a specific vibration to the output device
301 * @return true if vibration had no errors 320 * @return true if vibration had no errors
@@ -315,6 +334,13 @@ public:
315 */ 334 */
316 bool SetPollingMode(Common::Input::PollingMode polling_mode); 335 bool SetPollingMode(Common::Input::PollingMode polling_mode);
317 336
337 /**
338 * Sets the desired camera format to be polled from a controller
339 * @param camera_format size of each frame
340 * @return true if SetCameraFormat was successfull
341 */
342 bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format);
343
318 /// Returns the led pattern corresponding to this emulated controller 344 /// Returns the led pattern corresponding to this emulated controller
319 LedPattern GetLedPattern() const; 345 LedPattern GetLedPattern() const;
320 346
@@ -393,6 +419,12 @@ private:
393 void SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index); 419 void SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index);
394 420
395 /** 421 /**
422 * Updates the camera status of the controller
423 * @param callback A CallbackStatus containing the camera status
424 */
425 void SetCamera(const Common::Input::CallbackStatus& callback);
426
427 /**
396 * Triggers a callback that something has changed on the controller status 428 * Triggers a callback that something has changed on the controller status
397 * @param type Input type of the event to trigger 429 * @param type Input type of the event to trigger
398 * @param is_service_update indicates if this event should only be sent to HID services 430 * @param is_service_update indicates if this event should only be sent to HID services
@@ -417,6 +449,7 @@ private:
417 ControllerMotionParams motion_params; 449 ControllerMotionParams motion_params;
418 TriggerParams trigger_params; 450 TriggerParams trigger_params;
419 BatteryParams battery_params; 451 BatteryParams battery_params;
452 CameraParams camera_params;
420 OutputParams output_params; 453 OutputParams output_params;
421 454
422 ButtonDevices button_devices; 455 ButtonDevices button_devices;
@@ -424,6 +457,7 @@ private:
424 ControllerMotionDevices motion_devices; 457 ControllerMotionDevices motion_devices;
425 TriggerDevices trigger_devices; 458 TriggerDevices trigger_devices;
426 BatteryDevices battery_devices; 459 BatteryDevices battery_devices;
460 CameraDevices camera_devices;
427 OutputDevices output_devices; 461 OutputDevices output_devices;
428 462
429 // TAS related variables 463 // TAS related variables
diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp
index 18d9f042d..68d143a01 100644
--- a/src/core/hid/input_converter.cpp
+++ b/src/core/hid/input_converter.cpp
@@ -270,6 +270,20 @@ Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatu
270 return status; 270 return status;
271} 271}
272 272
273Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatus& callback) {
274 Common::Input::CameraStatus camera{};
275 switch (callback.type) {
276 case Common::Input::InputType::IrSensor:
277 camera = callback.camera_status;
278 break;
279 default:
280 LOG_ERROR(Input, "Conversion from type {} to camera not implemented", callback.type);
281 break;
282 }
283
284 return camera;
285}
286
273void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) { 287void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) {
274 const auto& properties = analog.properties; 288 const auto& properties = analog.properties;
275 float& raw_value = analog.raw_value; 289 float& raw_value = analog.raw_value;
diff --git a/src/core/hid/input_converter.h b/src/core/hid/input_converter.h
index 2be36889f..143c50cc0 100644
--- a/src/core/hid/input_converter.h
+++ b/src/core/hid/input_converter.h
@@ -77,6 +77,14 @@ Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackSta
77Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatus& callback); 77Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatus& callback);
78 78
79/** 79/**
80 * Converts raw input data into a valid camera status.
81 *
82 * @param callback Supported callbacks: Camera.
83 * @return A valid CameraObject object.
84 */
85Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatus& callback);
86
87/**
80 * Converts raw analog data into a valid analog value 88 * Converts raw analog data into a valid analog value
81 * @param analog An analog object containing raw data and properties 89 * @param analog An analog object containing raw data and properties
82 * @param clamp_value determines if the value needs to be clamped between -1.0f and 1.0f. 90 * @param clamp_value determines if the value needs to be clamped between -1.0f and 1.0f.
diff --git a/src/core/hid/irs_types.h b/src/core/hid/irs_types.h
new file mode 100644
index 000000000..88c5b016d
--- /dev/null
+++ b/src/core/hid/irs_types.h
@@ -0,0 +1,301 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "common/common_funcs.h"
7#include "common/common_types.h"
8#include "core/hid/hid_types.h"
9
10namespace Core::IrSensor {
11
12// This is nn::irsensor::CameraAmbientNoiseLevel
13enum class CameraAmbientNoiseLevel : u32 {
14 Low,
15 Medium,
16 High,
17 Unkown3, // This level can't be reached
18};
19
20// This is nn::irsensor::CameraLightTarget
21enum class CameraLightTarget : u32 {
22 AllLeds,
23 BrightLeds,
24 DimLeds,
25 None,
26};
27
28// This is nn::irsensor::PackedCameraLightTarget
29enum class PackedCameraLightTarget : u8 {
30 AllLeds,
31 BrightLeds,
32 DimLeds,
33 None,
34};
35
36// This is nn::irsensor::AdaptiveClusteringMode
37enum class AdaptiveClusteringMode : u32 {
38 StaticFov,
39 DynamicFov,
40};
41
42// This is nn::irsensor::AdaptiveClusteringTargetDistance
43enum class AdaptiveClusteringTargetDistance : u32 {
44 Near,
45 Middle,
46 Far,
47};
48
49// This is nn::irsensor::ImageTransferProcessorFormat
50enum class ImageTransferProcessorFormat : u32 {
51 Size320x240,
52 Size160x120,
53 Size80x60,
54 Size40x30,
55 Size20x15,
56};
57
58// This is nn::irsensor::PackedImageTransferProcessorFormat
59enum class PackedImageTransferProcessorFormat : u8 {
60 Size320x240,
61 Size160x120,
62 Size80x60,
63 Size40x30,
64 Size20x15,
65};
66
67// This is nn::irsensor::IrCameraStatus
68enum class IrCameraStatus : u32 {
69 Available,
70 Unsupported,
71 Unconnected,
72};
73
74// This is nn::irsensor::IrCameraInternalStatus
75enum class IrCameraInternalStatus : u32 {
76 Stopped,
77 FirmwareUpdateNeeded,
78 Unkown2,
79 Unkown3,
80 Unkown4,
81 FirmwareVersionRequested,
82 FirmwareVersionIsInvalid,
83 Ready,
84 Setting,
85};
86
87// This is nn::irsensor::detail::StatusManager::IrSensorMode
88enum class IrSensorMode : u64 {
89 None,
90 MomentProcessor,
91 ClusteringProcessor,
92 ImageTransferProcessor,
93 PointingProcessorMarker,
94 TeraPluginProcessor,
95 IrLedProcessor,
96};
97
98// This is nn::irsensor::ImageProcessorStatus
99enum ImageProcessorStatus : u32 {
100 Stopped,
101 Running,
102};
103
104// This is nn::irsensor::HandAnalysisMode
105enum class HandAnalysisMode : u32 {
106 None,
107 Silhouette,
108 Image,
109 SilhoueteAndImage,
110 SilhuetteOnly,
111};
112
113// This is nn::irsensor::IrSensorFunctionLevel
114enum class IrSensorFunctionLevel : u8 {
115 unknown0,
116 unknown1,
117 unknown2,
118 unknown3,
119 unknown4,
120};
121
122// This is nn::irsensor::MomentProcessorPreprocess
123enum class MomentProcessorPreprocess : u32 {
124 Unkown0,
125 Unkown1,
126};
127
128// This is nn::irsensor::PackedMomentProcessorPreprocess
129enum class PackedMomentProcessorPreprocess : u8 {
130 Unkown0,
131 Unkown1,
132};
133
134// This is nn::irsensor::PointingStatus
135enum class PointingStatus : u32 {
136 Unkown0,
137 Unkown1,
138};
139
140struct IrsRect {
141 s16 x;
142 s16 y;
143 s16 width;
144 s16 height;
145};
146
147struct IrsCentroid {
148 f32 x;
149 f32 y;
150};
151
152struct CameraConfig {
153 u64 exposure_time;
154 CameraLightTarget light_target;
155 u32 gain;
156 bool is_negative_used;
157 INSERT_PADDING_BYTES(7);
158};
159static_assert(sizeof(CameraConfig) == 0x18, "CameraConfig is an invalid size");
160
161struct PackedCameraConfig {
162 u64 exposure_time;
163 PackedCameraLightTarget light_target;
164 u8 gain;
165 bool is_negative_used;
166 INSERT_PADDING_BYTES(5);
167};
168static_assert(sizeof(PackedCameraConfig) == 0x10, "PackedCameraConfig is an invalid size");
169
170// This is nn::irsensor::IrCameraHandle
171struct IrCameraHandle {
172 u8 npad_id{};
173 Core::HID::NpadStyleIndex npad_type{Core::HID::NpadStyleIndex::None};
174 INSERT_PADDING_BYTES(2);
175};
176static_assert(sizeof(IrCameraHandle) == 4, "IrCameraHandle is an invalid size");
177
178// This is nn::irsensor::PackedMcuVersion
179struct PackedMcuVersion {
180 u16 major;
181 u16 minor;
182};
183static_assert(sizeof(PackedMcuVersion) == 4, "PackedMcuVersion is an invalid size");
184
185// This is nn::irsensor::PackedMomentProcessorConfig
186struct PackedMomentProcessorConfig {
187 PackedCameraConfig camera_config;
188 IrsRect window_of_interest;
189 PackedMcuVersion required_mcu_version;
190 PackedMomentProcessorPreprocess preprocess;
191 u8 preprocess_intensity_threshold;
192 INSERT_PADDING_BYTES(2);
193};
194static_assert(sizeof(PackedMomentProcessorConfig) == 0x20,
195 "PackedMomentProcessorConfig is an invalid size");
196
197// This is nn::irsensor::PackedClusteringProcessorConfig
198struct PackedClusteringProcessorConfig {
199 PackedCameraConfig camera_config;
200 IrsRect window_of_interest;
201 PackedMcuVersion required_mcu_version;
202 u32 pixel_count_min;
203 u32 pixel_count_max;
204 u8 object_intensity_min;
205 bool is_external_light_filter_enabled;
206 INSERT_PADDING_BYTES(2);
207};
208static_assert(sizeof(PackedClusteringProcessorConfig) == 0x28,
209 "PackedClusteringProcessorConfig is an invalid size");
210
211// This is nn::irsensor::PackedImageTransferProcessorConfig
212struct PackedImageTransferProcessorConfig {
213 PackedCameraConfig camera_config;
214 PackedMcuVersion required_mcu_version;
215 PackedImageTransferProcessorFormat format;
216 INSERT_PADDING_BYTES(3);
217};
218static_assert(sizeof(PackedImageTransferProcessorConfig) == 0x18,
219 "PackedImageTransferProcessorConfig is an invalid size");
220
221// This is nn::irsensor::PackedTeraPluginProcessorConfig
222struct PackedTeraPluginProcessorConfig {
223 PackedMcuVersion required_mcu_version;
224 u8 mode;
225 u8 unknown_1;
226 u8 unknown_2;
227 u8 unknown_3;
228};
229static_assert(sizeof(PackedTeraPluginProcessorConfig) == 0x8,
230 "PackedTeraPluginProcessorConfig is an invalid size");
231
232// This is nn::irsensor::PackedPointingProcessorConfig
233struct PackedPointingProcessorConfig {
234 IrsRect window_of_interest;
235 PackedMcuVersion required_mcu_version;
236};
237static_assert(sizeof(PackedPointingProcessorConfig) == 0xC,
238 "PackedPointingProcessorConfig is an invalid size");
239
240// This is nn::irsensor::PackedFunctionLevel
241struct PackedFunctionLevel {
242 IrSensorFunctionLevel function_level;
243 INSERT_PADDING_BYTES(3);
244};
245static_assert(sizeof(PackedFunctionLevel) == 0x4, "PackedFunctionLevel is an invalid size");
246
247// This is nn::irsensor::PackedImageTransferProcessorExConfig
248struct PackedImageTransferProcessorExConfig {
249 PackedCameraConfig camera_config;
250 PackedMcuVersion required_mcu_version;
251 PackedImageTransferProcessorFormat origin_format;
252 PackedImageTransferProcessorFormat trimming_format;
253 u16 trimming_start_x;
254 u16 trimming_start_y;
255 bool is_external_light_filter_enabled;
256 INSERT_PADDING_BYTES(5);
257};
258static_assert(sizeof(PackedImageTransferProcessorExConfig) == 0x20,
259 "PackedImageTransferProcessorExConfig is an invalid size");
260
261// This is nn::irsensor::PackedIrLedProcessorConfig
262struct PackedIrLedProcessorConfig {
263 PackedMcuVersion required_mcu_version;
264 u8 light_target;
265 INSERT_PADDING_BYTES(3);
266};
267static_assert(sizeof(PackedIrLedProcessorConfig) == 0x8,
268 "PackedIrLedProcessorConfig is an invalid size");
269
270// This is nn::irsensor::HandAnalysisConfig
271struct HandAnalysisConfig {
272 HandAnalysisMode mode;
273};
274static_assert(sizeof(HandAnalysisConfig) == 0x4, "HandAnalysisConfig is an invalid size");
275
276// This is nn::irsensor::detail::ProcessorState contents are different for each processor
277struct ProcessorState {
278 std::array<u8, 0xE20> processor_raw_data{};
279};
280static_assert(sizeof(ProcessorState) == 0xE20, "ProcessorState is an invalid size");
281
282// This is nn::irsensor::detail::DeviceFormat
283struct DeviceFormat {
284 Core::IrSensor::IrCameraStatus camera_status{Core::IrSensor::IrCameraStatus::Unconnected};
285 Core::IrSensor::IrCameraInternalStatus camera_internal_status{
286 Core::IrSensor::IrCameraInternalStatus::Ready};
287 Core::IrSensor::IrSensorMode mode{Core::IrSensor::IrSensorMode::None};
288 ProcessorState state{};
289};
290static_assert(sizeof(DeviceFormat) == 0xE30, "DeviceFormat is an invalid size");
291
292// This is nn::irsensor::ImageTransferProcessorState
293struct ImageTransferProcessorState {
294 u64 sampling_number;
295 Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level;
296 INSERT_PADDING_BYTES(4);
297};
298static_assert(sizeof(ImageTransferProcessorState) == 0x10,
299 "ImageTransferProcessorState is an invalid size");
300
301} // namespace Core::IrSensor
diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h
index 602e12606..416da15ec 100644
--- a/src/core/hle/ipc.h
+++ b/src/core/hle/ipc.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h
index 004bb2005..d631c0357 100644
--- a/src/core/hle/ipc_helpers.h
+++ b/src/core/hle/ipc_helpers.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/core/hle/kernel/global_scheduler_context.cpp b/src/core/hle/kernel/global_scheduler_context.cpp
index 164436b26..65576b8c4 100644
--- a/src/core/hle/kernel/global_scheduler_context.cpp
+++ b/src/core/hle/kernel/global_scheduler_context.cpp
@@ -41,12 +41,7 @@ void GlobalSchedulerContext::PreemptThreads() {
41 ASSERT(IsLocked()); 41 ASSERT(IsLocked());
42 for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { 42 for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
43 const u32 priority = preemption_priorities[core_id]; 43 const u32 priority = preemption_priorities[core_id];
44 kernel.Scheduler(core_id).RotateScheduledQueue(core_id, priority); 44 KScheduler::RotateScheduledQueue(kernel, core_id, priority);
45
46 // Signal an interrupt occurred. For core 3, this is a certainty, as preemption will result
47 // in the rotator thread being scheduled. For cores 0-2, this is to simulate or system
48 // interrupts that may have occurred.
49 kernel.PhysicalCore(core_id).Interrupt();
50 } 45 }
51} 46}
52 47
diff --git a/src/core/hle/kernel/k_client_port.cpp b/src/core/hle/kernel/k_client_port.cpp
index d63e77d15..3cb22ff4d 100644
--- a/src/core/hle/kernel/k_client_port.cpp
+++ b/src/core/hle/kernel/k_client_port.cpp
@@ -1,6 +1,5 @@
1// Copyright 2021 Citra Emulator Project 1// SPDX-FileCopyrightText: 2021 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include "common/scope_exit.h" 4#include "common/scope_exit.h"
6#include "core/hle/kernel/hle_ipc.h" 5#include "core/hle/kernel/hle_ipc.h"
diff --git a/src/core/hle/kernel/k_client_port.h b/src/core/hle/kernel/k_client_port.h
index ef8583efc..e17eff28f 100644
--- a/src/core/hle/kernel/k_client_port.h
+++ b/src/core/hle/kernel/k_client_port.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/core/hle/kernel/k_interrupt_manager.cpp b/src/core/hle/kernel/k_interrupt_manager.cpp
index d606a7f86..1b577a5b3 100644
--- a/src/core/hle/kernel/k_interrupt_manager.cpp
+++ b/src/core/hle/kernel/k_interrupt_manager.cpp
@@ -6,6 +6,7 @@
6#include "core/hle/kernel/k_scheduler.h" 6#include "core/hle/kernel/k_scheduler.h"
7#include "core/hle/kernel/k_thread.h" 7#include "core/hle/kernel/k_thread.h"
8#include "core/hle/kernel/kernel.h" 8#include "core/hle/kernel/kernel.h"
9#include "core/hle/kernel/physical_core.h"
9 10
10namespace Kernel::KInterruptManager { 11namespace Kernel::KInterruptManager {
11 12
@@ -15,6 +16,9 @@ void HandleInterrupt(KernelCore& kernel, s32 core_id) {
15 return; 16 return;
16 } 17 }
17 18
19 // Acknowledge the interrupt.
20 kernel.PhysicalCore(core_id).ClearInterrupt();
21
18 auto& current_thread = GetCurrentThread(kernel); 22 auto& current_thread = GetCurrentThread(kernel);
19 23
20 // If the user disable count is set, we may need to pin the current thread. 24 // If the user disable count is set, we may need to pin the current thread.
@@ -27,6 +31,9 @@ void HandleInterrupt(KernelCore& kernel, s32 core_id) {
27 // Set the interrupt flag for the thread. 31 // Set the interrupt flag for the thread.
28 GetCurrentThread(kernel).SetInterruptFlag(); 32 GetCurrentThread(kernel).SetInterruptFlag();
29 } 33 }
34
35 // Request interrupt scheduling.
36 kernel.CurrentScheduler()->RequestScheduleOnInterrupt();
30} 37}
31 38
32} // namespace Kernel::KInterruptManager 39} // namespace Kernel::KInterruptManager
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index b662788b3..d3e99665f 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <algorithm> 4#include <algorithm>
6#include <bitset> 5#include <bitset>
diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h
index 5e3e22ad8..d56d73bab 100644
--- a/src/core/hle/kernel/k_process.h
+++ b/src/core/hle/kernel/k_process.h
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp
index d599d2bcb..c34ce7a17 100644
--- a/src/core/hle/kernel/k_scheduler.cpp
+++ b/src/core/hle/kernel/k_scheduler.cpp
@@ -27,69 +27,185 @@ static void IncrementScheduledCount(Kernel::KThread* thread) {
27 } 27 }
28} 28}
29 29
30void KScheduler::RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedule) { 30KScheduler::KScheduler(KernelCore& kernel_) : kernel{kernel_} {
31 auto scheduler = kernel.CurrentScheduler(); 31 m_switch_fiber = std::make_shared<Common::Fiber>([this] {
32 32 while (true) {
33 u32 current_core{0xF}; 33 ScheduleImplFiber();
34 bool must_context_switch{};
35 if (scheduler) {
36 current_core = scheduler->core_id;
37 // TODO(bunnei): Should be set to true when we deprecate single core
38 must_context_switch = !kernel.IsPhantomModeForSingleCore();
39 }
40
41 while (cores_pending_reschedule != 0) {
42 const auto core = static_cast<u32>(std::countr_zero(cores_pending_reschedule));
43 ASSERT(core < Core::Hardware::NUM_CPU_CORES);
44 if (!must_context_switch || core != current_core) {
45 auto& phys_core = kernel.PhysicalCore(core);
46 phys_core.Interrupt();
47 } 34 }
48 cores_pending_reschedule &= ~(1ULL << core); 35 });
36
37 m_state.needs_scheduling = true;
38}
39
40KScheduler::~KScheduler() = default;
41
42void KScheduler::SetInterruptTaskRunnable() {
43 m_state.interrupt_task_runnable = true;
44 m_state.needs_scheduling = true;
45}
46
47void KScheduler::RequestScheduleOnInterrupt() {
48 m_state.needs_scheduling = true;
49
50 if (CanSchedule(kernel)) {
51 ScheduleOnInterrupt();
49 } 52 }
53}
50 54
51 for (std::size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; ++core_id) { 55void KScheduler::DisableScheduling(KernelCore& kernel) {
52 if (kernel.PhysicalCore(core_id).IsInterrupted()) { 56 ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 0);
53 KInterruptManager::HandleInterrupt(kernel, static_cast<s32>(core_id)); 57 GetCurrentThread(kernel).DisableDispatch();
54 } 58}
59
60void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling) {
61 ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 1);
62
63 auto* scheduler{kernel.CurrentScheduler()};
64
65 if (!scheduler || kernel.IsPhantomModeForSingleCore()) {
66 KScheduler::RescheduleCores(kernel, cores_needing_scheduling);
67 KScheduler::RescheduleCurrentHLEThread(kernel);
68 return;
69 }
70
71 scheduler->RescheduleOtherCores(cores_needing_scheduling);
72
73 if (GetCurrentThread(kernel).GetDisableDispatchCount() > 1) {
74 GetCurrentThread(kernel).EnableDispatch();
75 } else {
76 scheduler->RescheduleCurrentCore();
77 }
78}
79
80void KScheduler::RescheduleCurrentHLEThread(KernelCore& kernel) {
81 // HACK: we cannot schedule from this thread, it is not a core thread
82 ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1);
83
84 // Special case to ensure dummy threads that are waiting block
85 GetCurrentThread(kernel).IfDummyThreadTryWait();
86
87 ASSERT(GetCurrentThread(kernel).GetState() != ThreadState::Waiting);
88 GetCurrentThread(kernel).EnableDispatch();
89}
90
91u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) {
92 if (IsSchedulerUpdateNeeded(kernel)) {
93 return UpdateHighestPriorityThreadsImpl(kernel);
94 } else {
95 return 0;
96 }
97}
98
99void KScheduler::Schedule() {
100 ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1);
101 ASSERT(m_core_id == GetCurrentCoreId(kernel));
102
103 ScheduleImpl();
104}
105
106void KScheduler::ScheduleOnInterrupt() {
107 GetCurrentThread(kernel).DisableDispatch();
108 Schedule();
109 GetCurrentThread(kernel).EnableDispatch();
110}
111
112void KScheduler::PreemptSingleCore() {
113 GetCurrentThread(kernel).DisableDispatch();
114
115 auto* thread = GetCurrentThreadPointer(kernel);
116 auto& previous_scheduler = kernel.Scheduler(thread->GetCurrentCore());
117 previous_scheduler.Unload(thread);
118
119 Common::Fiber::YieldTo(thread->GetHostContext(), *m_switch_fiber);
120
121 GetCurrentThread(kernel).EnableDispatch();
122}
123
124void KScheduler::RescheduleCurrentCore() {
125 ASSERT(!kernel.IsPhantomModeForSingleCore());
126 ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1);
127
128 GetCurrentThread(kernel).EnableDispatch();
129
130 if (m_state.needs_scheduling.load()) {
131 // Disable interrupts, and then check again if rescheduling is needed.
132 // KScopedInterruptDisable intr_disable;
133
134 kernel.CurrentScheduler()->RescheduleCurrentCoreImpl();
55 } 135 }
136}
56 137
57 if (must_context_switch) { 138void KScheduler::RescheduleCurrentCoreImpl() {
58 auto core_scheduler = kernel.CurrentScheduler(); 139 // Check that scheduling is needed.
59 kernel.ExitSVCProfile(); 140 if (m_state.needs_scheduling.load()) [[likely]] {
60 core_scheduler->RescheduleCurrentCore(); 141 GetCurrentThread(kernel).DisableDispatch();
61 kernel.EnterSVCProfile(); 142 Schedule();
143 GetCurrentThread(kernel).EnableDispatch();
62 } 144 }
63} 145}
64 146
147void KScheduler::Initialize(KThread* main_thread, KThread* idle_thread, s32 core_id) {
148 // Set core ID/idle thread/interrupt task manager.
149 m_core_id = core_id;
150 m_idle_thread = idle_thread;
151 // m_state.idle_thread_stack = m_idle_thread->GetStackTop();
152 // m_state.interrupt_task_manager = &kernel.GetInterruptTaskManager();
153
154 // Insert the main thread into the priority queue.
155 // {
156 // KScopedSchedulerLock lk{kernel};
157 // GetPriorityQueue(kernel).PushBack(GetCurrentThreadPointer(kernel));
158 // SetSchedulerUpdateNeeded(kernel);
159 // }
160
161 // Bind interrupt handler.
162 // kernel.GetInterruptManager().BindHandler(
163 // GetSchedulerInterruptHandler(kernel), KInterruptName::Scheduler, m_core_id,
164 // KInterruptController::PriorityLevel::Scheduler, false, false);
165
166 // Set the current thread.
167 m_current_thread = main_thread;
168}
169
170void KScheduler::Activate() {
171 ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1);
172
173 // m_state.should_count_idle = KTargetSystem::IsDebugMode();
174 m_is_active = true;
175 RescheduleCurrentCore();
176}
177
178void KScheduler::OnThreadStart() {
179 GetCurrentThread(kernel).EnableDispatch();
180}
181
65u64 KScheduler::UpdateHighestPriorityThread(KThread* highest_thread) { 182u64 KScheduler::UpdateHighestPriorityThread(KThread* highest_thread) {
66 KScopedSpinLock lk{guard}; 183 if (KThread* prev_highest_thread = m_state.highest_priority_thread;
67 if (KThread* prev_highest_thread = state.highest_priority_thread; 184 prev_highest_thread != highest_thread) [[likely]] {
68 prev_highest_thread != highest_thread) { 185 if (prev_highest_thread != nullptr) [[likely]] {
69 if (prev_highest_thread != nullptr) {
70 IncrementScheduledCount(prev_highest_thread); 186 IncrementScheduledCount(prev_highest_thread);
71 prev_highest_thread->SetLastScheduledTick(system.CoreTiming().GetCPUTicks()); 187 prev_highest_thread->SetLastScheduledTick(kernel.System().CoreTiming().GetCPUTicks());
72 } 188 }
73 if (state.should_count_idle) { 189 if (m_state.should_count_idle) {
74 if (highest_thread != nullptr) { 190 if (highest_thread != nullptr) [[likely]] {
75 if (KProcess* process = highest_thread->GetOwnerProcess(); process != nullptr) { 191 if (KProcess* process = highest_thread->GetOwnerProcess(); process != nullptr) {
76 process->SetRunningThread(core_id, highest_thread, state.idle_count); 192 process->SetRunningThread(m_core_id, highest_thread, m_state.idle_count);
77 } 193 }
78 } else { 194 } else {
79 state.idle_count++; 195 m_state.idle_count++;
80 } 196 }
81 } 197 }
82 198
83 state.highest_priority_thread = highest_thread; 199 m_state.highest_priority_thread = highest_thread;
84 state.needs_scheduling.store(true); 200 m_state.needs_scheduling = true;
85 return (1ULL << core_id); 201 return (1ULL << m_core_id);
86 } else { 202 } else {
87 return 0; 203 return 0;
88 } 204 }
89} 205}
90 206
91u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) { 207u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
92 ASSERT(kernel.GlobalSchedulerContext().IsLocked()); 208 ASSERT(IsSchedulerLockedByCurrentThread(kernel));
93 209
94 // Clear that we need to update. 210 // Clear that we need to update.
95 ClearSchedulerUpdateNeeded(kernel); 211 ClearSchedulerUpdateNeeded(kernel);
@@ -98,18 +214,20 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
98 KThread* top_threads[Core::Hardware::NUM_CPU_CORES]; 214 KThread* top_threads[Core::Hardware::NUM_CPU_CORES];
99 auto& priority_queue = GetPriorityQueue(kernel); 215 auto& priority_queue = GetPriorityQueue(kernel);
100 216
101 /// We want to go over all cores, finding the highest priority thread and determining if 217 // We want to go over all cores, finding the highest priority thread and determining if
102 /// scheduling is needed for that core. 218 // scheduling is needed for that core.
103 for (size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { 219 for (size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
104 KThread* top_thread = priority_queue.GetScheduledFront(static_cast<s32>(core_id)); 220 KThread* top_thread = priority_queue.GetScheduledFront(static_cast<s32>(core_id));
105 if (top_thread != nullptr) { 221 if (top_thread != nullptr) {
106 // If the thread has no waiters, we need to check if the process has a thread pinned. 222 // We need to check if the thread's process has a pinned thread.
107 if (top_thread->GetNumKernelWaiters() == 0) { 223 if (KProcess* parent = top_thread->GetOwnerProcess()) {
108 if (KProcess* parent = top_thread->GetOwnerProcess(); parent != nullptr) { 224 // Check that there's a pinned thread other than the current top thread.
109 if (KThread* pinned = parent->GetPinnedThread(static_cast<s32>(core_id)); 225 if (KThread* pinned = parent->GetPinnedThread(static_cast<s32>(core_id));
110 pinned != nullptr && pinned != top_thread) { 226 pinned != nullptr && pinned != top_thread) {
111 // We prefer our parent's pinned thread if possible. However, we also don't 227 // We need to prefer threads with kernel waiters to the pinned thread.
112 // want to schedule un-runnable threads. 228 if (top_thread->GetNumKernelWaiters() ==
229 0 /* && top_thread != parent->GetExceptionThread() */) {
230 // If the pinned thread is runnable, use it.
113 if (pinned->GetRawState() == ThreadState::Runnable) { 231 if (pinned->GetRawState() == ThreadState::Runnable) {
114 top_thread = pinned; 232 top_thread = pinned;
115 } else { 233 } else {
@@ -129,7 +247,8 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
129 247
130 // Idle cores are bad. We're going to try to migrate threads to each idle core in turn. 248 // Idle cores are bad. We're going to try to migrate threads to each idle core in turn.
131 while (idle_cores != 0) { 249 while (idle_cores != 0) {
132 const auto core_id = static_cast<u32>(std::countr_zero(idle_cores)); 250 const s32 core_id = static_cast<s32>(std::countr_zero(idle_cores));
251
133 if (KThread* suggested = priority_queue.GetSuggestedFront(core_id); suggested != nullptr) { 252 if (KThread* suggested = priority_queue.GetSuggestedFront(core_id); suggested != nullptr) {
134 s32 migration_candidates[Core::Hardware::NUM_CPU_CORES]; 253 s32 migration_candidates[Core::Hardware::NUM_CPU_CORES];
135 size_t num_candidates = 0; 254 size_t num_candidates = 0;
@@ -150,7 +269,6 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
150 // The suggested thread isn't bound to its core, so we can migrate it! 269 // The suggested thread isn't bound to its core, so we can migrate it!
151 suggested->SetActiveCore(core_id); 270 suggested->SetActiveCore(core_id);
152 priority_queue.ChangeCore(suggested_core, suggested); 271 priority_queue.ChangeCore(suggested_core, suggested);
153
154 top_threads[core_id] = suggested; 272 top_threads[core_id] = suggested;
155 cores_needing_scheduling |= 273 cores_needing_scheduling |=
156 kernel.Scheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]); 274 kernel.Scheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]);
@@ -183,7 +301,6 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
183 // Perform the migration. 301 // Perform the migration.
184 suggested->SetActiveCore(core_id); 302 suggested->SetActiveCore(core_id);
185 priority_queue.ChangeCore(candidate_core, suggested); 303 priority_queue.ChangeCore(candidate_core, suggested);
186
187 top_threads[core_id] = suggested; 304 top_threads[core_id] = suggested;
188 cores_needing_scheduling |= 305 cores_needing_scheduling |=
189 kernel.Scheduler(core_id).UpdateHighestPriorityThread( 306 kernel.Scheduler(core_id).UpdateHighestPriorityThread(
@@ -200,24 +317,210 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
200 return cores_needing_scheduling; 317 return cores_needing_scheduling;
201} 318}
202 319
320void KScheduler::SwitchThread(KThread* next_thread) {
321 KProcess* const cur_process = kernel.CurrentProcess();
322 KThread* const cur_thread = GetCurrentThreadPointer(kernel);
323
324 // We never want to schedule a null thread, so use the idle thread if we don't have a next.
325 if (next_thread == nullptr) {
326 next_thread = m_idle_thread;
327 }
328
329 if (next_thread->GetCurrentCore() != m_core_id) {
330 next_thread->SetCurrentCore(m_core_id);
331 }
332
333 // If we're not actually switching thread, there's nothing to do.
334 if (next_thread == cur_thread) {
335 return;
336 }
337
338 // Next thread is now known not to be nullptr, and must not be dispatchable.
339 ASSERT(next_thread->GetDisableDispatchCount() == 1);
340 ASSERT(!next_thread->IsDummyThread());
341
342 // Update the CPU time tracking variables.
343 const s64 prev_tick = m_last_context_switch_time;
344 const s64 cur_tick = kernel.System().CoreTiming().GetCPUTicks();
345 const s64 tick_diff = cur_tick - prev_tick;
346 cur_thread->AddCpuTime(m_core_id, tick_diff);
347 if (cur_process != nullptr) {
348 cur_process->UpdateCPUTimeTicks(tick_diff);
349 }
350 m_last_context_switch_time = cur_tick;
351
352 // Update our previous thread.
353 if (cur_process != nullptr) {
354 if (!cur_thread->IsTerminationRequested() && cur_thread->GetActiveCore() == m_core_id)
355 [[likely]] {
356 m_state.prev_thread = cur_thread;
357 } else {
358 m_state.prev_thread = nullptr;
359 }
360 }
361
362 // Switch the current process, if we're switching processes.
363 // if (KProcess *next_process = next_thread->GetOwnerProcess(); next_process != cur_process) {
364 // KProcess::Switch(cur_process, next_process);
365 // }
366
367 // Set the new thread.
368 SetCurrentThread(kernel, next_thread);
369 m_current_thread = next_thread;
370
371 // Set the new Thread Local region.
372 // cpu::SwitchThreadLocalRegion(GetInteger(next_thread->GetThreadLocalRegionAddress()));
373}
374
375void KScheduler::ScheduleImpl() {
376 // First, clear the needs scheduling bool.
377 m_state.needs_scheduling.store(false, std::memory_order_seq_cst);
378
379 // Load the appropriate thread pointers for scheduling.
380 KThread* const cur_thread{GetCurrentThreadPointer(kernel)};
381 KThread* highest_priority_thread{m_state.highest_priority_thread};
382
383 // Check whether there are runnable interrupt tasks.
384 if (m_state.interrupt_task_runnable) {
385 // The interrupt task is runnable.
386 // We want to switch to the interrupt task/idle thread.
387 highest_priority_thread = nullptr;
388 }
389
390 // If there aren't, we want to check if the highest priority thread is the same as the current
391 // thread.
392 if (highest_priority_thread == cur_thread) {
393 // If they're the same, then we can just return.
394 return;
395 }
396
397 // The highest priority thread is not the same as the current thread.
398 // Jump to the switcher and continue executing from there.
399 m_switch_cur_thread = cur_thread;
400 m_switch_highest_priority_thread = highest_priority_thread;
401 m_switch_from_schedule = true;
402 Common::Fiber::YieldTo(cur_thread->host_context, *m_switch_fiber);
403
404 // Returning from ScheduleImpl occurs after this thread has been scheduled again.
405}
406
407void KScheduler::ScheduleImplFiber() {
408 KThread* const cur_thread{m_switch_cur_thread};
409 KThread* highest_priority_thread{m_switch_highest_priority_thread};
410
411 // If we're not coming from scheduling (i.e., we came from SC preemption),
412 // we should restart the scheduling loop directly. Not accurate to HOS.
413 if (!m_switch_from_schedule) {
414 goto retry;
415 }
416
417 // Mark that we are not coming from scheduling anymore.
418 m_switch_from_schedule = false;
419
420 // Save the original thread context.
421 Unload(cur_thread);
422
423 // The current thread's context has been entirely taken care of.
424 // Now we want to loop until we successfully switch the thread context.
425 while (true) {
426 // We're starting to try to do the context switch.
427 // Check if the highest priority thread is null.
428 if (!highest_priority_thread) {
429 // The next thread is nullptr!
430
431 // Switch to the idle thread. Note: HOS treats idling as a special case for
432 // performance. This is not *required* for yuzu's purposes, and for singlecore
433 // compatibility, we can just move the logic that would go here into the execution
434 // of the idle thread. If we ever remove singlecore, we should implement this
435 // accurately to HOS.
436 highest_priority_thread = m_idle_thread;
437 }
438
439 // We want to try to lock the highest priority thread's context.
440 // Try to take it.
441 while (!highest_priority_thread->context_guard.try_lock()) {
442 // The highest priority thread's context is already locked.
443 // Check if we need scheduling. If we don't, we can retry directly.
444 if (m_state.needs_scheduling.load(std::memory_order_seq_cst)) {
445 // If we do, another core is interfering, and we must start again.
446 goto retry;
447 }
448 }
449
450 // It's time to switch the thread.
451 // Switch to the highest priority thread.
452 SwitchThread(highest_priority_thread);
453
454 // Check if we need scheduling. If we do, then we can't complete the switch and should
455 // retry.
456 if (m_state.needs_scheduling.load(std::memory_order_seq_cst)) {
457 // Our switch failed.
458 // We should unlock the thread context, and then retry.
459 highest_priority_thread->context_guard.unlock();
460 goto retry;
461 } else {
462 break;
463 }
464
465 retry:
466
467 // We failed to successfully do the context switch, and need to retry.
468 // Clear needs_scheduling.
469 m_state.needs_scheduling.store(false, std::memory_order_seq_cst);
470
471 // Refresh the highest priority thread.
472 highest_priority_thread = m_state.highest_priority_thread;
473 }
474
475 // Reload the guest thread context.
476 Reload(highest_priority_thread);
477
478 // Reload the host thread.
479 Common::Fiber::YieldTo(m_switch_fiber, *highest_priority_thread->host_context);
480}
481
482void KScheduler::Unload(KThread* thread) {
483 auto& cpu_core = kernel.System().ArmInterface(m_core_id);
484 cpu_core.SaveContext(thread->GetContext32());
485 cpu_core.SaveContext(thread->GetContext64());
486 // Save the TPIDR_EL0 system register in case it was modified.
487 thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
488 cpu_core.ClearExclusiveState();
489
490 // Check if the thread is terminated by checking the DPC flags.
491 if ((thread->GetStackParameters().dpc_flags & static_cast<u32>(DpcFlag::Terminated)) == 0) {
492 // The thread isn't terminated, so we want to unlock it.
493 thread->context_guard.unlock();
494 }
495}
496
497void KScheduler::Reload(KThread* thread) {
498 auto& cpu_core = kernel.System().ArmInterface(m_core_id);
499 cpu_core.LoadContext(thread->GetContext32());
500 cpu_core.LoadContext(thread->GetContext64());
501 cpu_core.SetTlsAddress(thread->GetTLSAddress());
502 cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0());
503 cpu_core.LoadWatchpointArray(thread->GetOwnerProcess()->GetWatchpoints());
504 cpu_core.ClearExclusiveState();
505}
506
203void KScheduler::ClearPreviousThread(KernelCore& kernel, KThread* thread) { 507void KScheduler::ClearPreviousThread(KernelCore& kernel, KThread* thread) {
204 ASSERT(kernel.GlobalSchedulerContext().IsLocked()); 508 ASSERT(IsSchedulerLockedByCurrentThread(kernel));
205 for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; ++i) { 509 for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; ++i) {
206 // Get an atomic reference to the core scheduler's previous thread. 510 // Get an atomic reference to the core scheduler's previous thread.
207 std::atomic_ref<KThread*> prev_thread(kernel.Scheduler(static_cast<s32>(i)).prev_thread); 511 auto& prev_thread{kernel.Scheduler(i).m_state.prev_thread};
208 static_assert(std::atomic_ref<KThread*>::is_always_lock_free);
209 512
210 // Atomically clear the previous thread if it's our target. 513 // Atomically clear the previous thread if it's our target.
211 KThread* compare = thread; 514 KThread* compare = thread;
212 prev_thread.compare_exchange_strong(compare, nullptr); 515 prev_thread.compare_exchange_strong(compare, nullptr, std::memory_order_seq_cst);
213 } 516 }
214} 517}
215 518
216void KScheduler::OnThreadStateChanged(KernelCore& kernel, KThread* thread, ThreadState old_state) { 519void KScheduler::OnThreadStateChanged(KernelCore& kernel, KThread* thread, ThreadState old_state) {
217 ASSERT(kernel.GlobalSchedulerContext().IsLocked()); 520 ASSERT(IsSchedulerLockedByCurrentThread(kernel));
218 521
219 // Check if the state has changed, because if it hasn't there's nothing to do. 522 // Check if the state has changed, because if it hasn't there's nothing to do.
220 const auto cur_state = thread->GetRawState(); 523 const ThreadState cur_state = thread->GetRawState();
221 if (cur_state == old_state) { 524 if (cur_state == old_state) {
222 return; 525 return;
223 } 526 }
@@ -237,12 +540,12 @@ void KScheduler::OnThreadStateChanged(KernelCore& kernel, KThread* thread, Threa
237} 540}
238 541
239void KScheduler::OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s32 old_priority) { 542void KScheduler::OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s32 old_priority) {
240 ASSERT(kernel.GlobalSchedulerContext().IsLocked()); 543 ASSERT(IsSchedulerLockedByCurrentThread(kernel));
241 544
242 // If the thread is runnable, we want to change its priority in the queue. 545 // If the thread is runnable, we want to change its priority in the queue.
243 if (thread->GetRawState() == ThreadState::Runnable) { 546 if (thread->GetRawState() == ThreadState::Runnable) {
244 GetPriorityQueue(kernel).ChangePriority(old_priority, 547 GetPriorityQueue(kernel).ChangePriority(old_priority,
245 thread == kernel.GetCurrentEmuThread(), thread); 548 thread == GetCurrentThreadPointer(kernel), thread);
246 IncrementScheduledCount(thread); 549 IncrementScheduledCount(thread);
247 SetSchedulerUpdateNeeded(kernel); 550 SetSchedulerUpdateNeeded(kernel);
248 } 551 }
@@ -250,7 +553,7 @@ void KScheduler::OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s3
250 553
251void KScheduler::OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread, 554void KScheduler::OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread,
252 const KAffinityMask& old_affinity, s32 old_core) { 555 const KAffinityMask& old_affinity, s32 old_core) {
253 ASSERT(kernel.GlobalSchedulerContext().IsLocked()); 556 ASSERT(IsSchedulerLockedByCurrentThread(kernel));
254 557
255 // If the thread is runnable, we want to change its affinity in the queue. 558 // If the thread is runnable, we want to change its affinity in the queue.
256 if (thread->GetRawState() == ThreadState::Runnable) { 559 if (thread->GetRawState() == ThreadState::Runnable) {
@@ -260,15 +563,14 @@ void KScheduler::OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread
260 } 563 }
261} 564}
262 565
263void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) { 566void KScheduler::RotateScheduledQueue(KernelCore& kernel, s32 core_id, s32 priority) {
264 ASSERT(system.GlobalSchedulerContext().IsLocked()); 567 ASSERT(IsSchedulerLockedByCurrentThread(kernel));
265 568
266 // Get a reference to the priority queue. 569 // Get a reference to the priority queue.
267 auto& kernel = system.Kernel();
268 auto& priority_queue = GetPriorityQueue(kernel); 570 auto& priority_queue = GetPriorityQueue(kernel);
269 571
270 // Rotate the front of the queue to the end. 572 // Rotate the front of the queue to the end.
271 KThread* top_thread = priority_queue.GetScheduledFront(cpu_core_id, priority); 573 KThread* top_thread = priority_queue.GetScheduledFront(core_id, priority);
272 KThread* next_thread = nullptr; 574 KThread* next_thread = nullptr;
273 if (top_thread != nullptr) { 575 if (top_thread != nullptr) {
274 next_thread = priority_queue.MoveToScheduledBack(top_thread); 576 next_thread = priority_queue.MoveToScheduledBack(top_thread);
@@ -280,7 +582,7 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) {
280 582
281 // While we have a suggested thread, try to migrate it! 583 // While we have a suggested thread, try to migrate it!
282 { 584 {
283 KThread* suggested = priority_queue.GetSuggestedFront(cpu_core_id, priority); 585 KThread* suggested = priority_queue.GetSuggestedFront(core_id, priority);
284 while (suggested != nullptr) { 586 while (suggested != nullptr) {
285 // Check if the suggested thread is the top thread on its core. 587 // Check if the suggested thread is the top thread on its core.
286 const s32 suggested_core = suggested->GetActiveCore(); 588 const s32 suggested_core = suggested->GetActiveCore();
@@ -301,7 +603,7 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) {
301 // to the front of the queue. 603 // to the front of the queue.
302 if (top_on_suggested_core == nullptr || 604 if (top_on_suggested_core == nullptr ||
303 top_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) { 605 top_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) {
304 suggested->SetActiveCore(cpu_core_id); 606 suggested->SetActiveCore(core_id);
305 priority_queue.ChangeCore(suggested_core, suggested, true); 607 priority_queue.ChangeCore(suggested_core, suggested, true);
306 IncrementScheduledCount(suggested); 608 IncrementScheduledCount(suggested);
307 break; 609 break;
@@ -309,22 +611,21 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) {
309 } 611 }
310 612
311 // Get the next suggestion. 613 // Get the next suggestion.
312 suggested = priority_queue.GetSamePriorityNext(cpu_core_id, suggested); 614 suggested = priority_queue.GetSamePriorityNext(core_id, suggested);
313 } 615 }
314 } 616 }
315 617
316 // Now that we might have migrated a thread with the same priority, check if we can do better. 618 // Now that we might have migrated a thread with the same priority, check if we can do better.
317
318 { 619 {
319 KThread* best_thread = priority_queue.GetScheduledFront(cpu_core_id); 620 KThread* best_thread = priority_queue.GetScheduledFront(core_id);
320 if (best_thread == GetCurrentThreadPointer(kernel)) { 621 if (best_thread == GetCurrentThreadPointer(kernel)) {
321 best_thread = priority_queue.GetScheduledNext(cpu_core_id, best_thread); 622 best_thread = priority_queue.GetScheduledNext(core_id, best_thread);
322 } 623 }
323 624
324 // If the best thread we can choose has a priority the same or worse than ours, try to 625 // If the best thread we can choose has a priority the same or worse than ours, try to
325 // migrate a higher priority thread. 626 // migrate a higher priority thread.
326 if (best_thread != nullptr && best_thread->GetPriority() >= priority) { 627 if (best_thread != nullptr && best_thread->GetPriority() >= priority) {
327 KThread* suggested = priority_queue.GetSuggestedFront(cpu_core_id); 628 KThread* suggested = priority_queue.GetSuggestedFront(core_id);
328 while (suggested != nullptr) { 629 while (suggested != nullptr) {
329 // If the suggestion's priority is the same as ours, don't bother. 630 // If the suggestion's priority is the same as ours, don't bother.
330 if (suggested->GetPriority() >= best_thread->GetPriority()) { 631 if (suggested->GetPriority() >= best_thread->GetPriority()) {
@@ -343,7 +644,7 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) {
343 if (top_on_suggested_core == nullptr || 644 if (top_on_suggested_core == nullptr ||
344 top_on_suggested_core->GetPriority() >= 645 top_on_suggested_core->GetPriority() >=
345 HighestCoreMigrationAllowedPriority) { 646 HighestCoreMigrationAllowedPriority) {
346 suggested->SetActiveCore(cpu_core_id); 647 suggested->SetActiveCore(core_id);
347 priority_queue.ChangeCore(suggested_core, suggested, true); 648 priority_queue.ChangeCore(suggested_core, suggested, true);
348 IncrementScheduledCount(suggested); 649 IncrementScheduledCount(suggested);
349 break; 650 break;
@@ -351,7 +652,7 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) {
351 } 652 }
352 653
353 // Get the next suggestion. 654 // Get the next suggestion.
354 suggested = priority_queue.GetSuggestedNext(cpu_core_id, suggested); 655 suggested = priority_queue.GetSuggestedNext(core_id, suggested);
355 } 656 }
356 } 657 }
357 } 658 }
@@ -360,64 +661,6 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) {
360 SetSchedulerUpdateNeeded(kernel); 661 SetSchedulerUpdateNeeded(kernel);
361} 662}
362 663
363bool KScheduler::CanSchedule(KernelCore& kernel) {
364 return kernel.GetCurrentEmuThread()->GetDisableDispatchCount() <= 1;
365}
366
367bool KScheduler::IsSchedulerUpdateNeeded(const KernelCore& kernel) {
368 return kernel.GlobalSchedulerContext().scheduler_update_needed.load(std::memory_order_acquire);
369}
370
371void KScheduler::SetSchedulerUpdateNeeded(KernelCore& kernel) {
372 kernel.GlobalSchedulerContext().scheduler_update_needed.store(true, std::memory_order_release);
373}
374
375void KScheduler::ClearSchedulerUpdateNeeded(KernelCore& kernel) {
376 kernel.GlobalSchedulerContext().scheduler_update_needed.store(false, std::memory_order_release);
377}
378
379void KScheduler::DisableScheduling(KernelCore& kernel) {
380 // If we are shutting down the kernel, none of this is relevant anymore.
381 if (kernel.IsShuttingDown()) {
382 return;
383 }
384
385 ASSERT(GetCurrentThreadPointer(kernel)->GetDisableDispatchCount() >= 0);
386 GetCurrentThreadPointer(kernel)->DisableDispatch();
387}
388
389void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling) {
390 // If we are shutting down the kernel, none of this is relevant anymore.
391 if (kernel.IsShuttingDown()) {
392 return;
393 }
394
395 auto* current_thread = GetCurrentThreadPointer(kernel);
396
397 ASSERT(current_thread->GetDisableDispatchCount() >= 1);
398
399 if (current_thread->GetDisableDispatchCount() > 1) {
400 current_thread->EnableDispatch();
401 } else {
402 RescheduleCores(kernel, cores_needing_scheduling);
403 }
404
405 // Special case to ensure dummy threads that are waiting block.
406 current_thread->IfDummyThreadTryWait();
407}
408
409u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) {
410 if (IsSchedulerUpdateNeeded(kernel)) {
411 return UpdateHighestPriorityThreadsImpl(kernel);
412 } else {
413 return 0;
414 }
415}
416
417KSchedulerPriorityQueue& KScheduler::GetPriorityQueue(KernelCore& kernel) {
418 return kernel.GlobalSchedulerContext().priority_queue;
419}
420
421void KScheduler::YieldWithoutCoreMigration(KernelCore& kernel) { 664void KScheduler::YieldWithoutCoreMigration(KernelCore& kernel) {
422 // Validate preconditions. 665 // Validate preconditions.
423 ASSERT(CanSchedule(kernel)); 666 ASSERT(CanSchedule(kernel));
@@ -437,7 +680,7 @@ void KScheduler::YieldWithoutCoreMigration(KernelCore& kernel) {
437 680
438 // Perform the yield. 681 // Perform the yield.
439 { 682 {
440 KScopedSchedulerLock lock(kernel); 683 KScopedSchedulerLock sl{kernel};
441 684
442 const auto cur_state = cur_thread.GetRawState(); 685 const auto cur_state = cur_thread.GetRawState();
443 if (cur_state == ThreadState::Runnable) { 686 if (cur_state == ThreadState::Runnable) {
@@ -476,7 +719,7 @@ void KScheduler::YieldWithCoreMigration(KernelCore& kernel) {
476 719
477 // Perform the yield. 720 // Perform the yield.
478 { 721 {
479 KScopedSchedulerLock lock(kernel); 722 KScopedSchedulerLock sl{kernel};
480 723
481 const auto cur_state = cur_thread.GetRawState(); 724 const auto cur_state = cur_thread.GetRawState();
482 if (cur_state == ThreadState::Runnable) { 725 if (cur_state == ThreadState::Runnable) {
@@ -496,7 +739,7 @@ void KScheduler::YieldWithCoreMigration(KernelCore& kernel) {
496 739
497 if (KThread* running_on_suggested_core = 740 if (KThread* running_on_suggested_core =
498 (suggested_core >= 0) 741 (suggested_core >= 0)
499 ? kernel.Scheduler(suggested_core).state.highest_priority_thread 742 ? kernel.Scheduler(suggested_core).m_state.highest_priority_thread
500 : nullptr; 743 : nullptr;
501 running_on_suggested_core != suggested) { 744 running_on_suggested_core != suggested) {
502 // If the current thread's priority is higher than our suggestion's we prefer 745 // If the current thread's priority is higher than our suggestion's we prefer
@@ -564,7 +807,7 @@ void KScheduler::YieldToAnyThread(KernelCore& kernel) {
564 807
565 // Perform the yield. 808 // Perform the yield.
566 { 809 {
567 KScopedSchedulerLock lock(kernel); 810 KScopedSchedulerLock sl{kernel};
568 811
569 const auto cur_state = cur_thread.GetRawState(); 812 const auto cur_state = cur_thread.GetRawState();
570 if (cur_state == ThreadState::Runnable) { 813 if (cur_state == ThreadState::Runnable) {
@@ -621,223 +864,19 @@ void KScheduler::YieldToAnyThread(KernelCore& kernel) {
621 } 864 }
622} 865}
623 866
624KScheduler::KScheduler(Core::System& system_, s32 core_id_) : system{system_}, core_id{core_id_} { 867void KScheduler::RescheduleOtherCores(u64 cores_needing_scheduling) {
625 switch_fiber = std::make_shared<Common::Fiber>([this] { SwitchToCurrent(); }); 868 if (const u64 core_mask = cores_needing_scheduling & ~(1ULL << m_core_id); core_mask != 0) {
626 state.needs_scheduling.store(true); 869 RescheduleCores(kernel, core_mask);
627 state.interrupt_task_thread_runnable = false;
628 state.should_count_idle = false;
629 state.idle_count = 0;
630 state.idle_thread_stack = nullptr;
631 state.highest_priority_thread = nullptr;
632}
633
634void KScheduler::Finalize() {
635 if (idle_thread) {
636 idle_thread->Close();
637 idle_thread = nullptr;
638 }
639}
640
641KScheduler::~KScheduler() {
642 ASSERT(!idle_thread);
643}
644
645KThread* KScheduler::GetSchedulerCurrentThread() const {
646 if (auto result = current_thread.load(); result) {
647 return result;
648 } 870 }
649 return idle_thread;
650}
651
652u64 KScheduler::GetLastContextSwitchTicks() const {
653 return last_context_switch_time;
654} 871}
655 872
656void KScheduler::RescheduleCurrentCore() { 873void KScheduler::RescheduleCores(KernelCore& kernel, u64 core_mask) {
657 ASSERT(GetCurrentThread(system.Kernel()).GetDisableDispatchCount() == 1); 874 // Send IPI
658 875 for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
659 auto& phys_core = system.Kernel().PhysicalCore(core_id); 876 if (core_mask & (1ULL << i)) {
660 if (phys_core.IsInterrupted()) { 877 kernel.PhysicalCore(i).Interrupt();
661 phys_core.ClearInterrupt();
662 }
663
664 guard.Lock();
665 if (state.needs_scheduling.load()) {
666 Schedule();
667 } else {
668 GetCurrentThread(system.Kernel()).EnableDispatch();
669 guard.Unlock();
670 }
671}
672
673void KScheduler::OnThreadStart() {
674 SwitchContextStep2();
675}
676
677void KScheduler::Unload(KThread* thread) {
678 ASSERT(thread);
679
680 LOG_TRACE(Kernel, "core {}, unload thread {}", core_id, thread ? thread->GetName() : "nullptr");
681
682 if (thread->IsCallingSvc()) {
683 thread->ClearIsCallingSvc();
684 }
685
686 auto& physical_core = system.Kernel().PhysicalCore(core_id);
687 if (!physical_core.IsInitialized()) {
688 return;
689 }
690
691 Core::ARM_Interface& cpu_core = physical_core.ArmInterface();
692 cpu_core.SaveContext(thread->GetContext32());
693 cpu_core.SaveContext(thread->GetContext64());
694 // Save the TPIDR_EL0 system register in case it was modified.
695 thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
696 cpu_core.ClearExclusiveState();
697
698 if (!thread->IsTerminationRequested() && thread->GetActiveCore() == core_id) {
699 prev_thread = thread;
700 } else {
701 prev_thread = nullptr;
702 }
703
704 thread->context_guard.unlock();
705}
706
707void KScheduler::Reload(KThread* thread) {
708 LOG_TRACE(Kernel, "core {}, reload thread {}", core_id, thread->GetName());
709
710 Core::ARM_Interface& cpu_core = system.ArmInterface(core_id);
711 cpu_core.LoadContext(thread->GetContext32());
712 cpu_core.LoadContext(thread->GetContext64());
713 cpu_core.LoadWatchpointArray(thread->GetOwnerProcess()->GetWatchpoints());
714 cpu_core.SetTlsAddress(thread->GetTLSAddress());
715 cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0());
716 cpu_core.ClearExclusiveState();
717}
718
719void KScheduler::SwitchContextStep2() {
720 // Load context of new thread
721 Reload(GetCurrentThreadPointer(system.Kernel()));
722
723 RescheduleCurrentCore();
724}
725
726void KScheduler::Schedule() {
727 ASSERT(GetCurrentThread(system.Kernel()).GetDisableDispatchCount() == 1);
728 this->ScheduleImpl();
729}
730
731void KScheduler::ScheduleImpl() {
732 KThread* previous_thread = GetCurrentThreadPointer(system.Kernel());
733 KThread* next_thread = state.highest_priority_thread;
734
735 state.needs_scheduling.store(false);
736
737 // We never want to schedule a null thread, so use the idle thread if we don't have a next.
738 if (next_thread == nullptr) {
739 next_thread = idle_thread;
740 }
741
742 if (next_thread->GetCurrentCore() != core_id) {
743 next_thread->SetCurrentCore(core_id);
744 }
745
746 // We never want to schedule a dummy thread, as these are only used by host threads for locking.
747 if (next_thread->GetThreadType() == ThreadType::Dummy) {
748 ASSERT_MSG(false, "Dummy threads should never be scheduled!");
749 next_thread = idle_thread;
750 }
751
752 // If we're not actually switching thread, there's nothing to do.
753 if (next_thread == current_thread.load()) {
754 previous_thread->EnableDispatch();
755 guard.Unlock();
756 return;
757 }
758
759 // Update the CPU time tracking variables.
760 KProcess* const previous_process = system.Kernel().CurrentProcess();
761 UpdateLastContextSwitchTime(previous_thread, previous_process);
762
763 // Save context for previous thread
764 Unload(previous_thread);
765
766 std::shared_ptr<Common::Fiber>* old_context;
767 old_context = &previous_thread->GetHostContext();
768
769 // Set the new thread.
770 SetCurrentThread(system.Kernel(), next_thread);
771 current_thread.store(next_thread);
772
773 guard.Unlock();
774
775 Common::Fiber::YieldTo(*old_context, *switch_fiber);
776 /// When a thread wakes up, the scheduler may have changed to other in another core.
777 auto& next_scheduler = *system.Kernel().CurrentScheduler();
778 next_scheduler.SwitchContextStep2();
779}
780
781void KScheduler::SwitchToCurrent() {
782 while (true) {
783 {
784 KScopedSpinLock lk{guard};
785 current_thread.store(state.highest_priority_thread);
786 state.needs_scheduling.store(false);
787 } 878 }
788 const auto is_switch_pending = [this] {
789 KScopedSpinLock lk{guard};
790 return state.needs_scheduling.load();
791 };
792 do {
793 auto next_thread = current_thread.load();
794 if (next_thread != nullptr) {
795 const auto locked = next_thread->context_guard.try_lock();
796 if (state.needs_scheduling.load()) {
797 next_thread->context_guard.unlock();
798 break;
799 }
800 if (next_thread->GetActiveCore() != core_id) {
801 next_thread->context_guard.unlock();
802 break;
803 }
804 if (!locked) {
805 continue;
806 }
807 }
808 auto thread = next_thread ? next_thread : idle_thread;
809 SetCurrentThread(system.Kernel(), thread);
810 Common::Fiber::YieldTo(switch_fiber, *thread->GetHostContext());
811 } while (!is_switch_pending());
812 } 879 }
813} 880}
814 881
815void KScheduler::UpdateLastContextSwitchTime(KThread* thread, KProcess* process) {
816 const u64 prev_switch_ticks = last_context_switch_time;
817 const u64 most_recent_switch_ticks = system.CoreTiming().GetCPUTicks();
818 const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks;
819
820 if (thread != nullptr) {
821 thread->AddCpuTime(core_id, update_ticks);
822 }
823
824 if (process != nullptr) {
825 process->UpdateCPUTimeTicks(update_ticks);
826 }
827
828 last_context_switch_time = most_recent_switch_ticks;
829}
830
831void KScheduler::Initialize() {
832 idle_thread = KThread::Create(system.Kernel());
833 ASSERT(KThread::InitializeIdleThread(system, idle_thread, core_id).IsSuccess());
834 idle_thread->SetName(fmt::format("IdleThread:{}", core_id));
835 idle_thread->EnableDispatch();
836}
837
838KScopedSchedulerLock::KScopedSchedulerLock(KernelCore& kernel)
839 : KScopedLock(kernel.GlobalSchedulerContext().SchedulerLock()) {}
840
841KScopedSchedulerLock::~KScopedSchedulerLock() = default;
842
843} // namespace Kernel 882} // namespace Kernel
diff --git a/src/core/hle/kernel/k_scheduler.h b/src/core/hle/kernel/k_scheduler.h
index 6a4760eca..534321d8d 100644
--- a/src/core/hle/kernel/k_scheduler.h
+++ b/src/core/hle/kernel/k_scheduler.h
@@ -11,6 +11,7 @@
11#include "core/hle/kernel/k_scheduler_lock.h" 11#include "core/hle/kernel/k_scheduler_lock.h"
12#include "core/hle/kernel/k_scoped_lock.h" 12#include "core/hle/kernel/k_scoped_lock.h"
13#include "core/hle/kernel/k_spin_lock.h" 13#include "core/hle/kernel/k_spin_lock.h"
14#include "core/hle/kernel/k_thread.h"
14 15
15namespace Common { 16namespace Common {
16class Fiber; 17class Fiber;
@@ -23,184 +24,150 @@ class System;
23namespace Kernel { 24namespace Kernel {
24 25
25class KernelCore; 26class KernelCore;
27class KInterruptTaskManager;
26class KProcess; 28class KProcess;
27class SchedulerLock;
28class KThread; 29class KThread;
30class KScopedDisableDispatch;
31class KScopedSchedulerLock;
32class KScopedSchedulerLockAndSleep;
29 33
30class KScheduler final { 34class KScheduler final {
31public: 35public:
32 explicit KScheduler(Core::System& system_, s32 core_id_); 36 YUZU_NON_COPYABLE(KScheduler);
33 ~KScheduler(); 37 YUZU_NON_MOVEABLE(KScheduler);
34
35 void Finalize();
36 38
37 /// Reschedules to the next available thread (call after current thread is suspended) 39 using LockType = KAbstractSchedulerLock<KScheduler>;
38 void RescheduleCurrentCore();
39 40
40 /// Reschedules cores pending reschedule, to be called on EnableScheduling. 41 explicit KScheduler(KernelCore& kernel);
41 static void RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedule); 42 ~KScheduler();
42 43
43 /// The next two are for SingleCore Only. 44 void Initialize(KThread* main_thread, KThread* idle_thread, s32 core_id);
44 /// Unload current thread before preempting core. 45 void Activate();
46 void OnThreadStart();
45 void Unload(KThread* thread); 47 void Unload(KThread* thread);
46
47 /// Reload current thread after core preemption.
48 void Reload(KThread* thread); 48 void Reload(KThread* thread);
49 49
50 /// Gets the current running thread 50 void SetInterruptTaskRunnable();
51 [[nodiscard]] KThread* GetSchedulerCurrentThread() const; 51 void RequestScheduleOnInterrupt();
52 void PreemptSingleCore();
52 53
53 /// Gets the idle thread 54 u64 GetIdleCount() {
54 [[nodiscard]] KThread* GetIdleThread() const { 55 return m_state.idle_count;
55 return idle_thread;
56 } 56 }
57 57
58 /// Returns true if the scheduler is idle 58 KThread* GetIdleThread() const {
59 [[nodiscard]] bool IsIdle() const { 59 return m_idle_thread;
60 return GetSchedulerCurrentThread() == idle_thread;
61 } 60 }
62 61
63 /// Gets the timestamp for the last context switch in ticks. 62 bool IsIdle() const {
64 [[nodiscard]] u64 GetLastContextSwitchTicks() const; 63 return m_current_thread.load() == m_idle_thread;
65
66 [[nodiscard]] bool ContextSwitchPending() const {
67 return state.needs_scheduling.load(std::memory_order_relaxed);
68 } 64 }
69 65
70 void Initialize(); 66 KThread* GetPreviousThread() const {
67 return m_state.prev_thread;
68 }
71 69
72 void OnThreadStart(); 70 KThread* GetSchedulerCurrentThread() const {
71 return m_current_thread.load();
72 }
73 73
74 [[nodiscard]] std::shared_ptr<Common::Fiber>& ControlContext() { 74 s64 GetLastContextSwitchTime() const {
75 return switch_fiber; 75 return m_last_context_switch_time;
76 } 76 }
77 77
78 [[nodiscard]] const std::shared_ptr<Common::Fiber>& ControlContext() const { 78 // Static public API.
79 return switch_fiber; 79 static bool CanSchedule(KernelCore& kernel) {
80 return GetCurrentThread(kernel).GetDisableDispatchCount() == 0;
81 }
82 static bool IsSchedulerLockedByCurrentThread(KernelCore& kernel) {
83 return kernel.GlobalSchedulerContext().scheduler_lock.IsLockedByCurrentThread();
80 } 84 }
81 85
82 [[nodiscard]] u64 UpdateHighestPriorityThread(KThread* highest_thread); 86 static bool IsSchedulerUpdateNeeded(KernelCore& kernel) {
87 return kernel.GlobalSchedulerContext().scheduler_update_needed;
88 }
89 static void SetSchedulerUpdateNeeded(KernelCore& kernel) {
90 kernel.GlobalSchedulerContext().scheduler_update_needed = true;
91 }
92 static void ClearSchedulerUpdateNeeded(KernelCore& kernel) {
93 kernel.GlobalSchedulerContext().scheduler_update_needed = false;
94 }
83 95
84 /** 96 static void DisableScheduling(KernelCore& kernel);
85 * Takes a thread and moves it to the back of the it's priority list. 97 static void EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling);
86 *
87 * @note This operation can be redundant and no scheduling is changed if marked as so.
88 */
89 static void YieldWithoutCoreMigration(KernelCore& kernel);
90 98
91 /** 99 static u64 UpdateHighestPriorityThreads(KernelCore& kernel);
92 * Takes a thread and moves it to the back of the it's priority list.
93 * Afterwards, tries to pick a suggested thread from the suggested queue that has worse time or
94 * a better priority than the next thread in the core.
95 *
96 * @note This operation can be redundant and no scheduling is changed if marked as so.
97 */
98 static void YieldWithCoreMigration(KernelCore& kernel);
99
100 /**
101 * Takes a thread and moves it out of the scheduling queue.
102 * and into the suggested queue. If no thread can be scheduled afterwards in that core,
103 * a suggested thread is obtained instead.
104 *
105 * @note This operation can be redundant and no scheduling is changed if marked as so.
106 */
107 static void YieldToAnyThread(KernelCore& kernel);
108 100
109 static void ClearPreviousThread(KernelCore& kernel, KThread* thread); 101 static void ClearPreviousThread(KernelCore& kernel, KThread* thread);
110 102
111 /// Notify the scheduler a thread's status has changed.
112 static void OnThreadStateChanged(KernelCore& kernel, KThread* thread, ThreadState old_state); 103 static void OnThreadStateChanged(KernelCore& kernel, KThread* thread, ThreadState old_state);
113
114 /// Notify the scheduler a thread's priority has changed.
115 static void OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s32 old_priority); 104 static void OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s32 old_priority);
116
117 /// Notify the scheduler a thread's core and/or affinity mask has changed.
118 static void OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread, 105 static void OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread,
119 const KAffinityMask& old_affinity, s32 old_core); 106 const KAffinityMask& old_affinity, s32 old_core);
120 107
121 static bool CanSchedule(KernelCore& kernel); 108 static void RotateScheduledQueue(KernelCore& kernel, s32 core_id, s32 priority);
122 static bool IsSchedulerUpdateNeeded(const KernelCore& kernel); 109 static void RescheduleCores(KernelCore& kernel, u64 cores_needing_scheduling);
123 static void SetSchedulerUpdateNeeded(KernelCore& kernel); 110
124 static void ClearSchedulerUpdateNeeded(KernelCore& kernel); 111 static void YieldWithoutCoreMigration(KernelCore& kernel);
125 static void DisableScheduling(KernelCore& kernel); 112 static void YieldWithCoreMigration(KernelCore& kernel);
126 static void EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling); 113 static void YieldToAnyThread(KernelCore& kernel);
127 [[nodiscard]] static u64 UpdateHighestPriorityThreads(KernelCore& kernel);
128 114
129private: 115private:
130 friend class GlobalSchedulerContext; 116 // Static private API.
131 117 static KSchedulerPriorityQueue& GetPriorityQueue(KernelCore& kernel) {
132 /** 118 return kernel.GlobalSchedulerContext().priority_queue;
133 * Takes care of selecting the new scheduled threads in three steps: 119 }
134 * 120 static u64 UpdateHighestPriorityThreadsImpl(KernelCore& kernel);
135 * 1. First a thread is selected from the top of the priority queue. If no thread
136 * is obtained then we move to step two, else we are done.
137 *
138 * 2. Second we try to get a suggested thread that's not assigned to any core or
139 * that is not the top thread in that core.
140 *
141 * 3. Third is no suggested thread is found, we do a second pass and pick a running
142 * thread in another core and swap it with its current thread.
143 *
144 * returns the cores needing scheduling.
145 */
146 [[nodiscard]] static u64 UpdateHighestPriorityThreadsImpl(KernelCore& kernel);
147
148 [[nodiscard]] static KSchedulerPriorityQueue& GetPriorityQueue(KernelCore& kernel);
149
150 void RotateScheduledQueue(s32 cpu_core_id, s32 priority);
151 121
152 void Schedule(); 122 static void RescheduleCurrentHLEThread(KernelCore& kernel);
153 123
154 /// Switches the CPU's active thread context to that of the specified thread 124 // Instanced private API.
155 void ScheduleImpl(); 125 void ScheduleImpl();
126 void ScheduleImplFiber();
127 void SwitchThread(KThread* next_thread);
156 128
157 /// When a thread wakes up, it must run this through it's new scheduler 129 void Schedule();
158 void SwitchContextStep2(); 130 void ScheduleOnInterrupt();
159
160 /**
161 * Called on every context switch to update the internal timestamp
162 * This also updates the running time ticks for the given thread and
163 * process using the following difference:
164 *
165 * ticks += most_recent_ticks - last_context_switch_ticks
166 *
167 * The internal tick timestamp for the scheduler is simply the
168 * most recent tick count retrieved. No special arithmetic is
169 * applied to it.
170 */
171 void UpdateLastContextSwitchTime(KThread* thread, KProcess* process);
172
173 void SwitchToCurrent();
174 131
175 KThread* prev_thread{}; 132 void RescheduleOtherCores(u64 cores_needing_scheduling);
176 std::atomic<KThread*> current_thread{}; 133 void RescheduleCurrentCore();
134 void RescheduleCurrentCoreImpl();
177 135
178 KThread* idle_thread{}; 136 u64 UpdateHighestPriorityThread(KThread* thread);
179 137
180 std::shared_ptr<Common::Fiber> switch_fiber{}; 138private:
139 friend class KScopedDisableDispatch;
181 140
182 struct SchedulingState { 141 struct SchedulingState {
183 std::atomic<bool> needs_scheduling{}; 142 std::atomic<bool> needs_scheduling{false};
184 bool interrupt_task_thread_runnable{}; 143 bool interrupt_task_runnable{false};
185 bool should_count_idle{}; 144 bool should_count_idle{false};
186 u64 idle_count{}; 145 u64 idle_count{0};
187 KThread* highest_priority_thread{}; 146 KThread* highest_priority_thread{nullptr};
188 void* idle_thread_stack{}; 147 void* idle_thread_stack{nullptr};
148 std::atomic<KThread*> prev_thread{nullptr};
149 KInterruptTaskManager* interrupt_task_manager{nullptr};
189 }; 150 };
190 151
191 SchedulingState state; 152 KernelCore& kernel;
192 153 SchedulingState m_state;
193 Core::System& system; 154 bool m_is_active{false};
194 u64 last_context_switch_time{}; 155 s32 m_core_id{0};
195 const s32 core_id; 156 s64 m_last_context_switch_time{0};
196 157 KThread* m_idle_thread{nullptr};
197 KSpinLock guard{}; 158 std::atomic<KThread*> m_current_thread{nullptr};
159
160 std::shared_ptr<Common::Fiber> m_switch_fiber{};
161 KThread* m_switch_cur_thread{};
162 KThread* m_switch_highest_priority_thread{};
163 bool m_switch_from_schedule{};
198}; 164};
199 165
200class [[nodiscard]] KScopedSchedulerLock : KScopedLock<GlobalSchedulerContext::LockType> { 166class KScopedSchedulerLock : public KScopedLock<KScheduler::LockType> {
201public: 167public:
202 explicit KScopedSchedulerLock(KernelCore& kernel); 168 explicit KScopedSchedulerLock(KernelCore& kernel)
203 ~KScopedSchedulerLock(); 169 : KScopedLock(kernel.GlobalSchedulerContext().scheduler_lock) {}
170 ~KScopedSchedulerLock() = default;
204}; 171};
205 172
206} // namespace Kernel 173} // namespace Kernel
diff --git a/src/core/hle/kernel/k_scheduler_lock.h b/src/core/hle/kernel/k_scheduler_lock.h
index 4fa256970..73314b45e 100644
--- a/src/core/hle/kernel/k_scheduler_lock.h
+++ b/src/core/hle/kernel/k_scheduler_lock.h
@@ -5,9 +5,11 @@
5 5
6#include <atomic> 6#include <atomic>
7#include "common/assert.h" 7#include "common/assert.h"
8#include "core/hle/kernel/k_interrupt_manager.h"
8#include "core/hle/kernel/k_spin_lock.h" 9#include "core/hle/kernel/k_spin_lock.h"
9#include "core/hle/kernel/k_thread.h" 10#include "core/hle/kernel/k_thread.h"
10#include "core/hle/kernel/kernel.h" 11#include "core/hle/kernel/kernel.h"
12#include "core/hle/kernel/physical_core.h"
11 13
12namespace Kernel { 14namespace Kernel {
13 15
diff --git a/src/core/hle/kernel/k_shared_memory.cpp b/src/core/hle/kernel/k_shared_memory.cpp
index b77735736..8ff1545b6 100644
--- a/src/core/hle/kernel/k_shared_memory.cpp
+++ b/src/core/hle/kernel/k_shared_memory.cpp
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include "common/assert.h" 4#include "common/assert.h"
6#include "core/core.h" 5#include "core/core.h"
diff --git a/src/core/hle/kernel/k_shared_memory.h b/src/core/hle/kernel/k_shared_memory.h
index 2c1db0e70..34cb98456 100644
--- a/src/core/hle/kernel/k_shared_memory.h
+++ b/src/core/hle/kernel/k_shared_memory.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp
index 90de86770..174afc80d 100644
--- a/src/core/hle/kernel/k_thread.cpp
+++ b/src/core/hle/kernel/k_thread.cpp
@@ -258,7 +258,18 @@ Result KThread::InitializeThread(KThread* thread, KThreadFunction func, uintptr_
258} 258}
259 259
260Result KThread::InitializeDummyThread(KThread* thread) { 260Result KThread::InitializeDummyThread(KThread* thread) {
261 return thread->Initialize({}, {}, {}, DummyThreadPriority, 3, {}, ThreadType::Dummy); 261 // Initialize the thread.
262 R_TRY(thread->Initialize({}, {}, {}, DummyThreadPriority, 3, {}, ThreadType::Dummy));
263
264 // Initialize emulation parameters.
265 thread->stack_parameters.disable_count = 0;
266
267 return ResultSuccess;
268}
269
270Result KThread::InitializeMainThread(Core::System& system, KThread* thread, s32 virt_core) {
271 return InitializeThread(thread, {}, {}, {}, IdleThreadPriority, virt_core, {}, ThreadType::Main,
272 system.GetCpuManager().GetGuestActivateFunc());
262} 273}
263 274
264Result KThread::InitializeIdleThread(Core::System& system, KThread* thread, s32 virt_core) { 275Result KThread::InitializeIdleThread(Core::System& system, KThread* thread, s32 virt_core) {
@@ -277,7 +288,7 @@ Result KThread::InitializeUserThread(Core::System& system, KThread* thread, KThr
277 KProcess* owner) { 288 KProcess* owner) {
278 system.Kernel().GlobalSchedulerContext().AddThread(thread); 289 system.Kernel().GlobalSchedulerContext().AddThread(thread);
279 return InitializeThread(thread, func, arg, user_stack_top, prio, virt_core, owner, 290 return InitializeThread(thread, func, arg, user_stack_top, prio, virt_core, owner,
280 ThreadType::User, system.GetCpuManager().GetGuestThreadStartFunc()); 291 ThreadType::User, system.GetCpuManager().GetGuestThreadFunc());
281} 292}
282 293
283void KThread::PostDestroy(uintptr_t arg) { 294void KThread::PostDestroy(uintptr_t arg) {
@@ -1058,6 +1069,8 @@ void KThread::Exit() {
1058 // Register the thread as a work task. 1069 // Register the thread as a work task.
1059 KWorkerTaskManager::AddTask(kernel, KWorkerTaskManager::WorkerType::Exit, this); 1070 KWorkerTaskManager::AddTask(kernel, KWorkerTaskManager::WorkerType::Exit, this);
1060 } 1071 }
1072
1073 UNREACHABLE_MSG("KThread::Exit() would return");
1061} 1074}
1062 1075
1063Result KThread::Sleep(s64 timeout) { 1076Result KThread::Sleep(s64 timeout) {
@@ -1093,6 +1106,8 @@ void KThread::IfDummyThreadTryWait() {
1093 return; 1106 return;
1094 } 1107 }
1095 1108
1109 ASSERT(!kernel.IsPhantomModeForSingleCore());
1110
1096 // Block until we are no longer waiting. 1111 // Block until we are no longer waiting.
1097 std::unique_lock lk(dummy_wait_lock); 1112 std::unique_lock lk(dummy_wait_lock);
1098 dummy_wait_cv.wait( 1113 dummy_wait_cv.wait(
@@ -1197,16 +1212,13 @@ KScopedDisableDispatch::~KScopedDisableDispatch() {
1197 return; 1212 return;
1198 } 1213 }
1199 1214
1200 // Skip the reschedule if single-core, as dispatch tracking is disabled here.
1201 if (!Settings::values.use_multi_core.GetValue()) {
1202 return;
1203 }
1204
1205 if (GetCurrentThread(kernel).GetDisableDispatchCount() <= 1) { 1215 if (GetCurrentThread(kernel).GetDisableDispatchCount() <= 1) {
1206 auto scheduler = kernel.CurrentScheduler(); 1216 auto* scheduler = kernel.CurrentScheduler();
1207 1217
1208 if (scheduler) { 1218 if (scheduler && !kernel.IsPhantomModeForSingleCore()) {
1209 scheduler->RescheduleCurrentCore(); 1219 scheduler->RescheduleCurrentCore();
1220 } else {
1221 KScheduler::RescheduleCurrentHLEThread(kernel);
1210 } 1222 }
1211 } else { 1223 } else {
1212 GetCurrentThread(kernel).EnableDispatch(); 1224 GetCurrentThread(kernel).EnableDispatch();
diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h
index 28cd7ecb0..9ee20208e 100644
--- a/src/core/hle/kernel/k_thread.h
+++ b/src/core/hle/kernel/k_thread.h
@@ -413,6 +413,9 @@ public:
413 413
414 [[nodiscard]] static Result InitializeDummyThread(KThread* thread); 414 [[nodiscard]] static Result InitializeDummyThread(KThread* thread);
415 415
416 [[nodiscard]] static Result InitializeMainThread(Core::System& system, KThread* thread,
417 s32 virt_core);
418
416 [[nodiscard]] static Result InitializeIdleThread(Core::System& system, KThread* thread, 419 [[nodiscard]] static Result InitializeIdleThread(Core::System& system, KThread* thread,
417 s32 virt_core); 420 s32 virt_core);
418 421
@@ -480,39 +483,16 @@ public:
480 return per_core_priority_queue_entry[core]; 483 return per_core_priority_queue_entry[core];
481 } 484 }
482 485
483 [[nodiscard]] bool IsKernelThread() const {
484 return GetActiveCore() == 3;
485 }
486
487 [[nodiscard]] bool IsDispatchTrackingDisabled() const {
488 return is_single_core || IsKernelThread();
489 }
490
491 [[nodiscard]] s32 GetDisableDispatchCount() const { 486 [[nodiscard]] s32 GetDisableDispatchCount() const {
492 if (IsDispatchTrackingDisabled()) {
493 // TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch.
494 return 1;
495 }
496
497 return this->GetStackParameters().disable_count; 487 return this->GetStackParameters().disable_count;
498 } 488 }
499 489
500 void DisableDispatch() { 490 void DisableDispatch() {
501 if (IsDispatchTrackingDisabled()) {
502 // TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch.
503 return;
504 }
505
506 ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 0); 491 ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 0);
507 this->GetStackParameters().disable_count++; 492 this->GetStackParameters().disable_count++;
508 } 493 }
509 494
510 void EnableDispatch() { 495 void EnableDispatch() {
511 if (IsDispatchTrackingDisabled()) {
512 // TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch.
513 return;
514 }
515
516 ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() > 0); 496 ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() > 0);
517 this->GetStackParameters().disable_count--; 497 this->GetStackParameters().disable_count--;
518 } 498 }
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index f23c629dc..f4072e1c3 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -64,8 +64,6 @@ struct KernelCore::Impl {
64 64
65 is_phantom_mode_for_singlecore = false; 65 is_phantom_mode_for_singlecore = false;
66 66
67 InitializePhysicalCores();
68
69 // Derive the initial memory layout from the emulated board 67 // Derive the initial memory layout from the emulated board
70 Init::InitializeSlabResourceCounts(kernel); 68 Init::InitializeSlabResourceCounts(kernel);
71 DeriveInitialMemoryLayout(); 69 DeriveInitialMemoryLayout();
@@ -75,9 +73,9 @@ struct KernelCore::Impl {
75 InitializeSystemResourceLimit(kernel, system.CoreTiming()); 73 InitializeSystemResourceLimit(kernel, system.CoreTiming());
76 InitializeMemoryLayout(); 74 InitializeMemoryLayout();
77 Init::InitializeKPageBufferSlabHeap(system); 75 Init::InitializeKPageBufferSlabHeap(system);
78 InitializeSchedulers();
79 InitializeShutdownThreads(); 76 InitializeShutdownThreads();
80 InitializePreemption(kernel); 77 InitializePreemption(kernel);
78 InitializePhysicalCores();
81 79
82 RegisterHostThread(); 80 RegisterHostThread();
83 } 81 }
@@ -136,7 +134,6 @@ struct KernelCore::Impl {
136 shutdown_threads[core_id] = nullptr; 134 shutdown_threads[core_id] = nullptr;
137 } 135 }
138 136
139 schedulers[core_id]->Finalize();
140 schedulers[core_id].reset(); 137 schedulers[core_id].reset();
141 } 138 }
142 139
@@ -199,14 +196,21 @@ struct KernelCore::Impl {
199 exclusive_monitor = 196 exclusive_monitor =
200 Core::MakeExclusiveMonitor(system.Memory(), Core::Hardware::NUM_CPU_CORES); 197 Core::MakeExclusiveMonitor(system.Memory(), Core::Hardware::NUM_CPU_CORES);
201 for (u32 i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { 198 for (u32 i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
202 schedulers[i] = std::make_unique<Kernel::KScheduler>(system, i); 199 const s32 core{static_cast<s32>(i)};
200
201 schedulers[i] = std::make_unique<Kernel::KScheduler>(system.Kernel());
203 cores.emplace_back(i, system, *schedulers[i], interrupts); 202 cores.emplace_back(i, system, *schedulers[i], interrupts);
204 }
205 }
206 203
207 void InitializeSchedulers() { 204 auto* main_thread{Kernel::KThread::Create(system.Kernel())};
208 for (u32 i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { 205 main_thread->SetName(fmt::format("MainThread:{}", core));
209 cores[i].Scheduler().Initialize(); 206 main_thread->SetCurrentCore(core);
207 ASSERT(Kernel::KThread::InitializeMainThread(system, main_thread, core).IsSuccess());
208
209 auto* idle_thread{Kernel::KThread::Create(system.Kernel())};
210 idle_thread->SetCurrentCore(core);
211 ASSERT(Kernel::KThread::InitializeIdleThread(system, idle_thread, core).IsSuccess());
212
213 schedulers[i]->Initialize(main_thread, idle_thread, core);
210 } 214 }
211 } 215 }
212 216
@@ -1109,10 +1113,11 @@ void KernelCore::Suspend(bool suspended) {
1109} 1113}
1110 1114
1111void KernelCore::ShutdownCores() { 1115void KernelCore::ShutdownCores() {
1116 KScopedSchedulerLock lk{*this};
1117
1112 for (auto* thread : impl->shutdown_threads) { 1118 for (auto* thread : impl->shutdown_threads) {
1113 void(thread->Run()); 1119 void(thread->Run());
1114 } 1120 }
1115 InterruptAllPhysicalCores();
1116} 1121}
1117 1122
1118bool KernelCore::IsMulticore() const { 1123bool KernelCore::IsMulticore() const {
diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp
index a5b16ae2e..6e7dacf97 100644
--- a/src/core/hle/kernel/physical_core.cpp
+++ b/src/core/hle/kernel/physical_core.cpp
@@ -43,6 +43,7 @@ void PhysicalCore::Initialize([[maybe_unused]] bool is_64_bit) {
43 43
44void PhysicalCore::Run() { 44void PhysicalCore::Run() {
45 arm_interface->Run(); 45 arm_interface->Run();
46 arm_interface->ClearExclusiveState();
46} 47}
47 48
48void PhysicalCore::Idle() { 49void PhysicalCore::Idle() {
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 8655506b0..27e5a805d 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -887,7 +887,7 @@ static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle han
887 const auto* const current_thread = GetCurrentThreadPointer(system.Kernel()); 887 const auto* const current_thread = GetCurrentThreadPointer(system.Kernel());
888 const bool same_thread = current_thread == thread.GetPointerUnsafe(); 888 const bool same_thread = current_thread == thread.GetPointerUnsafe();
889 889
890 const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTicks(); 890 const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTime();
891 u64 out_ticks = 0; 891 u64 out_ticks = 0;
892 if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) { 892 if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) {
893 const u64 thread_ticks = current_thread->GetCpuTime(); 893 const u64 thread_ticks = current_thread->GetCpuTime();
@@ -3026,11 +3026,6 @@ void Call(Core::System& system, u32 immediate) {
3026 } 3026 }
3027 3027
3028 kernel.ExitSVCProfile(); 3028 kernel.ExitSVCProfile();
3029
3030 if (!thread->IsCallingSvc()) {
3031 auto* host_context = thread->GetHostContext().get();
3032 host_context->Rewind();
3033 }
3034} 3029}
3035 3030
3036} // namespace Kernel::Svc 3031} // namespace Kernel::Svc
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index aa9e5b89d..4de44cd06 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/core/hle/service/hid/errors.h b/src/core/hle/service/hid/errors.h
index 46282f42e..4613a4e60 100644
--- a/src/core/hle/service/hid/errors.h
+++ b/src/core/hle/service/hid/errors.h
@@ -19,3 +19,10 @@ constexpr Result InvalidNpadId{ErrorModule::HID, 709};
19constexpr Result NpadNotConnected{ErrorModule::HID, 710}; 19constexpr Result NpadNotConnected{ErrorModule::HID, 710};
20 20
21} // namespace Service::HID 21} // namespace Service::HID
22
23namespace Service::IRS {
24
25constexpr Result InvalidProcessorState{ErrorModule::Irsensor, 78};
26constexpr Result InvalidIrCameraHandle{ErrorModule::Irsensor, 204};
27
28} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 89bb12442..5ecbddf94 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -2345,8 +2345,8 @@ void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system
2345 std::make_shared<HidSys>(system)->InstallAsService(service_manager); 2345 std::make_shared<HidSys>(system)->InstallAsService(service_manager);
2346 std::make_shared<HidTmp>(system)->InstallAsService(service_manager); 2346 std::make_shared<HidTmp>(system)->InstallAsService(service_manager);
2347 2347
2348 std::make_shared<IRS>(system)->InstallAsService(service_manager); 2348 std::make_shared<Service::IRS::IRS>(system)->InstallAsService(service_manager);
2349 std::make_shared<IRS_SYS>(system)->InstallAsService(service_manager); 2349 std::make_shared<Service::IRS::IRS_SYS>(system)->InstallAsService(service_manager);
2350 2350
2351 std::make_shared<XCD_SYS>(system)->InstallAsService(service_manager); 2351 std::make_shared<XCD_SYS>(system)->InstallAsService(service_manager);
2352} 2352}
diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index d2a91d913..d5107e41f 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -1,16 +1,28 @@
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 <random>
6
4#include "core/core.h" 7#include "core/core.h"
5#include "core/core_timing.h" 8#include "core/core_timing.h"
9#include "core/hid/emulated_controller.h"
10#include "core/hid/hid_core.h"
6#include "core/hle/ipc_helpers.h" 11#include "core/hle/ipc_helpers.h"
7#include "core/hle/kernel/k_shared_memory.h" 12#include "core/hle/kernel/k_shared_memory.h"
8#include "core/hle/kernel/k_transfer_memory.h" 13#include "core/hle/kernel/k_transfer_memory.h"
9#include "core/hle/kernel/kernel.h" 14#include "core/hle/kernel/kernel.h"
10#include "core/hle/service/hid/errors.h" 15#include "core/hle/service/hid/errors.h"
11#include "core/hle/service/hid/irs.h" 16#include "core/hle/service/hid/irs.h"
17#include "core/hle/service/hid/irsensor/clustering_processor.h"
18#include "core/hle/service/hid/irsensor/image_transfer_processor.h"
19#include "core/hle/service/hid/irsensor/ir_led_processor.h"
20#include "core/hle/service/hid/irsensor/moment_processor.h"
21#include "core/hle/service/hid/irsensor/pointing_processor.h"
22#include "core/hle/service/hid/irsensor/tera_plugin_processor.h"
23#include "core/memory.h"
12 24
13namespace Service::HID { 25namespace Service::IRS {
14 26
15IRS::IRS(Core::System& system_) : ServiceFramework{system_, "irs"} { 27IRS::IRS(Core::System& system_) : ServiceFramework{system_, "irs"} {
16 // clang-format off 28 // clang-format off
@@ -36,14 +48,19 @@ IRS::IRS(Core::System& system_) : ServiceFramework{system_, "irs"} {
36 }; 48 };
37 // clang-format on 49 // clang-format on
38 50
51 u8* raw_shared_memory = system.Kernel().GetIrsSharedMem().GetPointer();
39 RegisterHandlers(functions); 52 RegisterHandlers(functions);
53 shared_memory = std::construct_at(reinterpret_cast<StatusManager*>(raw_shared_memory));
54
55 npad_device = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
40} 56}
57IRS::~IRS() = default;
41 58
42void IRS::ActivateIrsensor(Kernel::HLERequestContext& ctx) { 59void IRS::ActivateIrsensor(Kernel::HLERequestContext& ctx) {
43 IPC::RequestParser rp{ctx}; 60 IPC::RequestParser rp{ctx};
44 const auto applet_resource_user_id{rp.Pop<u64>()}; 61 const auto applet_resource_user_id{rp.Pop<u64>()};
45 62
46 LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}", 63 LOG_WARNING(Service_IRS, "(STUBBED) called, applet_resource_user_id={}",
47 applet_resource_user_id); 64 applet_resource_user_id);
48 65
49 IPC::ResponseBuilder rb{ctx, 2}; 66 IPC::ResponseBuilder rb{ctx, 2};
@@ -54,7 +71,7 @@ void IRS::DeactivateIrsensor(Kernel::HLERequestContext& ctx) {
54 IPC::RequestParser rp{ctx}; 71 IPC::RequestParser rp{ctx};
55 const auto applet_resource_user_id{rp.Pop<u64>()}; 72 const auto applet_resource_user_id{rp.Pop<u64>()};
56 73
57 LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}", 74 LOG_WARNING(Service_IRS, "(STUBBED) called, applet_resource_user_id={}",
58 applet_resource_user_id); 75 applet_resource_user_id);
59 76
60 IPC::ResponseBuilder rb{ctx, 2}; 77 IPC::ResponseBuilder rb{ctx, 2};
@@ -75,7 +92,7 @@ void IRS::GetIrsensorSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
75void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) { 92void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) {
76 IPC::RequestParser rp{ctx}; 93 IPC::RequestParser rp{ctx};
77 struct Parameters { 94 struct Parameters {
78 IrCameraHandle camera_handle; 95 Core::IrSensor::IrCameraHandle camera_handle;
79 INSERT_PADDING_WORDS_NOINIT(1); 96 INSERT_PADDING_WORDS_NOINIT(1);
80 u64 applet_resource_user_id; 97 u64 applet_resource_user_id;
81 }; 98 };
@@ -88,17 +105,23 @@ void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) {
88 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, 105 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
89 parameters.applet_resource_user_id); 106 parameters.applet_resource_user_id);
90 107
108 auto result = IsIrCameraHandleValid(parameters.camera_handle);
109 if (result.IsSuccess()) {
110 // TODO: Stop Image processor
111 result = ResultSuccess;
112 }
113
91 IPC::ResponseBuilder rb{ctx, 2}; 114 IPC::ResponseBuilder rb{ctx, 2};
92 rb.Push(ResultSuccess); 115 rb.Push(result);
93} 116}
94 117
95void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) { 118void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) {
96 IPC::RequestParser rp{ctx}; 119 IPC::RequestParser rp{ctx};
97 struct Parameters { 120 struct Parameters {
98 IrCameraHandle camera_handle; 121 Core::IrSensor::IrCameraHandle camera_handle;
99 INSERT_PADDING_WORDS_NOINIT(1); 122 INSERT_PADDING_WORDS_NOINIT(1);
100 u64 applet_resource_user_id; 123 u64 applet_resource_user_id;
101 PackedMomentProcessorConfig processor_config; 124 Core::IrSensor::PackedMomentProcessorConfig processor_config;
102 }; 125 };
103 static_assert(sizeof(Parameters) == 0x30, "Parameters has incorrect size."); 126 static_assert(sizeof(Parameters) == 0x30, "Parameters has incorrect size.");
104 127
@@ -109,19 +132,28 @@ void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) {
109 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, 132 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
110 parameters.applet_resource_user_id); 133 parameters.applet_resource_user_id);
111 134
135 const auto result = IsIrCameraHandleValid(parameters.camera_handle);
136
137 if (result.IsSuccess()) {
138 auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle);
139 MakeProcessor<MomentProcessor>(parameters.camera_handle, device);
140 auto& image_transfer_processor = GetProcessor<MomentProcessor>(parameters.camera_handle);
141 image_transfer_processor.SetConfig(parameters.processor_config);
142 }
143
112 IPC::ResponseBuilder rb{ctx, 2}; 144 IPC::ResponseBuilder rb{ctx, 2};
113 rb.Push(ResultSuccess); 145 rb.Push(result);
114} 146}
115 147
116void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) { 148void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) {
117 IPC::RequestParser rp{ctx}; 149 IPC::RequestParser rp{ctx};
118 struct Parameters { 150 struct Parameters {
119 IrCameraHandle camera_handle; 151 Core::IrSensor::IrCameraHandle camera_handle;
120 INSERT_PADDING_WORDS_NOINIT(1); 152 INSERT_PADDING_WORDS_NOINIT(1);
121 u64 applet_resource_user_id; 153 u64 applet_resource_user_id;
122 PackedClusteringProcessorConfig processor_config; 154 Core::IrSensor::PackedClusteringProcessorConfig processor_config;
123 }; 155 };
124 static_assert(sizeof(Parameters) == 0x40, "Parameters has incorrect size."); 156 static_assert(sizeof(Parameters) == 0x38, "Parameters has incorrect size.");
125 157
126 const auto parameters{rp.PopRaw<Parameters>()}; 158 const auto parameters{rp.PopRaw<Parameters>()};
127 159
@@ -130,17 +162,27 @@ void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) {
130 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, 162 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
131 parameters.applet_resource_user_id); 163 parameters.applet_resource_user_id);
132 164
165 auto result = IsIrCameraHandleValid(parameters.camera_handle);
166
167 if (result.IsSuccess()) {
168 auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle);
169 MakeProcessor<ClusteringProcessor>(parameters.camera_handle, device);
170 auto& image_transfer_processor =
171 GetProcessor<ClusteringProcessor>(parameters.camera_handle);
172 image_transfer_processor.SetConfig(parameters.processor_config);
173 }
174
133 IPC::ResponseBuilder rb{ctx, 2}; 175 IPC::ResponseBuilder rb{ctx, 2};
134 rb.Push(ResultSuccess); 176 rb.Push(result);
135} 177}
136 178
137void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) { 179void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) {
138 IPC::RequestParser rp{ctx}; 180 IPC::RequestParser rp{ctx};
139 struct Parameters { 181 struct Parameters {
140 IrCameraHandle camera_handle; 182 Core::IrSensor::IrCameraHandle camera_handle;
141 INSERT_PADDING_WORDS_NOINIT(1); 183 INSERT_PADDING_WORDS_NOINIT(1);
142 u64 applet_resource_user_id; 184 u64 applet_resource_user_id;
143 PackedImageTransferProcessorConfig processor_config; 185 Core::IrSensor::PackedImageTransferProcessorConfig processor_config;
144 u32 transfer_memory_size; 186 u32 transfer_memory_size;
145 }; 187 };
146 static_assert(sizeof(Parameters) == 0x30, "Parameters has incorrect size."); 188 static_assert(sizeof(Parameters) == 0x30, "Parameters has incorrect size.");
@@ -151,20 +193,42 @@ void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) {
151 auto t_mem = 193 auto t_mem =
152 system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle); 194 system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle);
153 195
154 LOG_WARNING(Service_IRS, 196 if (t_mem.IsNull()) {
155 "(STUBBED) called, npad_type={}, npad_id={}, transfer_memory_size={}, " 197 LOG_ERROR(Service_IRS, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle);
156 "applet_resource_user_id={}", 198 IPC::ResponseBuilder rb{ctx, 2};
157 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, 199 rb.Push(ResultUnknown);
158 parameters.transfer_memory_size, parameters.applet_resource_user_id); 200 return;
201 }
202
203 ASSERT_MSG(t_mem->GetSize() == parameters.transfer_memory_size, "t_mem has incorrect size");
204
205 u8* transfer_memory = system.Memory().GetPointer(t_mem->GetSourceAddress());
206
207 LOG_INFO(Service_IRS,
208 "called, npad_type={}, npad_id={}, transfer_memory_size={}, transfer_memory_size={}, "
209 "applet_resource_user_id={}",
210 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
211 parameters.transfer_memory_size, t_mem->GetSize(), parameters.applet_resource_user_id);
212
213 const auto result = IsIrCameraHandleValid(parameters.camera_handle);
214
215 if (result.IsSuccess()) {
216 auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle);
217 MakeProcessorWithCoreContext<ImageTransferProcessor>(parameters.camera_handle, device);
218 auto& image_transfer_processor =
219 GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
220 image_transfer_processor.SetConfig(parameters.processor_config);
221 image_transfer_processor.SetTransferMemoryPointer(transfer_memory);
222 }
159 223
160 IPC::ResponseBuilder rb{ctx, 2}; 224 IPC::ResponseBuilder rb{ctx, 2};
161 rb.Push(ResultSuccess); 225 rb.Push(result);
162} 226}
163 227
164void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) { 228void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) {
165 IPC::RequestParser rp{ctx}; 229 IPC::RequestParser rp{ctx};
166 struct Parameters { 230 struct Parameters {
167 IrCameraHandle camera_handle; 231 Core::IrSensor::IrCameraHandle camera_handle;
168 INSERT_PADDING_WORDS_NOINIT(1); 232 INSERT_PADDING_WORDS_NOINIT(1);
169 u64 applet_resource_user_id; 233 u64 applet_resource_user_id;
170 }; 234 };
@@ -172,32 +236,68 @@ void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) {
172 236
173 const auto parameters{rp.PopRaw<Parameters>()}; 237 const auto parameters{rp.PopRaw<Parameters>()};
174 238
175 LOG_WARNING(Service_IRS, 239 LOG_DEBUG(Service_IRS, "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}",
176 "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}", 240 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
177 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, 241 parameters.applet_resource_user_id);
178 parameters.applet_resource_user_id); 242
243 const auto result = IsIrCameraHandleValid(parameters.camera_handle);
244 if (result.IsError()) {
245 IPC::ResponseBuilder rb{ctx, 2};
246 rb.Push(result);
247 return;
248 }
249
250 const auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle);
251
252 if (device.mode != Core::IrSensor::IrSensorMode::ImageTransferProcessor) {
253 IPC::ResponseBuilder rb{ctx, 2};
254 rb.Push(InvalidProcessorState);
255 return;
256 }
179 257
180 IPC::ResponseBuilder rb{ctx, 5}; 258 std::vector<u8> data{};
259 const auto& image_transfer_processor =
260 GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
261 const auto& state = image_transfer_processor.GetState(data);
262
263 ctx.WriteBuffer(data);
264 IPC::ResponseBuilder rb{ctx, 6};
181 rb.Push(ResultSuccess); 265 rb.Push(ResultSuccess);
182 rb.PushRaw<u64>(system.CoreTiming().GetCPUTicks()); 266 rb.PushRaw(state);
183 rb.PushRaw<u32>(0);
184} 267}
185 268
186void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) { 269void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) {
187 IPC::RequestParser rp{ctx}; 270 IPC::RequestParser rp{ctx};
188 const auto camera_handle{rp.PopRaw<IrCameraHandle>()}; 271 struct Parameters {
189 const auto processor_config{rp.PopRaw<PackedTeraPluginProcessorConfig>()}; 272 Core::IrSensor::IrCameraHandle camera_handle;
190 const auto applet_resource_user_id{rp.Pop<u64>()}; 273 Core::IrSensor::PackedTeraPluginProcessorConfig processor_config;
274 INSERT_PADDING_WORDS_NOINIT(1);
275 u64 applet_resource_user_id;
276 };
277 static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
191 278
192 LOG_WARNING(Service_IRS, 279 const auto parameters{rp.PopRaw<Parameters>()};
193 "(STUBBED) called, npad_type={}, npad_id={}, mode={}, mcu_version={}.{}, " 280
194 "applet_resource_user_id={}", 281 LOG_WARNING(
195 camera_handle.npad_type, camera_handle.npad_id, processor_config.mode, 282 Service_IRS,
196 processor_config.required_mcu_version.major, 283 "(STUBBED) called, npad_type={}, npad_id={}, mode={}, mcu_version={}.{}, "
197 processor_config.required_mcu_version.minor, applet_resource_user_id); 284 "applet_resource_user_id={}",
285 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
286 parameters.processor_config.mode, parameters.processor_config.required_mcu_version.major,
287 parameters.processor_config.required_mcu_version.minor, parameters.applet_resource_user_id);
288
289 const auto result = IsIrCameraHandleValid(parameters.camera_handle);
290
291 if (result.IsSuccess()) {
292 auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle);
293 MakeProcessor<TeraPluginProcessor>(parameters.camera_handle, device);
294 auto& image_transfer_processor =
295 GetProcessor<TeraPluginProcessor>(parameters.camera_handle);
296 image_transfer_processor.SetConfig(parameters.processor_config);
297 }
198 298
199 IPC::ResponseBuilder rb{ctx, 2}; 299 IPC::ResponseBuilder rb{ctx, 2};
200 rb.Push(ResultSuccess); 300 rb.Push(result);
201} 301}
202 302
203void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) { 303void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) {
@@ -207,17 +307,17 @@ void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) {
207 if (npad_id > Core::HID::NpadIdType::Player8 && npad_id != Core::HID::NpadIdType::Invalid && 307 if (npad_id > Core::HID::NpadIdType::Player8 && npad_id != Core::HID::NpadIdType::Invalid &&
208 npad_id != Core::HID::NpadIdType::Handheld) { 308 npad_id != Core::HID::NpadIdType::Handheld) {
209 IPC::ResponseBuilder rb{ctx, 2}; 309 IPC::ResponseBuilder rb{ctx, 2};
210 rb.Push(InvalidNpadId); 310 rb.Push(Service::HID::InvalidNpadId);
211 return; 311 return;
212 } 312 }
213 313
214 IrCameraHandle camera_handle{ 314 Core::IrSensor::IrCameraHandle camera_handle{
215 .npad_id = static_cast<u8>(NpadIdTypeToIndex(npad_id)), 315 .npad_id = static_cast<u8>(NpadIdTypeToIndex(npad_id)),
216 .npad_type = Core::HID::NpadStyleIndex::None, 316 .npad_type = Core::HID::NpadStyleIndex::None,
217 }; 317 };
218 318
219 LOG_WARNING(Service_IRS, "(STUBBED) called, npad_id={}, camera_npad_id={}, camera_npad_type={}", 319 LOG_INFO(Service_IRS, "called, npad_id={}, camera_npad_id={}, camera_npad_type={}", npad_id,
220 npad_id, camera_handle.npad_id, camera_handle.npad_type); 320 camera_handle.npad_id, camera_handle.npad_type);
221 321
222 IPC::ResponseBuilder rb{ctx, 3}; 322 IPC::ResponseBuilder rb{ctx, 3};
223 rb.Push(ResultSuccess); 323 rb.Push(ResultSuccess);
@@ -226,8 +326,8 @@ void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) {
226 326
227void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) { 327void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) {
228 IPC::RequestParser rp{ctx}; 328 IPC::RequestParser rp{ctx};
229 const auto camera_handle{rp.PopRaw<IrCameraHandle>()}; 329 const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()};
230 const auto processor_config{rp.PopRaw<PackedPointingProcessorConfig>()}; 330 const auto processor_config{rp.PopRaw<Core::IrSensor::PackedPointingProcessorConfig>()};
231 const auto applet_resource_user_id{rp.Pop<u64>()}; 331 const auto applet_resource_user_id{rp.Pop<u64>()};
232 332
233 LOG_WARNING( 333 LOG_WARNING(
@@ -236,14 +336,23 @@ void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) {
236 camera_handle.npad_type, camera_handle.npad_id, processor_config.required_mcu_version.major, 336 camera_handle.npad_type, camera_handle.npad_id, processor_config.required_mcu_version.major,
237 processor_config.required_mcu_version.minor, applet_resource_user_id); 337 processor_config.required_mcu_version.minor, applet_resource_user_id);
238 338
339 auto result = IsIrCameraHandleValid(camera_handle);
340
341 if (result.IsSuccess()) {
342 auto& device = GetIrCameraSharedMemoryDeviceEntry(camera_handle);
343 MakeProcessor<PointingProcessor>(camera_handle, device);
344 auto& image_transfer_processor = GetProcessor<PointingProcessor>(camera_handle);
345 image_transfer_processor.SetConfig(processor_config);
346 }
347
239 IPC::ResponseBuilder rb{ctx, 2}; 348 IPC::ResponseBuilder rb{ctx, 2};
240 rb.Push(ResultSuccess); 349 rb.Push(result);
241} 350}
242 351
243void IRS::SuspendImageProcessor(Kernel::HLERequestContext& ctx) { 352void IRS::SuspendImageProcessor(Kernel::HLERequestContext& ctx) {
244 IPC::RequestParser rp{ctx}; 353 IPC::RequestParser rp{ctx};
245 struct Parameters { 354 struct Parameters {
246 IrCameraHandle camera_handle; 355 Core::IrSensor::IrCameraHandle camera_handle;
247 INSERT_PADDING_WORDS_NOINIT(1); 356 INSERT_PADDING_WORDS_NOINIT(1);
248 u64 applet_resource_user_id; 357 u64 applet_resource_user_id;
249 }; 358 };
@@ -256,14 +365,20 @@ void IRS::SuspendImageProcessor(Kernel::HLERequestContext& ctx) {
256 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, 365 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
257 parameters.applet_resource_user_id); 366 parameters.applet_resource_user_id);
258 367
368 auto result = IsIrCameraHandleValid(parameters.camera_handle);
369 if (result.IsSuccess()) {
370 // TODO: Suspend image processor
371 result = ResultSuccess;
372 }
373
259 IPC::ResponseBuilder rb{ctx, 2}; 374 IPC::ResponseBuilder rb{ctx, 2};
260 rb.Push(ResultSuccess); 375 rb.Push(result);
261} 376}
262 377
263void IRS::CheckFirmwareVersion(Kernel::HLERequestContext& ctx) { 378void IRS::CheckFirmwareVersion(Kernel::HLERequestContext& ctx) {
264 IPC::RequestParser rp{ctx}; 379 IPC::RequestParser rp{ctx};
265 const auto camera_handle{rp.PopRaw<IrCameraHandle>()}; 380 const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()};
266 const auto mcu_version{rp.PopRaw<PackedMcuVersion>()}; 381 const auto mcu_version{rp.PopRaw<Core::IrSensor::PackedMcuVersion>()};
267 const auto applet_resource_user_id{rp.Pop<u64>()}; 382 const auto applet_resource_user_id{rp.Pop<u64>()};
268 383
269 LOG_WARNING( 384 LOG_WARNING(
@@ -272,37 +387,45 @@ void IRS::CheckFirmwareVersion(Kernel::HLERequestContext& ctx) {
272 camera_handle.npad_type, camera_handle.npad_id, applet_resource_user_id, mcu_version.major, 387 camera_handle.npad_type, camera_handle.npad_id, applet_resource_user_id, mcu_version.major,
273 mcu_version.minor); 388 mcu_version.minor);
274 389
390 auto result = IsIrCameraHandleValid(camera_handle);
391 if (result.IsSuccess()) {
392 // TODO: Check firmware version
393 result = ResultSuccess;
394 }
395
275 IPC::ResponseBuilder rb{ctx, 2}; 396 IPC::ResponseBuilder rb{ctx, 2};
276 rb.Push(ResultSuccess); 397 rb.Push(result);
277} 398}
278 399
279void IRS::SetFunctionLevel(Kernel::HLERequestContext& ctx) { 400void IRS::SetFunctionLevel(Kernel::HLERequestContext& ctx) {
280 IPC::RequestParser rp{ctx}; 401 IPC::RequestParser rp{ctx};
281 struct Parameters { 402 const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()};
282 IrCameraHandle camera_handle; 403 const auto function_level{rp.PopRaw<Core::IrSensor::PackedFunctionLevel>()};
283 PackedFunctionLevel function_level; 404 const auto applet_resource_user_id{rp.Pop<u64>()};
284 u64 applet_resource_user_id;
285 };
286 static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
287
288 const auto parameters{rp.PopRaw<Parameters>()};
289 405
290 LOG_WARNING(Service_IRS, 406 LOG_WARNING(
291 "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}", 407 Service_IRS,
292 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, 408 "(STUBBED) called, npad_type={}, npad_id={}, function_level={}, applet_resource_user_id={}",
293 parameters.applet_resource_user_id); 409 camera_handle.npad_type, camera_handle.npad_id, function_level.function_level,
410 applet_resource_user_id);
411
412 auto result = IsIrCameraHandleValid(camera_handle);
413 if (result.IsSuccess()) {
414 // TODO: Set Function level
415 result = ResultSuccess;
416 }
294 417
295 IPC::ResponseBuilder rb{ctx, 2}; 418 IPC::ResponseBuilder rb{ctx, 2};
296 rb.Push(ResultSuccess); 419 rb.Push(result);
297} 420}
298 421
299void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) { 422void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) {
300 IPC::RequestParser rp{ctx}; 423 IPC::RequestParser rp{ctx};
301 struct Parameters { 424 struct Parameters {
302 IrCameraHandle camera_handle; 425 Core::IrSensor::IrCameraHandle camera_handle;
303 INSERT_PADDING_WORDS_NOINIT(1); 426 INSERT_PADDING_WORDS_NOINIT(1);
304 u64 applet_resource_user_id; 427 u64 applet_resource_user_id;
305 PackedImageTransferProcessorExConfig processor_config; 428 Core::IrSensor::PackedImageTransferProcessorExConfig processor_config;
306 u64 transfer_memory_size; 429 u64 transfer_memory_size;
307 }; 430 };
308 static_assert(sizeof(Parameters) == 0x38, "Parameters has incorrect size."); 431 static_assert(sizeof(Parameters) == 0x38, "Parameters has incorrect size.");
@@ -313,20 +436,33 @@ void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) {
313 auto t_mem = 436 auto t_mem =
314 system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle); 437 system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle);
315 438
316 LOG_WARNING(Service_IRS, 439 u8* transfer_memory = system.Memory().GetPointer(t_mem->GetSourceAddress());
317 "(STUBBED) called, npad_type={}, npad_id={}, transfer_memory_size={}, " 440
318 "applet_resource_user_id={}", 441 LOG_INFO(Service_IRS,
319 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, 442 "called, npad_type={}, npad_id={}, transfer_memory_size={}, "
320 parameters.transfer_memory_size, parameters.applet_resource_user_id); 443 "applet_resource_user_id={}",
444 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
445 parameters.transfer_memory_size, parameters.applet_resource_user_id);
446
447 auto result = IsIrCameraHandleValid(parameters.camera_handle);
448
449 if (result.IsSuccess()) {
450 auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle);
451 MakeProcessorWithCoreContext<ImageTransferProcessor>(parameters.camera_handle, device);
452 auto& image_transfer_processor =
453 GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
454 image_transfer_processor.SetConfig(parameters.processor_config);
455 image_transfer_processor.SetTransferMemoryPointer(transfer_memory);
456 }
321 457
322 IPC::ResponseBuilder rb{ctx, 2}; 458 IPC::ResponseBuilder rb{ctx, 2};
323 rb.Push(ResultSuccess); 459 rb.Push(result);
324} 460}
325 461
326void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) { 462void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) {
327 IPC::RequestParser rp{ctx}; 463 IPC::RequestParser rp{ctx};
328 const auto camera_handle{rp.PopRaw<IrCameraHandle>()}; 464 const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()};
329 const auto processor_config{rp.PopRaw<PackedIrLedProcessorConfig>()}; 465 const auto processor_config{rp.PopRaw<Core::IrSensor::PackedIrLedProcessorConfig>()};
330 const auto applet_resource_user_id{rp.Pop<u64>()}; 466 const auto applet_resource_user_id{rp.Pop<u64>()};
331 467
332 LOG_WARNING(Service_IRS, 468 LOG_WARNING(Service_IRS,
@@ -336,14 +472,23 @@ void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) {
336 processor_config.required_mcu_version.major, 472 processor_config.required_mcu_version.major,
337 processor_config.required_mcu_version.minor, applet_resource_user_id); 473 processor_config.required_mcu_version.minor, applet_resource_user_id);
338 474
475 auto result = IsIrCameraHandleValid(camera_handle);
476
477 if (result.IsSuccess()) {
478 auto& device = GetIrCameraSharedMemoryDeviceEntry(camera_handle);
479 MakeProcessor<IrLedProcessor>(camera_handle, device);
480 auto& image_transfer_processor = GetProcessor<IrLedProcessor>(camera_handle);
481 image_transfer_processor.SetConfig(processor_config);
482 }
483
339 IPC::ResponseBuilder rb{ctx, 2}; 484 IPC::ResponseBuilder rb{ctx, 2};
340 rb.Push(ResultSuccess); 485 rb.Push(result);
341} 486}
342 487
343void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) { 488void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) {
344 IPC::RequestParser rp{ctx}; 489 IPC::RequestParser rp{ctx};
345 struct Parameters { 490 struct Parameters {
346 IrCameraHandle camera_handle; 491 Core::IrSensor::IrCameraHandle camera_handle;
347 INSERT_PADDING_WORDS_NOINIT(1); 492 INSERT_PADDING_WORDS_NOINIT(1);
348 u64 applet_resource_user_id; 493 u64 applet_resource_user_id;
349 }; 494 };
@@ -356,14 +501,20 @@ void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) {
356 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id, 501 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
357 parameters.applet_resource_user_id); 502 parameters.applet_resource_user_id);
358 503
504 auto result = IsIrCameraHandleValid(parameters.camera_handle);
505 if (result.IsSuccess()) {
506 // TODO: Stop image processor async
507 result = ResultSuccess;
508 }
509
359 IPC::ResponseBuilder rb{ctx, 2}; 510 IPC::ResponseBuilder rb{ctx, 2};
360 rb.Push(ResultSuccess); 511 rb.Push(result);
361} 512}
362 513
363void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) { 514void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) {
364 IPC::RequestParser rp{ctx}; 515 IPC::RequestParser rp{ctx};
365 struct Parameters { 516 struct Parameters {
366 PackedFunctionLevel function_level; 517 Core::IrSensor::PackedFunctionLevel function_level;
367 INSERT_PADDING_WORDS_NOINIT(1); 518 INSERT_PADDING_WORDS_NOINIT(1);
368 u64 applet_resource_user_id; 519 u64 applet_resource_user_id;
369 }; 520 };
@@ -378,7 +529,22 @@ void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) {
378 rb.Push(ResultSuccess); 529 rb.Push(ResultSuccess);
379} 530}
380 531
381IRS::~IRS() = default; 532Result IRS::IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_handle) const {
533 if (camera_handle.npad_id >
534 static_cast<u8>(NpadIdTypeToIndex(Core::HID::NpadIdType::Handheld))) {
535 return InvalidIrCameraHandle;
536 }
537 if (camera_handle.npad_type != Core::HID::NpadStyleIndex::None) {
538 return InvalidIrCameraHandle;
539 }
540 return ResultSuccess;
541}
542
543Core::IrSensor::DeviceFormat& IRS::GetIrCameraSharedMemoryDeviceEntry(
544 const Core::IrSensor::IrCameraHandle& camera_handle) {
545 ASSERT_MSG(sizeof(StatusManager::device) > camera_handle.npad_id, "invalid npad_id");
546 return shared_memory->device[camera_handle.npad_id];
547}
382 548
383IRS_SYS::IRS_SYS(Core::System& system_) : ServiceFramework{system_, "irs:sys"} { 549IRS_SYS::IRS_SYS(Core::System& system_) : ServiceFramework{system_, "irs:sys"} {
384 // clang-format off 550 // clang-format off
@@ -395,4 +561,4 @@ IRS_SYS::IRS_SYS(Core::System& system_) : ServiceFramework{system_, "irs:sys"} {
395 561
396IRS_SYS::~IRS_SYS() = default; 562IRS_SYS::~IRS_SYS() = default;
397 563
398} // namespace Service::HID 564} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irs.h b/src/core/hle/service/hid/irs.h
index 361dc2213..2e6115c73 100644
--- a/src/core/hle/service/hid/irs.h
+++ b/src/core/hle/service/hid/irs.h
@@ -4,13 +4,19 @@
4#pragma once 4#pragma once
5 5
6#include "core/hid/hid_types.h" 6#include "core/hid/hid_types.h"
7#include "core/hid/irs_types.h"
8#include "core/hle/service/hid/irsensor/processor_base.h"
7#include "core/hle/service/service.h" 9#include "core/hle/service/service.h"
8 10
9namespace Core { 11namespace Core {
10class System; 12class System;
11} 13}
12 14
13namespace Service::HID { 15namespace Core::HID {
16class EmulatedController;
17} // namespace Core::HID
18
19namespace Service::IRS {
14 20
15class IRS final : public ServiceFramework<IRS> { 21class IRS final : public ServiceFramework<IRS> {
16public: 22public:
@@ -18,234 +24,19 @@ public:
18 ~IRS() override; 24 ~IRS() override;
19 25
20private: 26private:
21 // This is nn::irsensor::IrCameraStatus 27 // This is nn::irsensor::detail::AruidFormat
22 enum IrCameraStatus : u32 { 28 struct AruidFormat {
23 Available, 29 u64 sensor_aruid;
24 Unsupported, 30 u64 sensor_aruid_status;
25 Unconnected,
26 };
27
28 // This is nn::irsensor::IrCameraInternalStatus
29 enum IrCameraInternalStatus : u32 {
30 Stopped,
31 FirmwareUpdateNeeded,
32 Unkown2,
33 Unkown3,
34 Unkown4,
35 FirmwareVersionRequested,
36 FirmwareVersionIsInvalid,
37 Ready,
38 Setting,
39 };
40
41 // This is nn::irsensor::detail::StatusManager::IrSensorMode
42 enum IrSensorMode : u64 {
43 None,
44 MomentProcessor,
45 ClusteringProcessor,
46 ImageTransferProcessor,
47 PointingProcessorMarker,
48 TeraPluginProcessor,
49 IrLedProcessor,
50 };
51
52 // This is nn::irsensor::ImageProcessorStatus
53 enum ImageProcessorStatus : u8 {
54 stopped,
55 running,
56 };
57
58 // This is nn::irsensor::ImageTransferProcessorFormat
59 enum ImageTransferProcessorFormat : u8 {
60 Size320x240,
61 Size160x120,
62 Size80x60,
63 Size40x30,
64 Size20x15,
65 };
66
67 // This is nn::irsensor::AdaptiveClusteringMode
68 enum AdaptiveClusteringMode : u8 {
69 StaticFov,
70 DynamicFov,
71 };
72
73 // This is nn::irsensor::AdaptiveClusteringTargetDistance
74 enum AdaptiveClusteringTargetDistance : u8 {
75 Near,
76 Middle,
77 Far,
78 };
79
80 // This is nn::irsensor::IrsHandAnalysisMode
81 enum IrsHandAnalysisMode : u8 {
82 Silhouette,
83 Image,
84 SilhoueteAndImage,
85 SilhuetteOnly,
86 };
87
88 // This is nn::irsensor::IrSensorFunctionLevel
89 enum IrSensorFunctionLevel : u8 {
90 unknown0,
91 unknown1,
92 unknown2,
93 unknown3,
94 unknown4,
95 };
96
97 // This is nn::irsensor::IrCameraHandle
98 struct IrCameraHandle {
99 u8 npad_id{};
100 Core::HID::NpadStyleIndex npad_type{Core::HID::NpadStyleIndex::None};
101 INSERT_PADDING_BYTES(2);
102 };
103 static_assert(sizeof(IrCameraHandle) == 4, "IrCameraHandle is an invalid size");
104
105 struct IrsRect {
106 s16 x;
107 s16 y;
108 s16 width;
109 s16 height;
110 }; 31 };
32 static_assert(sizeof(AruidFormat) == 0x10, "AruidFormat is an invalid size");
111 33
112 // This is nn::irsensor::PackedMcuVersion 34 // This is nn::irsensor::detail::StatusManager
113 struct PackedMcuVersion { 35 struct StatusManager {
114 u16 major; 36 std::array<Core::IrSensor::DeviceFormat, 9> device;
115 u16 minor; 37 std::array<AruidFormat, 5> aruid;
116 }; 38 };
117 static_assert(sizeof(PackedMcuVersion) == 4, "PackedMcuVersion is an invalid size"); 39 static_assert(sizeof(StatusManager) == 0x8000, "StatusManager is an invalid size");
118
119 // This is nn::irsensor::MomentProcessorConfig
120 struct MomentProcessorConfig {
121 u64 exposire_time;
122 u8 light_target;
123 u8 gain;
124 u8 is_negative_used;
125 INSERT_PADDING_BYTES(7);
126 IrsRect window_of_interest;
127 u8 preprocess;
128 u8 preprocess_intensity_threshold;
129 INSERT_PADDING_BYTES(5);
130 };
131 static_assert(sizeof(MomentProcessorConfig) == 0x28,
132 "MomentProcessorConfig is an invalid size");
133
134 // This is nn::irsensor::PackedMomentProcessorConfig
135 struct PackedMomentProcessorConfig {
136 u64 exposire_time;
137 u8 light_target;
138 u8 gain;
139 u8 is_negative_used;
140 INSERT_PADDING_BYTES(5);
141 IrsRect window_of_interest;
142 PackedMcuVersion required_mcu_version;
143 u8 preprocess;
144 u8 preprocess_intensity_threshold;
145 INSERT_PADDING_BYTES(2);
146 };
147 static_assert(sizeof(PackedMomentProcessorConfig) == 0x20,
148 "PackedMomentProcessorConfig is an invalid size");
149
150 // This is nn::irsensor::ClusteringProcessorConfig
151 struct ClusteringProcessorConfig {
152 u64 exposire_time;
153 u32 light_target;
154 u32 gain;
155 u8 is_negative_used;
156 INSERT_PADDING_BYTES(7);
157 IrsRect window_of_interest;
158 u32 pixel_count_min;
159 u32 pixel_count_max;
160 u32 object_intensity_min;
161 u8 is_external_light_filter_enabled;
162 INSERT_PADDING_BYTES(3);
163 };
164 static_assert(sizeof(ClusteringProcessorConfig) == 0x30,
165 "ClusteringProcessorConfig is an invalid size");
166
167 // This is nn::irsensor::PackedClusteringProcessorConfig
168 struct PackedClusteringProcessorConfig {
169 u64 exposire_time;
170 u8 light_target;
171 u8 gain;
172 u8 is_negative_used;
173 INSERT_PADDING_BYTES(5);
174 IrsRect window_of_interest;
175 PackedMcuVersion required_mcu_version;
176 u32 pixel_count_min;
177 u32 pixel_count_max;
178 u32 object_intensity_min;
179 u8 is_external_light_filter_enabled;
180 INSERT_PADDING_BYTES(2);
181 };
182 static_assert(sizeof(PackedClusteringProcessorConfig) == 0x30,
183 "PackedClusteringProcessorConfig is an invalid size");
184
185 // This is nn::irsensor::PackedImageTransferProcessorConfig
186 struct PackedImageTransferProcessorConfig {
187 u64 exposire_time;
188 u8 light_target;
189 u8 gain;
190 u8 is_negative_used;
191 INSERT_PADDING_BYTES(5);
192 PackedMcuVersion required_mcu_version;
193 u8 format;
194 INSERT_PADDING_BYTES(3);
195 };
196 static_assert(sizeof(PackedImageTransferProcessorConfig) == 0x18,
197 "PackedImageTransferProcessorConfig is an invalid size");
198
199 // This is nn::irsensor::PackedTeraPluginProcessorConfig
200 struct PackedTeraPluginProcessorConfig {
201 PackedMcuVersion required_mcu_version;
202 u8 mode;
203 INSERT_PADDING_BYTES(3);
204 };
205 static_assert(sizeof(PackedTeraPluginProcessorConfig) == 0x8,
206 "PackedTeraPluginProcessorConfig is an invalid size");
207
208 // This is nn::irsensor::PackedPointingProcessorConfig
209 struct PackedPointingProcessorConfig {
210 IrsRect window_of_interest;
211 PackedMcuVersion required_mcu_version;
212 };
213 static_assert(sizeof(PackedPointingProcessorConfig) == 0xC,
214 "PackedPointingProcessorConfig is an invalid size");
215
216 // This is nn::irsensor::PackedFunctionLevel
217 struct PackedFunctionLevel {
218 IrSensorFunctionLevel function_level;
219 INSERT_PADDING_BYTES(3);
220 };
221 static_assert(sizeof(PackedFunctionLevel) == 0x4, "PackedFunctionLevel is an invalid size");
222
223 // This is nn::irsensor::PackedImageTransferProcessorExConfig
224 struct PackedImageTransferProcessorExConfig {
225 u64 exposire_time;
226 u8 light_target;
227 u8 gain;
228 u8 is_negative_used;
229 INSERT_PADDING_BYTES(5);
230 PackedMcuVersion required_mcu_version;
231 ImageTransferProcessorFormat origin_format;
232 ImageTransferProcessorFormat trimming_format;
233 u16 trimming_start_x;
234 u16 trimming_start_y;
235 u8 is_external_light_filter_enabled;
236 INSERT_PADDING_BYTES(3);
237 };
238 static_assert(sizeof(PackedImageTransferProcessorExConfig) == 0x20,
239 "PackedImageTransferProcessorExConfig is an invalid size");
240
241 // This is nn::irsensor::PackedIrLedProcessorConfig
242 struct PackedIrLedProcessorConfig {
243 PackedMcuVersion required_mcu_version;
244 u8 light_target;
245 INSERT_PADDING_BYTES(3);
246 };
247 static_assert(sizeof(PackedIrLedProcessorConfig) == 0x8,
248 "PackedIrLedProcessorConfig is an invalid size");
249 40
250 void ActivateIrsensor(Kernel::HLERequestContext& ctx); 41 void ActivateIrsensor(Kernel::HLERequestContext& ctx);
251 void DeactivateIrsensor(Kernel::HLERequestContext& ctx); 42 void DeactivateIrsensor(Kernel::HLERequestContext& ctx);
@@ -265,6 +56,56 @@ private:
265 void RunIrLedProcessor(Kernel::HLERequestContext& ctx); 56 void RunIrLedProcessor(Kernel::HLERequestContext& ctx);
266 void StopImageProcessorAsync(Kernel::HLERequestContext& ctx); 57 void StopImageProcessorAsync(Kernel::HLERequestContext& ctx);
267 void ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx); 58 void ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx);
59
60 Result IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_handle) const;
61 Core::IrSensor::DeviceFormat& GetIrCameraSharedMemoryDeviceEntry(
62 const Core::IrSensor::IrCameraHandle& camera_handle);
63
64 template <typename T>
65 void MakeProcessor(const Core::IrSensor::IrCameraHandle& handle,
66 Core::IrSensor::DeviceFormat& device_state) {
67 const auto index = static_cast<std::size_t>(handle.npad_id);
68 if (index > sizeof(processors)) {
69 LOG_CRITICAL(Service_IRS, "Invalid index {}", index);
70 return;
71 }
72 processors[index] = std::make_unique<T>(device_state);
73 }
74
75 template <typename T>
76 void MakeProcessorWithCoreContext(const Core::IrSensor::IrCameraHandle& handle,
77 Core::IrSensor::DeviceFormat& device_state) {
78 const auto index = static_cast<std::size_t>(handle.npad_id);
79 if (index > sizeof(processors)) {
80 LOG_CRITICAL(Service_IRS, "Invalid index {}", index);
81 return;
82 }
83 processors[index] = std::make_unique<T>(system.HIDCore(), device_state, index);
84 }
85
86 template <typename T>
87 T& GetProcessor(const Core::IrSensor::IrCameraHandle& handle) {
88 const auto index = static_cast<std::size_t>(handle.npad_id);
89 if (index > sizeof(processors)) {
90 LOG_CRITICAL(Service_IRS, "Invalid index {}", index);
91 return static_cast<T&>(*processors[0]);
92 }
93 return static_cast<T&>(*processors[index]);
94 }
95
96 template <typename T>
97 const T& GetProcessor(const Core::IrSensor::IrCameraHandle& handle) const {
98 const auto index = static_cast<std::size_t>(handle.npad_id);
99 if (index > sizeof(processors)) {
100 LOG_CRITICAL(Service_IRS, "Invalid index {}", index);
101 return static_cast<T&>(*processors[0]);
102 }
103 return static_cast<T&>(*processors[index]);
104 }
105
106 Core::HID::EmulatedController* npad_device = nullptr;
107 StatusManager* shared_memory = nullptr;
108 std::array<std::unique_ptr<ProcessorBase>, 9> processors{};
268}; 109};
269 110
270class IRS_SYS final : public ServiceFramework<IRS_SYS> { 111class IRS_SYS final : public ServiceFramework<IRS_SYS> {
@@ -273,4 +114,4 @@ public:
273 ~IRS_SYS() override; 114 ~IRS_SYS() override;
274}; 115};
275 116
276} // namespace Service::HID 117} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/clustering_processor.cpp b/src/core/hle/service/hid/irsensor/clustering_processor.cpp
new file mode 100644
index 000000000..6479af212
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/clustering_processor.cpp
@@ -0,0 +1,34 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include "core/hle/service/hid/irsensor/clustering_processor.h"
5
6namespace Service::IRS {
7ClusteringProcessor::ClusteringProcessor(Core::IrSensor::DeviceFormat& device_format)
8 : device(device_format) {
9 device.mode = Core::IrSensor::IrSensorMode::ClusteringProcessor;
10 device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
11 device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
12}
13
14ClusteringProcessor::~ClusteringProcessor() = default;
15
16void ClusteringProcessor::StartProcessor() {}
17
18void ClusteringProcessor::SuspendProcessor() {}
19
20void ClusteringProcessor::StopProcessor() {}
21
22void ClusteringProcessor::SetConfig(Core::IrSensor::PackedClusteringProcessorConfig config) {
23 current_config.camera_config.exposure_time = config.camera_config.exposure_time;
24 current_config.camera_config.gain = config.camera_config.gain;
25 current_config.camera_config.is_negative_used = config.camera_config.is_negative_used;
26 current_config.camera_config.light_target =
27 static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target);
28 current_config.pixel_count_min = config.pixel_count_min;
29 current_config.pixel_count_max = config.pixel_count_max;
30 current_config.is_external_light_filter_enabled = config.is_external_light_filter_enabled;
31 current_config.object_intensity_min = config.object_intensity_min;
32}
33
34} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/clustering_processor.h b/src/core/hle/service/hid/irsensor/clustering_processor.h
new file mode 100644
index 000000000..6e2ba8846
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/clustering_processor.h
@@ -0,0 +1,74 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "common/common_types.h"
7#include "core/hid/irs_types.h"
8#include "core/hle/service/hid/irsensor/processor_base.h"
9
10namespace Service::IRS {
11class ClusteringProcessor final : public ProcessorBase {
12public:
13 explicit ClusteringProcessor(Core::IrSensor::DeviceFormat& device_format);
14 ~ClusteringProcessor() override;
15
16 // Called when the processor is initialized
17 void StartProcessor() override;
18
19 // Called when the processor is suspended
20 void SuspendProcessor() override;
21
22 // Called when the processor is stopped
23 void StopProcessor() override;
24
25 // Sets config parameters of the camera
26 void SetConfig(Core::IrSensor::PackedClusteringProcessorConfig config);
27
28private:
29 // This is nn::irsensor::ClusteringProcessorConfig
30 struct ClusteringProcessorConfig {
31 Core::IrSensor::CameraConfig camera_config;
32 Core::IrSensor::IrsRect window_of_interest;
33 u32 pixel_count_min;
34 u32 pixel_count_max;
35 u32 object_intensity_min;
36 bool is_external_light_filter_enabled;
37 INSERT_PADDING_BYTES(3);
38 };
39 static_assert(sizeof(ClusteringProcessorConfig) == 0x30,
40 "ClusteringProcessorConfig is an invalid size");
41
42 // This is nn::irsensor::AdaptiveClusteringProcessorConfig
43 struct AdaptiveClusteringProcessorConfig {
44 Core::IrSensor::AdaptiveClusteringMode mode;
45 Core::IrSensor::AdaptiveClusteringTargetDistance target_distance;
46 };
47 static_assert(sizeof(AdaptiveClusteringProcessorConfig) == 0x8,
48 "AdaptiveClusteringProcessorConfig is an invalid size");
49
50 // This is nn::irsensor::ClusteringData
51 struct ClusteringData {
52 f32 average_intensity;
53 Core::IrSensor::IrsCentroid centroid;
54 u32 pixel_count;
55 Core::IrSensor::IrsRect bound;
56 };
57 static_assert(sizeof(ClusteringData) == 0x18, "ClusteringData is an invalid size");
58
59 // This is nn::irsensor::ClusteringProcessorState
60 struct ClusteringProcessorState {
61 s64 sampling_number;
62 u64 timestamp;
63 u8 object_count;
64 INSERT_PADDING_BYTES(3);
65 Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level;
66 std::array<ClusteringData, 0x10> data;
67 };
68 static_assert(sizeof(ClusteringProcessorState) == 0x198,
69 "ClusteringProcessorState is an invalid size");
70
71 ClusteringProcessorConfig current_config{};
72 Core::IrSensor::DeviceFormat& device;
73};
74} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp b/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp
new file mode 100644
index 000000000..98f0c579d
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp
@@ -0,0 +1,150 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include "core/hid/emulated_controller.h"
5#include "core/hid/hid_core.h"
6#include "core/hle/service/hid/irsensor/image_transfer_processor.h"
7
8namespace Service::IRS {
9ImageTransferProcessor::ImageTransferProcessor(Core::HID::HIDCore& hid_core_,
10 Core::IrSensor::DeviceFormat& device_format,
11 std::size_t npad_index)
12 : device{device_format} {
13 npad_device = hid_core_.GetEmulatedControllerByIndex(npad_index);
14
15 Core::HID::ControllerUpdateCallback engine_callback{
16 .on_change = [this](Core::HID::ControllerTriggerType type) { OnControllerUpdate(type); },
17 .is_npad_service = true,
18 };
19 callback_key = npad_device->SetCallback(engine_callback);
20
21 device.mode = Core::IrSensor::IrSensorMode::ImageTransferProcessor;
22 device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
23 device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
24}
25
26ImageTransferProcessor::~ImageTransferProcessor() {
27 npad_device->DeleteCallback(callback_key);
28};
29
30void ImageTransferProcessor::StartProcessor() {
31 is_active = true;
32 device.camera_status = Core::IrSensor::IrCameraStatus::Available;
33 device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Ready;
34 processor_state.sampling_number = 0;
35 processor_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low;
36}
37
38void ImageTransferProcessor::SuspendProcessor() {}
39
40void ImageTransferProcessor::StopProcessor() {}
41
42void ImageTransferProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType type) {
43 if (type != Core::HID::ControllerTriggerType::IrSensor) {
44 return;
45 }
46 if (!is_transfer_memory_set) {
47 return;
48 }
49
50 const auto camera_data = npad_device->GetCamera();
51
52 // This indicates how much ambient light is precent
53 processor_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low;
54 processor_state.sampling_number = camera_data.sample;
55
56 if (camera_data.format != current_config.origin_format) {
57 LOG_WARNING(Service_IRS, "Wrong Input format {} expected {}", camera_data.format,
58 current_config.origin_format);
59 memset(transfer_memory, 0, GetDataSize(current_config.trimming_format));
60 return;
61 }
62
63 if (current_config.origin_format > current_config.trimming_format) {
64 LOG_WARNING(Service_IRS, "Origin format {} is smaller than trimming format {}",
65 current_config.origin_format, current_config.trimming_format);
66 memset(transfer_memory, 0, GetDataSize(current_config.trimming_format));
67 return;
68 }
69
70 std::vector<u8> window_data{};
71 const auto origin_width = GetDataWidth(current_config.origin_format);
72 const auto origin_height = GetDataHeight(current_config.origin_format);
73 const auto trimming_width = GetDataWidth(current_config.trimming_format);
74 const auto trimming_height = GetDataHeight(current_config.trimming_format);
75 window_data.resize(GetDataSize(current_config.trimming_format));
76
77 if (trimming_width + current_config.trimming_start_x > origin_width ||
78 trimming_height + current_config.trimming_start_y > origin_height) {
79 LOG_WARNING(Service_IRS,
80 "Trimming area ({}, {}, {}, {}) is outside of origin area ({}, {})",
81 current_config.trimming_start_x, current_config.trimming_start_y,
82 trimming_width, trimming_height, origin_width, origin_height);
83 memset(transfer_memory, 0, GetDataSize(current_config.trimming_format));
84 return;
85 }
86
87 for (std::size_t y = 0; y < trimming_height; y++) {
88 for (std::size_t x = 0; x < trimming_width; x++) {
89 const std::size_t window_index = (y * trimming_width) + x;
90 const std::size_t origin_index =
91 ((y + current_config.trimming_start_y) * origin_width) + x +
92 current_config.trimming_start_x;
93 window_data[window_index] = camera_data.data[origin_index];
94 }
95 }
96
97 memcpy(transfer_memory, window_data.data(), GetDataSize(current_config.trimming_format));
98
99 if (!IsProcessorActive()) {
100 StartProcessor();
101 }
102}
103
104void ImageTransferProcessor::SetConfig(Core::IrSensor::PackedImageTransferProcessorConfig config) {
105 current_config.camera_config.exposure_time = config.camera_config.exposure_time;
106 current_config.camera_config.gain = config.camera_config.gain;
107 current_config.camera_config.is_negative_used = config.camera_config.is_negative_used;
108 current_config.camera_config.light_target =
109 static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target);
110 current_config.origin_format =
111 static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.format);
112 current_config.trimming_format =
113 static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.format);
114 current_config.trimming_start_x = 0;
115 current_config.trimming_start_y = 0;
116
117 npad_device->SetCameraFormat(current_config.origin_format);
118}
119
120void ImageTransferProcessor::SetConfig(
121 Core::IrSensor::PackedImageTransferProcessorExConfig config) {
122 current_config.camera_config.exposure_time = config.camera_config.exposure_time;
123 current_config.camera_config.gain = config.camera_config.gain;
124 current_config.camera_config.is_negative_used = config.camera_config.is_negative_used;
125 current_config.camera_config.light_target =
126 static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target);
127 current_config.origin_format =
128 static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.origin_format);
129 current_config.trimming_format =
130 static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.trimming_format);
131 current_config.trimming_start_x = config.trimming_start_x;
132 current_config.trimming_start_y = config.trimming_start_y;
133
134 npad_device->SetCameraFormat(current_config.origin_format);
135}
136
137void ImageTransferProcessor::SetTransferMemoryPointer(u8* t_mem) {
138 is_transfer_memory_set = true;
139 transfer_memory = t_mem;
140}
141
142Core::IrSensor::ImageTransferProcessorState ImageTransferProcessor::GetState(
143 std::vector<u8>& data) const {
144 const auto size = GetDataSize(current_config.trimming_format);
145 data.resize(size);
146 memcpy(data.data(), transfer_memory, size);
147 return processor_state;
148}
149
150} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/image_transfer_processor.h b/src/core/hle/service/hid/irsensor/image_transfer_processor.h
new file mode 100644
index 000000000..393df492d
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/image_transfer_processor.h
@@ -0,0 +1,73 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "common/common_types.h"
7#include "core/hid/irs_types.h"
8#include "core/hle/service/hid/irsensor/processor_base.h"
9
10namespace Core::HID {
11class EmulatedController;
12} // namespace Core::HID
13
14namespace Service::IRS {
15class ImageTransferProcessor final : public ProcessorBase {
16public:
17 explicit ImageTransferProcessor(Core::HID::HIDCore& hid_core_,
18 Core::IrSensor::DeviceFormat& device_format,
19 std::size_t npad_index);
20 ~ImageTransferProcessor() override;
21
22 // Called when the processor is initialized
23 void StartProcessor() override;
24
25 // Called when the processor is suspended
26 void SuspendProcessor() override;
27
28 // Called when the processor is stopped
29 void StopProcessor() override;
30
31 // Sets config parameters of the camera
32 void SetConfig(Core::IrSensor::PackedImageTransferProcessorConfig config);
33 void SetConfig(Core::IrSensor::PackedImageTransferProcessorExConfig config);
34
35 // Transfer memory where the image data will be stored
36 void SetTransferMemoryPointer(u8* t_mem);
37
38 Core::IrSensor::ImageTransferProcessorState GetState(std::vector<u8>& data) const;
39
40private:
41 // This is nn::irsensor::ImageTransferProcessorConfig
42 struct ImageTransferProcessorConfig {
43 Core::IrSensor::CameraConfig camera_config;
44 Core::IrSensor::ImageTransferProcessorFormat format;
45 };
46 static_assert(sizeof(ImageTransferProcessorConfig) == 0x20,
47 "ImageTransferProcessorConfig is an invalid size");
48
49 // This is nn::irsensor::ImageTransferProcessorExConfig
50 struct ImageTransferProcessorExConfig {
51 Core::IrSensor::CameraConfig camera_config;
52 Core::IrSensor::ImageTransferProcessorFormat origin_format;
53 Core::IrSensor::ImageTransferProcessorFormat trimming_format;
54 u16 trimming_start_x;
55 u16 trimming_start_y;
56 bool is_external_light_filter_enabled;
57 INSERT_PADDING_BYTES(3);
58 };
59 static_assert(sizeof(ImageTransferProcessorExConfig) == 0x28,
60 "ImageTransferProcessorExConfig is an invalid size");
61
62 void OnControllerUpdate(Core::HID::ControllerTriggerType type);
63
64 ImageTransferProcessorExConfig current_config{};
65 Core::IrSensor::ImageTransferProcessorState processor_state{};
66 Core::IrSensor::DeviceFormat& device;
67 Core::HID::EmulatedController* npad_device;
68 int callback_key{};
69
70 u8* transfer_memory = nullptr;
71 bool is_transfer_memory_set = false;
72};
73} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/ir_led_processor.cpp b/src/core/hle/service/hid/irsensor/ir_led_processor.cpp
new file mode 100644
index 000000000..8e6dd99e4
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/ir_led_processor.cpp
@@ -0,0 +1,27 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include "core/hle/service/hid/irsensor/ir_led_processor.h"
5
6namespace Service::IRS {
7IrLedProcessor::IrLedProcessor(Core::IrSensor::DeviceFormat& device_format)
8 : device(device_format) {
9 device.mode = Core::IrSensor::IrSensorMode::IrLedProcessor;
10 device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
11 device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
12}
13
14IrLedProcessor::~IrLedProcessor() = default;
15
16void IrLedProcessor::StartProcessor() {}
17
18void IrLedProcessor::SuspendProcessor() {}
19
20void IrLedProcessor::StopProcessor() {}
21
22void IrLedProcessor::SetConfig(Core::IrSensor::PackedIrLedProcessorConfig config) {
23 current_config.light_target =
24 static_cast<Core::IrSensor::CameraLightTarget>(config.light_target);
25}
26
27} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/ir_led_processor.h b/src/core/hle/service/hid/irsensor/ir_led_processor.h
new file mode 100644
index 000000000..c3d8693c9
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/ir_led_processor.h
@@ -0,0 +1,47 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "common/bit_field.h"
7#include "common/common_types.h"
8#include "core/hid/irs_types.h"
9#include "core/hle/service/hid/irsensor/processor_base.h"
10
11namespace Service::IRS {
12class IrLedProcessor final : public ProcessorBase {
13public:
14 explicit IrLedProcessor(Core::IrSensor::DeviceFormat& device_format);
15 ~IrLedProcessor() override;
16
17 // Called when the processor is initialized
18 void StartProcessor() override;
19
20 // Called when the processor is suspended
21 void SuspendProcessor() override;
22
23 // Called when the processor is stopped
24 void StopProcessor() override;
25
26 // Sets config parameters of the camera
27 void SetConfig(Core::IrSensor::PackedIrLedProcessorConfig config);
28
29private:
30 // This is nn::irsensor::IrLedProcessorConfig
31 struct IrLedProcessorConfig {
32 Core::IrSensor::CameraLightTarget light_target;
33 };
34 static_assert(sizeof(IrLedProcessorConfig) == 0x4, "IrLedProcessorConfig is an invalid size");
35
36 struct IrLedProcessorState {
37 s64 sampling_number;
38 u64 timestamp;
39 std::array<u8, 0x8> data;
40 };
41 static_assert(sizeof(IrLedProcessorState) == 0x18, "IrLedProcessorState is an invalid size");
42
43 IrLedProcessorConfig current_config{};
44 Core::IrSensor::DeviceFormat& device;
45};
46
47} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/moment_processor.cpp b/src/core/hle/service/hid/irsensor/moment_processor.cpp
new file mode 100644
index 000000000..dbaca420a
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/moment_processor.cpp
@@ -0,0 +1,34 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include "core/hle/service/hid/irsensor/moment_processor.h"
5
6namespace Service::IRS {
7MomentProcessor::MomentProcessor(Core::IrSensor::DeviceFormat& device_format)
8 : device(device_format) {
9 device.mode = Core::IrSensor::IrSensorMode::MomentProcessor;
10 device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
11 device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
12}
13
14MomentProcessor::~MomentProcessor() = default;
15
16void MomentProcessor::StartProcessor() {}
17
18void MomentProcessor::SuspendProcessor() {}
19
20void MomentProcessor::StopProcessor() {}
21
22void MomentProcessor::SetConfig(Core::IrSensor::PackedMomentProcessorConfig config) {
23 current_config.camera_config.exposure_time = config.camera_config.exposure_time;
24 current_config.camera_config.gain = config.camera_config.gain;
25 current_config.camera_config.is_negative_used = config.camera_config.is_negative_used;
26 current_config.camera_config.light_target =
27 static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target);
28 current_config.window_of_interest = config.window_of_interest;
29 current_config.preprocess =
30 static_cast<Core::IrSensor::MomentProcessorPreprocess>(config.preprocess);
31 current_config.preprocess_intensity_threshold = config.preprocess_intensity_threshold;
32}
33
34} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/moment_processor.h b/src/core/hle/service/hid/irsensor/moment_processor.h
new file mode 100644
index 000000000..d4bd22e0f
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/moment_processor.h
@@ -0,0 +1,61 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "common/bit_field.h"
7#include "common/common_types.h"
8#include "core/hid/irs_types.h"
9#include "core/hle/service/hid/irsensor/processor_base.h"
10
11namespace Service::IRS {
12class MomentProcessor final : public ProcessorBase {
13public:
14 explicit MomentProcessor(Core::IrSensor::DeviceFormat& device_format);
15 ~MomentProcessor() override;
16
17 // Called when the processor is initialized
18 void StartProcessor() override;
19
20 // Called when the processor is suspended
21 void SuspendProcessor() override;
22
23 // Called when the processor is stopped
24 void StopProcessor() override;
25
26 // Sets config parameters of the camera
27 void SetConfig(Core::IrSensor::PackedMomentProcessorConfig config);
28
29private:
30 // This is nn::irsensor::MomentProcessorConfig
31 struct MomentProcessorConfig {
32 Core::IrSensor::CameraConfig camera_config;
33 Core::IrSensor::IrsRect window_of_interest;
34 Core::IrSensor::MomentProcessorPreprocess preprocess;
35 u32 preprocess_intensity_threshold;
36 };
37 static_assert(sizeof(MomentProcessorConfig) == 0x28,
38 "MomentProcessorConfig is an invalid size");
39
40 // This is nn::irsensor::MomentStatistic
41 struct MomentStatistic {
42 f32 average_intensity;
43 Core::IrSensor::IrsCentroid centroid;
44 };
45 static_assert(sizeof(MomentStatistic) == 0xC, "MomentStatistic is an invalid size");
46
47 // This is nn::irsensor::MomentProcessorState
48 struct MomentProcessorState {
49 s64 sampling_number;
50 u64 timestamp;
51 Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level;
52 INSERT_PADDING_BYTES(4);
53 std::array<MomentStatistic, 0x30> stadistic;
54 };
55 static_assert(sizeof(MomentProcessorState) == 0x258, "MomentProcessorState is an invalid size");
56
57 MomentProcessorConfig current_config{};
58 Core::IrSensor::DeviceFormat& device;
59};
60
61} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/pointing_processor.cpp b/src/core/hle/service/hid/irsensor/pointing_processor.cpp
new file mode 100644
index 000000000..929f177fc
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/pointing_processor.cpp
@@ -0,0 +1,26 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include "core/hle/service/hid/irsensor/pointing_processor.h"
5
6namespace Service::IRS {
7PointingProcessor::PointingProcessor(Core::IrSensor::DeviceFormat& device_format)
8 : device(device_format) {
9 device.mode = Core::IrSensor::IrSensorMode::PointingProcessorMarker;
10 device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
11 device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
12}
13
14PointingProcessor::~PointingProcessor() = default;
15
16void PointingProcessor::StartProcessor() {}
17
18void PointingProcessor::SuspendProcessor() {}
19
20void PointingProcessor::StopProcessor() {}
21
22void PointingProcessor::SetConfig(Core::IrSensor::PackedPointingProcessorConfig config) {
23 current_config.window_of_interest = config.window_of_interest;
24}
25
26} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/pointing_processor.h b/src/core/hle/service/hid/irsensor/pointing_processor.h
new file mode 100644
index 000000000..cf4930794
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/pointing_processor.h
@@ -0,0 +1,61 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "common/common_types.h"
7#include "core/hid/irs_types.h"
8#include "core/hle/service/hid/irsensor/processor_base.h"
9
10namespace Service::IRS {
11class PointingProcessor final : public ProcessorBase {
12public:
13 explicit PointingProcessor(Core::IrSensor::DeviceFormat& device_format);
14 ~PointingProcessor() override;
15
16 // Called when the processor is initialized
17 void StartProcessor() override;
18
19 // Called when the processor is suspended
20 void SuspendProcessor() override;
21
22 // Called when the processor is stopped
23 void StopProcessor() override;
24
25 // Sets config parameters of the camera
26 void SetConfig(Core::IrSensor::PackedPointingProcessorConfig config);
27
28private:
29 // This is nn::irsensor::PointingProcessorConfig
30 struct PointingProcessorConfig {
31 Core::IrSensor::IrsRect window_of_interest;
32 };
33 static_assert(sizeof(PointingProcessorConfig) == 0x8,
34 "PointingProcessorConfig is an invalid size");
35
36 struct PointingProcessorMarkerData {
37 u8 pointing_status;
38 INSERT_PADDING_BYTES(3);
39 u32 unknown;
40 float unkown_float1;
41 float position_x;
42 float position_y;
43 float unkown_float2;
44 Core::IrSensor::IrsRect window_of_interest;
45 };
46 static_assert(sizeof(PointingProcessorMarkerData) == 0x20,
47 "PointingProcessorMarkerData is an invalid size");
48
49 struct PointingProcessorMarkerState {
50 s64 sampling_number;
51 u64 timestamp;
52 std::array<PointingProcessorMarkerData, 0x3> data;
53 };
54 static_assert(sizeof(PointingProcessorMarkerState) == 0x70,
55 "PointingProcessorMarkerState is an invalid size");
56
57 PointingProcessorConfig current_config{};
58 Core::IrSensor::DeviceFormat& device;
59};
60
61} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/processor_base.cpp b/src/core/hle/service/hid/irsensor/processor_base.cpp
new file mode 100644
index 000000000..4d43ca17a
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/processor_base.cpp
@@ -0,0 +1,67 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include "core/hle/service/hid/irsensor/processor_base.h"
5
6namespace Service::IRS {
7
8ProcessorBase::ProcessorBase() {}
9ProcessorBase::~ProcessorBase() = default;
10
11bool ProcessorBase::IsProcessorActive() const {
12 return is_active;
13}
14
15std::size_t ProcessorBase::GetDataSize(Core::IrSensor::ImageTransferProcessorFormat format) const {
16 switch (format) {
17 case Core::IrSensor::ImageTransferProcessorFormat::Size320x240:
18 return 320 * 240;
19 case Core::IrSensor::ImageTransferProcessorFormat::Size160x120:
20 return 160 * 120;
21 case Core::IrSensor::ImageTransferProcessorFormat::Size80x60:
22 return 80 * 60;
23 case Core::IrSensor::ImageTransferProcessorFormat::Size40x30:
24 return 40 * 30;
25 case Core::IrSensor::ImageTransferProcessorFormat::Size20x15:
26 return 20 * 15;
27 default:
28 return 0;
29 }
30}
31
32std::size_t ProcessorBase::GetDataWidth(Core::IrSensor::ImageTransferProcessorFormat format) const {
33 switch (format) {
34 case Core::IrSensor::ImageTransferProcessorFormat::Size320x240:
35 return 320;
36 case Core::IrSensor::ImageTransferProcessorFormat::Size160x120:
37 return 160;
38 case Core::IrSensor::ImageTransferProcessorFormat::Size80x60:
39 return 80;
40 case Core::IrSensor::ImageTransferProcessorFormat::Size40x30:
41 return 40;
42 case Core::IrSensor::ImageTransferProcessorFormat::Size20x15:
43 return 20;
44 default:
45 return 0;
46 }
47}
48
49std::size_t ProcessorBase::GetDataHeight(
50 Core::IrSensor::ImageTransferProcessorFormat format) const {
51 switch (format) {
52 case Core::IrSensor::ImageTransferProcessorFormat::Size320x240:
53 return 240;
54 case Core::IrSensor::ImageTransferProcessorFormat::Size160x120:
55 return 120;
56 case Core::IrSensor::ImageTransferProcessorFormat::Size80x60:
57 return 60;
58 case Core::IrSensor::ImageTransferProcessorFormat::Size40x30:
59 return 30;
60 case Core::IrSensor::ImageTransferProcessorFormat::Size20x15:
61 return 15;
62 default:
63 return 0;
64 }
65}
66
67} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/processor_base.h b/src/core/hle/service/hid/irsensor/processor_base.h
new file mode 100644
index 000000000..bc0d2977b
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/processor_base.h
@@ -0,0 +1,33 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "common/common_types.h"
7#include "core/hid/irs_types.h"
8
9namespace Service::IRS {
10class ProcessorBase {
11public:
12 explicit ProcessorBase();
13 virtual ~ProcessorBase();
14
15 virtual void StartProcessor() = 0;
16 virtual void SuspendProcessor() = 0;
17 virtual void StopProcessor() = 0;
18
19 bool IsProcessorActive() const;
20
21protected:
22 /// Returns the number of bytes the image uses
23 std::size_t GetDataSize(Core::IrSensor::ImageTransferProcessorFormat format) const;
24
25 /// Returns the width of the image
26 std::size_t GetDataWidth(Core::IrSensor::ImageTransferProcessorFormat format) const;
27
28 /// Returns the height of the image
29 std::size_t GetDataHeight(Core::IrSensor::ImageTransferProcessorFormat format) const;
30
31 bool is_active{false};
32};
33} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp b/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp
new file mode 100644
index 000000000..e691c840a
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp
@@ -0,0 +1,29 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include "core/hle/service/hid/irsensor/tera_plugin_processor.h"
5
6namespace Service::IRS {
7TeraPluginProcessor::TeraPluginProcessor(Core::IrSensor::DeviceFormat& device_format)
8 : device(device_format) {
9 device.mode = Core::IrSensor::IrSensorMode::TeraPluginProcessor;
10 device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
11 device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
12}
13
14TeraPluginProcessor::~TeraPluginProcessor() = default;
15
16void TeraPluginProcessor::StartProcessor() {}
17
18void TeraPluginProcessor::SuspendProcessor() {}
19
20void TeraPluginProcessor::StopProcessor() {}
21
22void TeraPluginProcessor::SetConfig(Core::IrSensor::PackedTeraPluginProcessorConfig config) {
23 current_config.mode = config.mode;
24 current_config.unknown_1 = config.unknown_1;
25 current_config.unknown_2 = config.unknown_2;
26 current_config.unknown_3 = config.unknown_3;
27}
28
29} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/tera_plugin_processor.h b/src/core/hle/service/hid/irsensor/tera_plugin_processor.h
new file mode 100644
index 000000000..bbea7ed0b
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/tera_plugin_processor.h
@@ -0,0 +1,53 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "common/bit_field.h"
7#include "common/common_types.h"
8#include "core/hid/irs_types.h"
9#include "core/hle/service/hid/irsensor/processor_base.h"
10
11namespace Service::IRS {
12class TeraPluginProcessor final : public ProcessorBase {
13public:
14 explicit TeraPluginProcessor(Core::IrSensor::DeviceFormat& device_format);
15 ~TeraPluginProcessor() override;
16
17 // Called when the processor is initialized
18 void StartProcessor() override;
19
20 // Called when the processor is suspended
21 void SuspendProcessor() override;
22
23 // Called when the processor is stopped
24 void StopProcessor() override;
25
26 // Sets config parameters of the camera
27 void SetConfig(Core::IrSensor::PackedTeraPluginProcessorConfig config);
28
29private:
30 // This is nn::irsensor::TeraPluginProcessorConfig
31 struct TeraPluginProcessorConfig {
32 u8 mode;
33 u8 unknown_1;
34 u8 unknown_2;
35 u8 unknown_3;
36 };
37 static_assert(sizeof(TeraPluginProcessorConfig) == 0x4,
38 "TeraPluginProcessorConfig is an invalid size");
39
40 struct TeraPluginProcessorState {
41 s64 sampling_number;
42 u64 timestamp;
43 Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level;
44 std::array<u8, 0x12c> data;
45 };
46 static_assert(sizeof(TeraPluginProcessorState) == 0x140,
47 "TeraPluginProcessorState is an invalid size");
48
49 TeraPluginProcessorConfig current_config{};
50 Core::IrSensor::DeviceFormat& device;
51};
52
53} // namespace Service::IRS
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index 7055ea93e..2889973e4 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -18,8 +18,8 @@ namespace {
18 18
19} // Anonymous namespace 19} // Anonymous namespace
20 20
21#include "core/network/network.h" 21#include "core/internal_network/network.h"
22#include "core/network/network_interface.h" 22#include "core/internal_network/network_interface.h"
23 23
24namespace Service::NIFM { 24namespace Service::NIFM {
25 25
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index 3e9dc4a13..c7194731e 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -13,8 +13,8 @@
13#include "core/hle/kernel/k_thread.h" 13#include "core/hle/kernel/k_thread.h"
14#include "core/hle/service/sockets/bsd.h" 14#include "core/hle/service/sockets/bsd.h"
15#include "core/hle/service/sockets/sockets_translate.h" 15#include "core/hle/service/sockets/sockets_translate.h"
16#include "core/network/network.h" 16#include "core/internal_network/network.h"
17#include "core/network/sockets.h" 17#include "core/internal_network/sockets.h"
18 18
19namespace Service::Sockets { 19namespace Service::Sockets {
20 20
diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h
index fed740d87..9ea36428d 100644
--- a/src/core/hle/service/sockets/bsd.h
+++ b/src/core/hle/service/sockets/bsd.h
@@ -16,7 +16,7 @@ class System;
16 16
17namespace Network { 17namespace Network {
18class Socket; 18class Socket;
19} 19} // namespace Network
20 20
21namespace Service::Sockets { 21namespace Service::Sockets {
22 22
diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp
index 9c0936d97..2db10ec81 100644
--- a/src/core/hle/service/sockets/sockets_translate.cpp
+++ b/src/core/hle/service/sockets/sockets_translate.cpp
@@ -7,7 +7,7 @@
7#include "common/common_types.h" 7#include "common/common_types.h"
8#include "core/hle/service/sockets/sockets.h" 8#include "core/hle/service/sockets/sockets.h"
9#include "core/hle/service/sockets/sockets_translate.h" 9#include "core/hle/service/sockets/sockets_translate.h"
10#include "core/network/network.h" 10#include "core/internal_network/network.h"
11 11
12namespace Service::Sockets { 12namespace Service::Sockets {
13 13
diff --git a/src/core/hle/service/sockets/sockets_translate.h b/src/core/hle/service/sockets/sockets_translate.h
index 5e9809add..c93291d3e 100644
--- a/src/core/hle/service/sockets/sockets_translate.h
+++ b/src/core/hle/service/sockets/sockets_translate.h
@@ -7,7 +7,7 @@
7 7
8#include "common/common_types.h" 8#include "common/common_types.h"
9#include "core/hle/service/sockets/sockets.h" 9#include "core/hle/service/sockets/sockets.h"
10#include "core/network/network.h" 10#include "core/internal_network/network.h"
11 11
12namespace Service::Sockets { 12namespace Service::Sockets {
13 13
diff --git a/src/core/network/network.cpp b/src/core/internal_network/network.cpp
index fdafbea92..36c43cc8f 100644
--- a/src/core/network/network.cpp
+++ b/src/core/internal_network/network.cpp
@@ -29,9 +29,9 @@
29#include "common/common_types.h" 29#include "common/common_types.h"
30#include "common/logging/log.h" 30#include "common/logging/log.h"
31#include "common/settings.h" 31#include "common/settings.h"
32#include "core/network/network.h" 32#include "core/internal_network/network.h"
33#include "core/network/network_interface.h" 33#include "core/internal_network/network_interface.h"
34#include "core/network/sockets.h" 34#include "core/internal_network/sockets.h"
35 35
36namespace Network { 36namespace Network {
37 37
diff --git a/src/core/network/network.h b/src/core/internal_network/network.h
index 10e5ef10d..10e5ef10d 100644
--- a/src/core/network/network.h
+++ b/src/core/internal_network/network.h
diff --git a/src/core/network/network_interface.cpp b/src/core/internal_network/network_interface.cpp
index 15ecc6abf..0f0a66160 100644
--- a/src/core/network/network_interface.cpp
+++ b/src/core/internal_network/network_interface.cpp
@@ -11,7 +11,7 @@
11#include "common/logging/log.h" 11#include "common/logging/log.h"
12#include "common/settings.h" 12#include "common/settings.h"
13#include "common/string_util.h" 13#include "common/string_util.h"
14#include "core/network/network_interface.h" 14#include "core/internal_network/network_interface.h"
15 15
16#ifdef _WIN32 16#ifdef _WIN32
17#include <iphlpapi.h> 17#include <iphlpapi.h>
diff --git a/src/core/network/network_interface.h b/src/core/internal_network/network_interface.h
index 9b98b6b42..9b98b6b42 100644
--- a/src/core/network/network_interface.h
+++ b/src/core/internal_network/network_interface.h
diff --git a/src/core/network/sockets.h b/src/core/internal_network/sockets.h
index f889159f5..77e27e928 100644
--- a/src/core/network/sockets.h
+++ b/src/core/internal_network/sockets.h
@@ -3,6 +3,7 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <map>
6#include <memory> 7#include <memory>
7#include <utility> 8#include <utility>
8 9
@@ -12,7 +13,7 @@
12#endif 13#endif
13 14
14#include "common/common_types.h" 15#include "common/common_types.h"
15#include "core/network/network.h" 16#include "core/internal_network/network.h"
16 17
17// TODO: C++20 Replace std::vector usages with std::span 18// TODO: C++20 Replace std::vector usages with std::span
18 19
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 635449fce..1b44280b5 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <algorithm> 4#include <algorithm>
6#include <cstring> 5#include <cstring>
diff --git a/src/core/memory.h b/src/core/memory.h
index 780c45385..2a21fbcfd 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp
index 6ef459b7a..f09c176f8 100644
--- a/src/core/perf_stats.cpp
+++ b/src/core/perf_stats.cpp
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <algorithm> 4#include <algorithm>
6#include <chrono> 5#include <chrono>
diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h
index 816202588..dd6becc02 100644
--- a/src/core/perf_stats.h
+++ b/src/core/perf_stats.h
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 654db0b52..abcf6eb11 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <array> 4#include <array>
6 5
diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h
index 6f3d45bea..887dc98f3 100644
--- a/src/core/telemetry_session.h
+++ b/src/core/telemetry_session.h
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 48e799cf5..4b91b88ce 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -1,4 +1,9 @@
1# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-2.0-or-later
3
1add_library(input_common STATIC 4add_library(input_common STATIC
5 drivers/camera.cpp
6 drivers/camera.h
2 drivers/gc_adapter.cpp 7 drivers/gc_adapter.cpp
3 drivers/gc_adapter.h 8 drivers/gc_adapter.h
4 drivers/keyboard.cpp 9 drivers/keyboard.cpp
diff --git a/src/input_common/drivers/camera.cpp b/src/input_common/drivers/camera.cpp
new file mode 100644
index 000000000..dceea67e0
--- /dev/null
+++ b/src/input_common/drivers/camera.cpp
@@ -0,0 +1,82 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <fmt/format.h>
5
6#include "common/param_package.h"
7#include "input_common/drivers/camera.h"
8
9namespace InputCommon {
10constexpr PadIdentifier identifier = {
11 .guid = Common::UUID{},
12 .port = 0,
13 .pad = 0,
14};
15
16Camera::Camera(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
17 PreSetController(identifier);
18}
19
20void Camera::SetCameraData(std::size_t width, std::size_t height, std::vector<u32> data) {
21 const std::size_t desired_width = getImageWidth();
22 const std::size_t desired_height = getImageHeight();
23 status.data.resize(desired_width * desired_height);
24
25 // Resize image to desired format
26 for (std::size_t y = 0; y < desired_height; y++) {
27 for (std::size_t x = 0; x < desired_width; x++) {
28 const std::size_t pixel_index = y * desired_width + x;
29 const std::size_t old_x = width * x / desired_width;
30 const std::size_t old_y = height * y / desired_height;
31 const std::size_t data_pixel_index = old_y * width + old_x;
32 status.data[pixel_index] = static_cast<u8>(data[data_pixel_index] & 0xFF);
33 }
34 }
35
36 SetCamera(identifier, status);
37}
38
39std::size_t Camera::getImageWidth() const {
40 switch (status.format) {
41 case Common::Input::CameraFormat::Size320x240:
42 return 320;
43 case Common::Input::CameraFormat::Size160x120:
44 return 160;
45 case Common::Input::CameraFormat::Size80x60:
46 return 80;
47 case Common::Input::CameraFormat::Size40x30:
48 return 40;
49 case Common::Input::CameraFormat::Size20x15:
50 return 20;
51 case Common::Input::CameraFormat::None:
52 default:
53 return 0;
54 }
55}
56
57std::size_t Camera::getImageHeight() const {
58 switch (status.format) {
59 case Common::Input::CameraFormat::Size320x240:
60 return 240;
61 case Common::Input::CameraFormat::Size160x120:
62 return 120;
63 case Common::Input::CameraFormat::Size80x60:
64 return 60;
65 case Common::Input::CameraFormat::Size40x30:
66 return 30;
67 case Common::Input::CameraFormat::Size20x15:
68 return 15;
69 case Common::Input::CameraFormat::None:
70 default:
71 return 0;
72 }
73}
74
75Common::Input::CameraError Camera::SetCameraFormat(
76 [[maybe_unused]] const PadIdentifier& identifier_,
77 const Common::Input::CameraFormat camera_format) {
78 status.format = camera_format;
79 return Common::Input::CameraError::None;
80}
81
82} // namespace InputCommon
diff --git a/src/input_common/drivers/camera.h b/src/input_common/drivers/camera.h
new file mode 100644
index 000000000..b8a7c75e5
--- /dev/null
+++ b/src/input_common/drivers/camera.h
@@ -0,0 +1,29 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "input_common/input_engine.h"
7
8namespace InputCommon {
9
10/**
11 * A button device factory representing a keyboard. It receives keyboard events and forward them
12 * to all button devices it created.
13 */
14class Camera final : public InputEngine {
15public:
16 explicit Camera(std::string input_engine_);
17
18 void SetCameraData(std::size_t width, std::size_t height, std::vector<u32> data);
19
20 std::size_t getImageWidth() const;
21 std::size_t getImageHeight() const;
22
23 Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_,
24 Common::Input::CameraFormat camera_format) override;
25
26 Common::Input::CameraStatus status{};
27};
28
29} // namespace InputCommon
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index 00474ac77..de388ec4c 100644
--- a/src/input_common/drivers/sdl_driver.cpp
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -1,6 +1,5 @@
1// Copyright 2018 Citra Emulator Project 1// SPDX-FileCopyrightText: 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include "common/logging/log.h" 4#include "common/logging/log.h"
6#include "common/math_util.h" 5#include "common/math_util.h"
diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h
index 7dc7a93c7..fc3a44572 100644
--- a/src/input_common/drivers/sdl_driver.h
+++ b/src/input_common/drivers/sdl_driver.h
@@ -1,6 +1,5 @@
1// Copyright 2018 Citra Emulator Project 1// SPDX-FileCopyrightText: 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/input_common/drivers/tas_input.cpp b/src/input_common/drivers/tas_input.cpp
index 66dbefe00..21c6ed405 100644
--- a/src/input_common/drivers/tas_input.cpp
+++ b/src/input_common/drivers/tas_input.cpp
@@ -1,5 +1,5 @@
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 <cstring> 4#include <cstring>
5#include <fmt/format.h> 5#include <fmt/format.h>
diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp
index 825262a07..808b21069 100644
--- a/src/input_common/drivers/udp_client.cpp
+++ b/src/input_common/drivers/udp_client.cpp
@@ -1,6 +1,5 @@
1// Copyright 2018 Citra Emulator Project 1// SPDX-FileCopyrightText: 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <random> 4#include <random>
6#include <boost/asio.hpp> 5#include <boost/asio.hpp>
diff --git a/src/input_common/drivers/udp_client.h b/src/input_common/drivers/udp_client.h
index dece2a45b..cea9f579a 100644
--- a/src/input_common/drivers/udp_client.h
+++ b/src/input_common/drivers/udp_client.h
@@ -1,6 +1,5 @@
1// Copyright 2018 Citra Emulator Project 1// SPDX-FileCopyrightText: 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/input_common/helpers/stick_from_buttons.cpp b/src/input_common/helpers/stick_from_buttons.cpp
index 31e6f62ab..536d413a5 100644
--- a/src/input_common/helpers/stick_from_buttons.cpp
+++ b/src/input_common/helpers/stick_from_buttons.cpp
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <chrono> 4#include <chrono>
6#include <cmath> 5#include <cmath>
diff --git a/src/input_common/helpers/stick_from_buttons.h b/src/input_common/helpers/stick_from_buttons.h
index 437ace4f7..e8d865743 100644
--- a/src/input_common/helpers/stick_from_buttons.h
+++ b/src/input_common/helpers/stick_from_buttons.h
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/input_common/helpers/touch_from_buttons.cpp b/src/input_common/helpers/touch_from_buttons.cpp
index f1b57d03a..da4a3dca5 100644
--- a/src/input_common/helpers/touch_from_buttons.cpp
+++ b/src/input_common/helpers/touch_from_buttons.cpp
@@ -1,6 +1,5 @@
1// Copyright 2020 Citra Emulator Project 1// SPDX-FileCopyrightText: 2020 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <algorithm> 4#include <algorithm>
6#include "common/settings.h" 5#include "common/settings.h"
diff --git a/src/input_common/helpers/touch_from_buttons.h b/src/input_common/helpers/touch_from_buttons.h
index 628f18215..c6cb3ab3c 100644
--- a/src/input_common/helpers/touch_from_buttons.h
+++ b/src/input_common/helpers/touch_from_buttons.h
@@ -1,6 +1,5 @@
1// Copyright 2020 Citra Emulator Project 1// SPDX-FileCopyrightText: 2020 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/input_common/helpers/udp_protocol.cpp b/src/input_common/helpers/udp_protocol.cpp
index cdeab7e11..994380d21 100644
--- a/src/input_common/helpers/udp_protocol.cpp
+++ b/src/input_common/helpers/udp_protocol.cpp
@@ -1,6 +1,5 @@
1// Copyright 2018 Citra Emulator Project 1// SPDX-FileCopyrightText: 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <cstddef> 4#include <cstddef>
6#include <cstring> 5#include <cstring>
diff --git a/src/input_common/helpers/udp_protocol.h b/src/input_common/helpers/udp_protocol.h
index 597f51cd3..d9643ffe0 100644
--- a/src/input_common/helpers/udp_protocol.h
+++ b/src/input_common/helpers/udp_protocol.h
@@ -1,6 +1,5 @@
1// Copyright 2018 Citra Emulator Project 1// SPDX-FileCopyrightText: 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
@@ -85,7 +84,7 @@ enum RegisterFlags : u8 {
85struct Version {}; 84struct Version {};
86/** 85/**
87 * Requests the server to send information about what controllers are plugged into the ports 86 * Requests the server to send information about what controllers are plugged into the ports
88 * In citra's case, we only have one controller, so for simplicity's sake, we can just send a 87 * In yuzu's case, we only have one controller, so for simplicity's sake, we can just send a
89 * request explicitly for the first controller port and leave it at that. In the future it would be 88 * request explicitly for the first controller port and leave it at that. In the future it would be
90 * nice to make this configurable 89 * nice to make this configurable
91 */ 90 */
diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp
index 12214d146..6ede0e4b0 100644
--- a/src/input_common/input_engine.cpp
+++ b/src/input_common/input_engine.cpp
@@ -90,6 +90,18 @@ void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const B
90 TriggerOnMotionChange(identifier, motion, value); 90 TriggerOnMotionChange(identifier, motion, value);
91} 91}
92 92
93void InputEngine::SetCamera(const PadIdentifier& identifier,
94 const Common::Input::CameraStatus& value) {
95 {
96 std::scoped_lock lock{mutex};
97 ControllerData& controller = controller_list.at(identifier);
98 if (!configuring) {
99 controller.camera = value;
100 }
101 }
102 TriggerOnCameraChange(identifier, value);
103}
104
93bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const { 105bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const {
94 std::scoped_lock lock{mutex}; 106 std::scoped_lock lock{mutex};
95 const auto controller_iter = controller_list.find(identifier); 107 const auto controller_iter = controller_list.find(identifier);
@@ -165,6 +177,18 @@ BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion)
165 return controller.motions.at(motion); 177 return controller.motions.at(motion);
166} 178}
167 179
180Common::Input::CameraStatus InputEngine::GetCamera(const PadIdentifier& identifier) const {
181 std::scoped_lock lock{mutex};
182 const auto controller_iter = controller_list.find(identifier);
183 if (controller_iter == controller_list.cend()) {
184 LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(),
185 identifier.pad, identifier.port);
186 return {};
187 }
188 const ControllerData& controller = controller_iter->second;
189 return controller.camera;
190}
191
168void InputEngine::ResetButtonState() { 192void InputEngine::ResetButtonState() {
169 for (const auto& controller : controller_list) { 193 for (const auto& controller : controller_list) {
170 for (const auto& button : controller.second.buttons) { 194 for (const auto& button : controller.second.buttons) {
@@ -317,6 +341,20 @@ void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int mot
317 }); 341 });
318} 342}
319 343
344void InputEngine::TriggerOnCameraChange(const PadIdentifier& identifier,
345 [[maybe_unused]] const Common::Input::CameraStatus& value) {
346 std::scoped_lock lock{mutex_callback};
347 for (const auto& poller_pair : callback_list) {
348 const InputIdentifier& poller = poller_pair.second;
349 if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Camera, 0)) {
350 continue;
351 }
352 if (poller.callback.on_change) {
353 poller.callback.on_change();
354 }
355 }
356}
357
320bool InputEngine::IsInputIdentifierEqual(const InputIdentifier& input_identifier, 358bool InputEngine::IsInputIdentifierEqual(const InputIdentifier& input_identifier,
321 const PadIdentifier& identifier, EngineInputType type, 359 const PadIdentifier& identifier, EngineInputType type,
322 int index) const { 360 int index) const {
diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h
index 13295bd49..f6b3c4610 100644
--- a/src/input_common/input_engine.h
+++ b/src/input_common/input_engine.h
@@ -36,11 +36,12 @@ struct BasicMotion {
36// Types of input that are stored in the engine 36// Types of input that are stored in the engine
37enum class EngineInputType { 37enum class EngineInputType {
38 None, 38 None,
39 Analog,
40 Battery,
39 Button, 41 Button,
42 Camera,
40 HatButton, 43 HatButton,
41 Analog,
42 Motion, 44 Motion,
43 Battery,
44}; 45};
45 46
46namespace std { 47namespace std {
@@ -115,10 +116,17 @@ public:
115 // Sets polling mode to a controller 116 // Sets polling mode to a controller
116 virtual Common::Input::PollingError SetPollingMode( 117 virtual Common::Input::PollingError SetPollingMode(
117 [[maybe_unused]] const PadIdentifier& identifier, 118 [[maybe_unused]] const PadIdentifier& identifier,
118 [[maybe_unused]] const Common::Input::PollingMode vibration) { 119 [[maybe_unused]] const Common::Input::PollingMode polling_mode) {
119 return Common::Input::PollingError::NotSupported; 120 return Common::Input::PollingError::NotSupported;
120 } 121 }
121 122
123 // Sets camera format to a controller
124 virtual Common::Input::CameraError SetCameraFormat(
125 [[maybe_unused]] const PadIdentifier& identifier,
126 [[maybe_unused]] Common::Input::CameraFormat camera_format) {
127 return Common::Input::CameraError::NotSupported;
128 }
129
122 // Returns the engine name 130 // Returns the engine name
123 [[nodiscard]] const std::string& GetEngineName() const; 131 [[nodiscard]] const std::string& GetEngineName() const;
124 132
@@ -174,6 +182,7 @@ public:
174 f32 GetAxis(const PadIdentifier& identifier, int axis) const; 182 f32 GetAxis(const PadIdentifier& identifier, int axis) const;
175 Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const; 183 Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const;
176 BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const; 184 BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const;
185 Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const;
177 186
178 int SetCallback(InputIdentifier input_identifier); 187 int SetCallback(InputIdentifier input_identifier);
179 void SetMappingCallback(MappingCallback callback); 188 void SetMappingCallback(MappingCallback callback);
@@ -185,6 +194,7 @@ protected:
185 void SetAxis(const PadIdentifier& identifier, int axis, f32 value); 194 void SetAxis(const PadIdentifier& identifier, int axis, f32 value);
186 void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value); 195 void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value);
187 void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value); 196 void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value);
197 void SetCamera(const PadIdentifier& identifier, const Common::Input::CameraStatus& value);
188 198
189 virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const { 199 virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const {
190 return "Unknown"; 200 return "Unknown";
@@ -197,6 +207,7 @@ private:
197 std::unordered_map<int, float> axes; 207 std::unordered_map<int, float> axes;
198 std::unordered_map<int, BasicMotion> motions; 208 std::unordered_map<int, BasicMotion> motions;
199 Common::Input::BatteryLevel battery{}; 209 Common::Input::BatteryLevel battery{};
210 Common::Input::CameraStatus camera{};
200 }; 211 };
201 212
202 void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value); 213 void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value);
@@ -205,6 +216,8 @@ private:
205 void TriggerOnBatteryChange(const PadIdentifier& identifier, Common::Input::BatteryLevel value); 216 void TriggerOnBatteryChange(const PadIdentifier& identifier, Common::Input::BatteryLevel value);
206 void TriggerOnMotionChange(const PadIdentifier& identifier, int motion, 217 void TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
207 const BasicMotion& value); 218 const BasicMotion& value);
219 void TriggerOnCameraChange(const PadIdentifier& identifier,
220 const Common::Input::CameraStatus& value);
208 221
209 bool IsInputIdentifierEqual(const InputIdentifier& input_identifier, 222 bool IsInputIdentifierEqual(const InputIdentifier& input_identifier,
210 const PadIdentifier& identifier, EngineInputType type, 223 const PadIdentifier& identifier, EngineInputType type,
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp
index 49ccb4422..133422d5c 100644
--- a/src/input_common/input_poller.cpp
+++ b/src/input_common/input_poller.cpp
@@ -664,6 +664,47 @@ private:
664 InputEngine* input_engine; 664 InputEngine* input_engine;
665}; 665};
666 666
667class InputFromCamera final : public Common::Input::InputDevice {
668public:
669 explicit InputFromCamera(PadIdentifier identifier_, InputEngine* input_engine_)
670 : identifier(identifier_), input_engine(input_engine_) {
671 UpdateCallback engine_callback{[this]() { OnChange(); }};
672 const InputIdentifier input_identifier{
673 .identifier = identifier,
674 .type = EngineInputType::Camera,
675 .index = 0,
676 .callback = engine_callback,
677 };
678 callback_key = input_engine->SetCallback(input_identifier);
679 }
680
681 ~InputFromCamera() override {
682 input_engine->DeleteCallback(callback_key);
683 }
684
685 Common::Input::CameraStatus GetStatus() const {
686 return input_engine->GetCamera(identifier);
687 }
688
689 void ForceUpdate() override {
690 OnChange();
691 }
692
693 void OnChange() {
694 const Common::Input::CallbackStatus status{
695 .type = Common::Input::InputType::IrSensor,
696 .camera_status = GetStatus(),
697 };
698
699 TriggerOnChange(status);
700 }
701
702private:
703 const PadIdentifier identifier;
704 int callback_key;
705 InputEngine* input_engine;
706};
707
667class OutputFromIdentifier final : public Common::Input::OutputDevice { 708class OutputFromIdentifier final : public Common::Input::OutputDevice {
668public: 709public:
669 explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_) 710 explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_)
@@ -682,6 +723,10 @@ public:
682 return input_engine->SetPollingMode(identifier, polling_mode); 723 return input_engine->SetPollingMode(identifier, polling_mode);
683 } 724 }
684 725
726 Common::Input::CameraError SetCameraFormat(Common::Input::CameraFormat camera_format) override {
727 return input_engine->SetCameraFormat(identifier, camera_format);
728 }
729
685private: 730private:
686 const PadIdentifier identifier; 731 const PadIdentifier identifier;
687 InputEngine* input_engine; 732 InputEngine* input_engine;
@@ -920,6 +965,18 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice(
920 properties_y, properties_z, input_engine.get()); 965 properties_y, properties_z, input_engine.get());
921} 966}
922 967
968std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateCameraDevice(
969 const Common::ParamPackage& params) {
970 const PadIdentifier identifier = {
971 .guid = Common::UUID{params.Get("guid", "")},
972 .port = static_cast<std::size_t>(params.Get("port", 0)),
973 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
974 };
975
976 input_engine->PreSetController(identifier);
977 return std::make_unique<InputFromCamera>(identifier, input_engine.get());
978}
979
923InputFactory::InputFactory(std::shared_ptr<InputEngine> input_engine_) 980InputFactory::InputFactory(std::shared_ptr<InputEngine> input_engine_)
924 : input_engine(std::move(input_engine_)) {} 981 : input_engine(std::move(input_engine_)) {}
925 982
@@ -928,6 +985,9 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::Create(
928 if (params.Has("battery")) { 985 if (params.Has("battery")) {
929 return CreateBatteryDevice(params); 986 return CreateBatteryDevice(params);
930 } 987 }
988 if (params.Has("camera")) {
989 return CreateCameraDevice(params);
990 }
931 if (params.Has("button") && params.Has("axis")) { 991 if (params.Has("button") && params.Has("axis")) {
932 return CreateTriggerDevice(params); 992 return CreateTriggerDevice(params);
933 } 993 }
diff --git a/src/input_common/input_poller.h b/src/input_common/input_poller.h
index 6ebe0dbf5..4410a8415 100644
--- a/src/input_common/input_poller.h
+++ b/src/input_common/input_poller.h
@@ -211,6 +211,17 @@ private:
211 */ 211 */
212 std::unique_ptr<Common::Input::InputDevice> CreateMotionDevice(Common::ParamPackage params); 212 std::unique_ptr<Common::Input::InputDevice> CreateMotionDevice(Common::ParamPackage params);
213 213
214 /**
215 * Creates a camera device from the parameters given.
216 * @param params contains parameters for creating the device:
217 * - "guid": text string for identifying controllers
218 * - "port": port of the connected device
219 * - "pad": slot of the connected controller
220 * @returns a unique input device with the parameters specified
221 */
222 std::unique_ptr<Common::Input::InputDevice> CreateCameraDevice(
223 const Common::ParamPackage& params);
224
214 std::shared_ptr<InputEngine> input_engine; 225 std::shared_ptr<InputEngine> input_engine;
215}; 226};
216} // namespace InputCommon 227} // namespace InputCommon
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 21834fb6b..75a57b9fc 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -1,10 +1,10 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <memory> 4#include <memory>
6#include "common/input.h" 5#include "common/input.h"
7#include "common/param_package.h" 6#include "common/param_package.h"
7#include "input_common/drivers/camera.h"
8#include "input_common/drivers/gc_adapter.h" 8#include "input_common/drivers/gc_adapter.h"
9#include "input_common/drivers/keyboard.h" 9#include "input_common/drivers/keyboard.h"
10#include "input_common/drivers/mouse.h" 10#include "input_common/drivers/mouse.h"
@@ -78,6 +78,15 @@ struct InputSubsystem::Impl {
78 Common::Input::RegisterFactory<Common::Input::OutputDevice>(tas_input->GetEngineName(), 78 Common::Input::RegisterFactory<Common::Input::OutputDevice>(tas_input->GetEngineName(),
79 tas_output_factory); 79 tas_output_factory);
80 80
81 camera = std::make_shared<Camera>("camera");
82 camera->SetMappingCallback(mapping_callback);
83 camera_input_factory = std::make_shared<InputFactory>(camera);
84 camera_output_factory = std::make_shared<OutputFactory>(camera);
85 Common::Input::RegisterFactory<Common::Input::InputDevice>(camera->GetEngineName(),
86 camera_input_factory);
87 Common::Input::RegisterFactory<Common::Input::OutputDevice>(camera->GetEngineName(),
88 camera_output_factory);
89
81#ifdef HAVE_SDL2 90#ifdef HAVE_SDL2
82 sdl = std::make_shared<SDLDriver>("sdl"); 91 sdl = std::make_shared<SDLDriver>("sdl");
83 sdl->SetMappingCallback(mapping_callback); 92 sdl->SetMappingCallback(mapping_callback);
@@ -317,6 +326,7 @@ struct InputSubsystem::Impl {
317 std::shared_ptr<TouchScreen> touch_screen; 326 std::shared_ptr<TouchScreen> touch_screen;
318 std::shared_ptr<TasInput::Tas> tas_input; 327 std::shared_ptr<TasInput::Tas> tas_input;
319 std::shared_ptr<CemuhookUDP::UDPClient> udp_client; 328 std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
329 std::shared_ptr<Camera> camera;
320 330
321 std::shared_ptr<InputFactory> keyboard_factory; 331 std::shared_ptr<InputFactory> keyboard_factory;
322 std::shared_ptr<InputFactory> mouse_factory; 332 std::shared_ptr<InputFactory> mouse_factory;
@@ -324,12 +334,14 @@ struct InputSubsystem::Impl {
324 std::shared_ptr<InputFactory> touch_screen_factory; 334 std::shared_ptr<InputFactory> touch_screen_factory;
325 std::shared_ptr<InputFactory> udp_client_input_factory; 335 std::shared_ptr<InputFactory> udp_client_input_factory;
326 std::shared_ptr<InputFactory> tas_input_factory; 336 std::shared_ptr<InputFactory> tas_input_factory;
337 std::shared_ptr<InputFactory> camera_input_factory;
327 338
328 std::shared_ptr<OutputFactory> keyboard_output_factory; 339 std::shared_ptr<OutputFactory> keyboard_output_factory;
329 std::shared_ptr<OutputFactory> mouse_output_factory; 340 std::shared_ptr<OutputFactory> mouse_output_factory;
330 std::shared_ptr<OutputFactory> gcadapter_output_factory; 341 std::shared_ptr<OutputFactory> gcadapter_output_factory;
331 std::shared_ptr<OutputFactory> udp_client_output_factory; 342 std::shared_ptr<OutputFactory> udp_client_output_factory;
332 std::shared_ptr<OutputFactory> tas_output_factory; 343 std::shared_ptr<OutputFactory> tas_output_factory;
344 std::shared_ptr<OutputFactory> camera_output_factory;
333 345
334#ifdef HAVE_SDL2 346#ifdef HAVE_SDL2
335 std::shared_ptr<SDLDriver> sdl; 347 std::shared_ptr<SDLDriver> sdl;
@@ -382,6 +394,14 @@ const TasInput::Tas* InputSubsystem::GetTas() const {
382 return impl->tas_input.get(); 394 return impl->tas_input.get();
383} 395}
384 396
397Camera* InputSubsystem::GetCamera() {
398 return impl->camera.get();
399}
400
401const Camera* InputSubsystem::GetCamera() const {
402 return impl->camera.get();
403}
404
385std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const { 405std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
386 return impl->GetInputDevices(); 406 return impl->GetInputDevices();
387} 407}
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 147c310c4..9a969e747 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
@@ -30,6 +29,7 @@ enum Values : int;
30} 29}
31 30
32namespace InputCommon { 31namespace InputCommon {
32class Camera;
33class Keyboard; 33class Keyboard;
34class Mouse; 34class Mouse;
35class TouchScreen; 35class TouchScreen;
@@ -92,9 +92,15 @@ public:
92 /// Retrieves the underlying tas input device. 92 /// Retrieves the underlying tas input device.
93 [[nodiscard]] TasInput::Tas* GetTas(); 93 [[nodiscard]] TasInput::Tas* GetTas();
94 94
95 /// Retrieves the underlying tas input device. 95 /// Retrieves the underlying tas input device.
96 [[nodiscard]] const TasInput::Tas* GetTas() const; 96 [[nodiscard]] const TasInput::Tas* GetTas() const;
97 97
98 /// Retrieves the underlying camera input device.
99 [[nodiscard]] Camera* GetCamera();
100
101 /// Retrieves the underlying camera input device.
102 [[nodiscard]] const Camera* GetCamera() const;
103
98 /** 104 /**
99 * Returns all available input devices that this Factory can create a new device with. 105 * Returns all available input devices that this Factory can create a new device with.
100 * Each returned ParamPackage should have a `display` field used for display, a `engine` field 106 * Each returned ParamPackage should have a `display` field used for display, a `engine` field
diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt
new file mode 100644
index 000000000..312f79b68
--- /dev/null
+++ b/src/network/CMakeLists.txt
@@ -0,0 +1,19 @@
1# SPDX-FileCopyrightText: 2022 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-3.0-or-later
3
4add_library(network STATIC
5 network.cpp
6 network.h
7 packet.cpp
8 packet.h
9 room.cpp
10 room.h
11 room_member.cpp
12 room_member.h
13 verify_user.cpp
14 verify_user.h
15)
16
17create_target_directory_groups(network)
18
19target_link_libraries(network PRIVATE common enet Boost::boost)
diff --git a/src/network/network.cpp b/src/network/network.cpp
new file mode 100644
index 000000000..0841e4134
--- /dev/null
+++ b/src/network/network.cpp
@@ -0,0 +1,50 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/assert.h"
5#include "common/logging/log.h"
6#include "enet/enet.h"
7#include "network/network.h"
8
9namespace Network {
10
11RoomNetwork::RoomNetwork() {
12 m_room = std::make_shared<Room>();
13 m_room_member = std::make_shared<RoomMember>();
14}
15
16bool RoomNetwork::Init() {
17 if (enet_initialize() != 0) {
18 LOG_ERROR(Network, "Error initalizing ENet");
19 return false;
20 }
21 m_room = std::make_shared<Room>();
22 m_room_member = std::make_shared<RoomMember>();
23 LOG_DEBUG(Network, "initialized OK");
24 return true;
25}
26
27std::weak_ptr<Room> RoomNetwork::GetRoom() {
28 return m_room;
29}
30
31std::weak_ptr<RoomMember> RoomNetwork::GetRoomMember() {
32 return m_room_member;
33}
34
35void RoomNetwork::Shutdown() {
36 if (m_room_member) {
37 if (m_room_member->IsConnected())
38 m_room_member->Leave();
39 m_room_member.reset();
40 }
41 if (m_room) {
42 if (m_room->GetState() == Room::State::Open)
43 m_room->Destroy();
44 m_room.reset();
45 }
46 enet_deinitialize();
47 LOG_DEBUG(Network, "shutdown OK");
48}
49
50} // namespace Network
diff --git a/src/network/network.h b/src/network/network.h
new file mode 100644
index 000000000..e4de207b2
--- /dev/null
+++ b/src/network/network.h
@@ -0,0 +1,33 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include "network/room.h"
8#include "network/room_member.h"
9
10namespace Network {
11
12class RoomNetwork {
13public:
14 RoomNetwork();
15
16 /// Initializes and registers the network device, the room, and the room member.
17 bool Init();
18
19 /// Returns a pointer to the room handle
20 std::weak_ptr<Room> GetRoom();
21
22 /// Returns a pointer to the room member handle
23 std::weak_ptr<RoomMember> GetRoomMember();
24
25 /// Unregisters the network device, the room, and the room member and shut them down.
26 void Shutdown();
27
28private:
29 std::shared_ptr<RoomMember> m_room_member; ///< RoomMember (Client) for network games
30 std::shared_ptr<Room> m_room; ///< Room (Server) for network games
31};
32
33} // namespace Network
diff --git a/src/network/packet.cpp b/src/network/packet.cpp
new file mode 100644
index 000000000..0e22f1eb4
--- /dev/null
+++ b/src/network/packet.cpp
@@ -0,0 +1,262 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#ifdef _WIN32
5#include <winsock2.h>
6#else
7#include <arpa/inet.h>
8#endif
9#include <cstring>
10#include <string>
11#include "network/packet.h"
12
13namespace Network {
14
15#ifndef htonll
16static u64 htonll(u64 x) {
17 return ((1 == htonl(1)) ? (x) : ((uint64_t)htonl((x)&0xFFFFFFFF) << 32) | htonl((x) >> 32));
18}
19#endif
20
21#ifndef ntohll
22static u64 ntohll(u64 x) {
23 return ((1 == ntohl(1)) ? (x) : ((uint64_t)ntohl((x)&0xFFFFFFFF) << 32) | ntohl((x) >> 32));
24}
25#endif
26
27void Packet::Append(const void* in_data, std::size_t size_in_bytes) {
28 if (in_data && (size_in_bytes > 0)) {
29 std::size_t start = data.size();
30 data.resize(start + size_in_bytes);
31 std::memcpy(&data[start], in_data, size_in_bytes);
32 }
33}
34
35void Packet::Read(void* out_data, std::size_t size_in_bytes) {
36 if (out_data && CheckSize(size_in_bytes)) {
37 std::memcpy(out_data, &data[read_pos], size_in_bytes);
38 read_pos += size_in_bytes;
39 }
40}
41
42void Packet::Clear() {
43 data.clear();
44 read_pos = 0;
45 is_valid = true;
46}
47
48const void* Packet::GetData() const {
49 return !data.empty() ? &data[0] : nullptr;
50}
51
52void Packet::IgnoreBytes(u32 length) {
53 read_pos += length;
54}
55
56std::size_t Packet::GetDataSize() const {
57 return data.size();
58}
59
60bool Packet::EndOfPacket() const {
61 return read_pos >= data.size();
62}
63
64Packet::operator bool() const {
65 return is_valid;
66}
67
68Packet& Packet::Read(bool& out_data) {
69 u8 value{};
70 if (Read(value)) {
71 out_data = (value != 0);
72 }
73 return *this;
74}
75
76Packet& Packet::Read(s8& out_data) {
77 Read(&out_data, sizeof(out_data));
78 return *this;
79}
80
81Packet& Packet::Read(u8& out_data) {
82 Read(&out_data, sizeof(out_data));
83 return *this;
84}
85
86Packet& Packet::Read(s16& out_data) {
87 s16 value{};
88 Read(&value, sizeof(value));
89 out_data = ntohs(value);
90 return *this;
91}
92
93Packet& Packet::Read(u16& out_data) {
94 u16 value{};
95 Read(&value, sizeof(value));
96 out_data = ntohs(value);
97 return *this;
98}
99
100Packet& Packet::Read(s32& out_data) {
101 s32 value{};
102 Read(&value, sizeof(value));
103 out_data = ntohl(value);
104 return *this;
105}
106
107Packet& Packet::Read(u32& out_data) {
108 u32 value{};
109 Read(&value, sizeof(value));
110 out_data = ntohl(value);
111 return *this;
112}
113
114Packet& Packet::Read(s64& out_data) {
115 s64 value{};
116 Read(&value, sizeof(value));
117 out_data = ntohll(value);
118 return *this;
119}
120
121Packet& Packet::Read(u64& out_data) {
122 u64 value{};
123 Read(&value, sizeof(value));
124 out_data = ntohll(value);
125 return *this;
126}
127
128Packet& Packet::Read(float& out_data) {
129 Read(&out_data, sizeof(out_data));
130 return *this;
131}
132
133Packet& Packet::Read(double& out_data) {
134 Read(&out_data, sizeof(out_data));
135 return *this;
136}
137
138Packet& Packet::Read(char* out_data) {
139 // First extract string length
140 u32 length = 0;
141 Read(length);
142
143 if ((length > 0) && CheckSize(length)) {
144 // Then extract characters
145 std::memcpy(out_data, &data[read_pos], length);
146 out_data[length] = '\0';
147
148 // Update reading position
149 read_pos += length;
150 }
151
152 return *this;
153}
154
155Packet& Packet::Read(std::string& out_data) {
156 // First extract string length
157 u32 length = 0;
158 Read(length);
159
160 out_data.clear();
161 if ((length > 0) && CheckSize(length)) {
162 // Then extract characters
163 out_data.assign(&data[read_pos], length);
164
165 // Update reading position
166 read_pos += length;
167 }
168
169 return *this;
170}
171
172Packet& Packet::Write(bool in_data) {
173 Write(static_cast<u8>(in_data));
174 return *this;
175}
176
177Packet& Packet::Write(s8 in_data) {
178 Append(&in_data, sizeof(in_data));
179 return *this;
180}
181
182Packet& Packet::Write(u8 in_data) {
183 Append(&in_data, sizeof(in_data));
184 return *this;
185}
186
187Packet& Packet::Write(s16 in_data) {
188 s16 toWrite = htons(in_data);
189 Append(&toWrite, sizeof(toWrite));
190 return *this;
191}
192
193Packet& Packet::Write(u16 in_data) {
194 u16 toWrite = htons(in_data);
195 Append(&toWrite, sizeof(toWrite));
196 return *this;
197}
198
199Packet& Packet::Write(s32 in_data) {
200 s32 toWrite = htonl(in_data);
201 Append(&toWrite, sizeof(toWrite));
202 return *this;
203}
204
205Packet& Packet::Write(u32 in_data) {
206 u32 toWrite = htonl(in_data);
207 Append(&toWrite, sizeof(toWrite));
208 return *this;
209}
210
211Packet& Packet::Write(s64 in_data) {
212 s64 toWrite = htonll(in_data);
213 Append(&toWrite, sizeof(toWrite));
214 return *this;
215}
216
217Packet& Packet::Write(u64 in_data) {
218 u64 toWrite = htonll(in_data);
219 Append(&toWrite, sizeof(toWrite));
220 return *this;
221}
222
223Packet& Packet::Write(float in_data) {
224 Append(&in_data, sizeof(in_data));
225 return *this;
226}
227
228Packet& Packet::Write(double in_data) {
229 Append(&in_data, sizeof(in_data));
230 return *this;
231}
232
233Packet& Packet::Write(const char* in_data) {
234 // First insert string length
235 u32 length = static_cast<u32>(std::strlen(in_data));
236 Write(length);
237
238 // Then insert characters
239 Append(in_data, length * sizeof(char));
240
241 return *this;
242}
243
244Packet& Packet::Write(const std::string& in_data) {
245 // First insert string length
246 u32 length = static_cast<u32>(in_data.size());
247 Write(length);
248
249 // Then insert characters
250 if (length > 0)
251 Append(in_data.c_str(), length * sizeof(std::string::value_type));
252
253 return *this;
254}
255
256bool Packet::CheckSize(std::size_t size) {
257 is_valid = is_valid && (read_pos + size <= data.size());
258
259 return is_valid;
260}
261
262} // namespace Network
diff --git a/src/network/packet.h b/src/network/packet.h
new file mode 100644
index 000000000..e69217488
--- /dev/null
+++ b/src/network/packet.h
@@ -0,0 +1,165 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <vector>
8#include "common/common_types.h"
9
10namespace Network {
11
12/// A class that serializes data for network transfer. It also handles endianess
13class Packet {
14public:
15 Packet() = default;
16 ~Packet() = default;
17
18 /**
19 * Append data to the end of the packet
20 * @param data Pointer to the sequence of bytes to append
21 * @param size_in_bytes Number of bytes to append
22 */
23 void Append(const void* data, std::size_t size_in_bytes);
24
25 /**
26 * Reads data from the current read position of the packet
27 * @param out_data Pointer where the data should get written to
28 * @param size_in_bytes Number of bytes to read
29 */
30 void Read(void* out_data, std::size_t size_in_bytes);
31
32 /**
33 * Clear the packet
34 * After calling Clear, the packet is empty.
35 */
36 void Clear();
37
38 /**
39 * Ignores bytes while reading
40 * @param length THe number of bytes to ignore
41 */
42 void IgnoreBytes(u32 length);
43
44 /**
45 * Get a pointer to the data contained in the packet
46 * @return Pointer to the data
47 */
48 const void* GetData() const;
49
50 /**
51 * This function returns the number of bytes pointed to by
52 * what getData returns.
53 * @return Data size, in bytes
54 */
55 std::size_t GetDataSize() const;
56
57 /**
58 * This function is useful to know if there is some data
59 * left to be read, without actually reading it.
60 * @return True if all data was read, false otherwise
61 */
62 bool EndOfPacket() const;
63
64 explicit operator bool() const;
65
66 /// Overloads of read function to read data from the packet
67 Packet& Read(bool& out_data);
68 Packet& Read(s8& out_data);
69 Packet& Read(u8& out_data);
70 Packet& Read(s16& out_data);
71 Packet& Read(u16& out_data);
72 Packet& Read(s32& out_data);
73 Packet& Read(u32& out_data);
74 Packet& Read(s64& out_data);
75 Packet& Read(u64& out_data);
76 Packet& Read(float& out_data);
77 Packet& Read(double& out_data);
78 Packet& Read(char* out_data);
79 Packet& Read(std::string& out_data);
80 template <typename T>
81 Packet& Read(std::vector<T>& out_data);
82 template <typename T, std::size_t S>
83 Packet& Read(std::array<T, S>& out_data);
84
85 /// Overloads of write function to write data into the packet
86 Packet& Write(bool in_data);
87 Packet& Write(s8 in_data);
88 Packet& Write(u8 in_data);
89 Packet& Write(s16 in_data);
90 Packet& Write(u16 in_data);
91 Packet& Write(s32 in_data);
92 Packet& Write(u32 in_data);
93 Packet& Write(s64 in_data);
94 Packet& Write(u64 in_data);
95 Packet& Write(float in_data);
96 Packet& Write(double in_data);
97 Packet& Write(const char* in_data);
98 Packet& Write(const std::string& in_data);
99 template <typename T>
100 Packet& Write(const std::vector<T>& in_data);
101 template <typename T, std::size_t S>
102 Packet& Write(const std::array<T, S>& data);
103
104private:
105 /**
106 * Check if the packet can extract a given number of bytes
107 * This function updates accordingly the state of the packet.
108 * @param size Size to check
109 * @return True if size bytes can be read from the packet
110 */
111 bool CheckSize(std::size_t size);
112
113 // Member data
114 std::vector<char> data; ///< Data stored in the packet
115 std::size_t read_pos = 0; ///< Current reading position in the packet
116 bool is_valid = true; ///< Reading state of the packet
117};
118
119template <typename T>
120Packet& Packet::Read(std::vector<T>& out_data) {
121 // First extract the size
122 u32 size = 0;
123 Read(size);
124 out_data.resize(size);
125
126 // Then extract the data
127 for (std::size_t i = 0; i < out_data.size(); ++i) {
128 T character;
129 Read(character);
130 out_data[i] = character;
131 }
132 return *this;
133}
134
135template <typename T, std::size_t S>
136Packet& Packet::Read(std::array<T, S>& out_data) {
137 for (std::size_t i = 0; i < out_data.size(); ++i) {
138 T character;
139 Read(character);
140 out_data[i] = character;
141 }
142 return *this;
143}
144
145template <typename T>
146Packet& Packet::Write(const std::vector<T>& in_data) {
147 // First insert the size
148 Write(static_cast<u32>(in_data.size()));
149
150 // Then insert the data
151 for (std::size_t i = 0; i < in_data.size(); ++i) {
152 Write(in_data[i]);
153 }
154 return *this;
155}
156
157template <typename T, std::size_t S>
158Packet& Packet::Write(const std::array<T, S>& in_data) {
159 for (std::size_t i = 0; i < in_data.size(); ++i) {
160 Write(in_data[i]);
161 }
162 return *this;
163}
164
165} // namespace Network
diff --git a/src/network/room.cpp b/src/network/room.cpp
new file mode 100644
index 000000000..3fc3a0383
--- /dev/null
+++ b/src/network/room.cpp
@@ -0,0 +1,1110 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <atomic>
6#include <iomanip>
7#include <mutex>
8#include <random>
9#include <regex>
10#include <shared_mutex>
11#include <sstream>
12#include <thread>
13#include "common/logging/log.h"
14#include "enet/enet.h"
15#include "network/packet.h"
16#include "network/room.h"
17#include "network/verify_user.h"
18
19namespace Network {
20
21class Room::RoomImpl {
22public:
23 // This MAC address is used to generate a 'Nintendo' like Mac address.
24 const MacAddress NintendoOUI;
25 std::mt19937 random_gen; ///< Random number generator. Used for GenerateMacAddress
26
27 ENetHost* server = nullptr; ///< Network interface.
28
29 std::atomic<State> state{State::Closed}; ///< Current state of the room.
30 RoomInformation room_information; ///< Information about this room.
31
32 std::string verify_uid; ///< A GUID which may be used for verfication.
33 mutable std::mutex verify_uid_mutex; ///< Mutex for verify_uid
34
35 std::string password; ///< The password required to connect to this room.
36
37 struct Member {
38 std::string nickname; ///< The nickname of the member.
39 std::string console_id_hash; ///< A hash of the console ID of the member.
40 GameInfo game_info; ///< The current game of the member
41 MacAddress mac_address; ///< The assigned mac address of the member.
42 /// Data of the user, often including authenticated forum username.
43 VerifyUser::UserData user_data;
44 ENetPeer* peer; ///< The remote peer.
45 };
46 using MemberList = std::vector<Member>;
47 MemberList members; ///< Information about the members of this room
48 mutable std::shared_mutex member_mutex; ///< Mutex for locking the members list
49
50 UsernameBanList username_ban_list; ///< List of banned usernames
51 IPBanList ip_ban_list; ///< List of banned IP addresses
52 mutable std::mutex ban_list_mutex; ///< Mutex for the ban lists
53
54 RoomImpl()
55 : NintendoOUI{0x00, 0x1F, 0x32, 0x00, 0x00, 0x00}, random_gen(std::random_device()()) {}
56
57 /// Thread that receives and dispatches network packets
58 std::unique_ptr<std::thread> room_thread;
59
60 /// Verification backend of the room
61 std::unique_ptr<VerifyUser::Backend> verify_backend;
62
63 /// Thread function that will receive and dispatch messages until the room is destroyed.
64 void ServerLoop();
65 void StartLoop();
66
67 /**
68 * Parses and answers a room join request from a client.
69 * Validates the uniqueness of the username and assigns the MAC address
70 * that the client will use for the remainder of the connection.
71 */
72 void HandleJoinRequest(const ENetEvent* event);
73
74 /**
75 * Parses and answers a kick request from a client.
76 * Validates the permissions and that the given user exists and then kicks the member.
77 */
78 void HandleModKickPacket(const ENetEvent* event);
79
80 /**
81 * Parses and answers a ban request from a client.
82 * Validates the permissions and bans the user (by forum username or IP).
83 */
84 void HandleModBanPacket(const ENetEvent* event);
85
86 /**
87 * Parses and answers a unban request from a client.
88 * Validates the permissions and unbans the address.
89 */
90 void HandleModUnbanPacket(const ENetEvent* event);
91
92 /**
93 * Parses and answers a get ban list request from a client.
94 * Validates the permissions and returns the ban list.
95 */
96 void HandleModGetBanListPacket(const ENetEvent* event);
97
98 /**
99 * Returns whether the nickname is valid, ie. isn't already taken by someone else in the room.
100 */
101 bool IsValidNickname(const std::string& nickname) const;
102
103 /**
104 * Returns whether the MAC address is valid, ie. isn't already taken by someone else in the
105 * room.
106 */
107 bool IsValidMacAddress(const MacAddress& 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
115 /**
116 * Returns whether a user has mod permissions.
117 */
118 bool HasModPermission(const ENetPeer* client) const;
119
120 /**
121 * Sends a ID_ROOM_IS_FULL message telling the client that the room is full.
122 */
123 void SendRoomIsFull(ENetPeer* client);
124
125 /**
126 * Sends a ID_ROOM_NAME_COLLISION message telling the client that the name is invalid.
127 */
128 void SendNameCollision(ENetPeer* client);
129
130 /**
131 * Sends a ID_ROOM_MAC_COLLISION message telling the client that the MAC is invalid.
132 */
133 void SendMacCollision(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
141 /**
142 * Sends a ID_ROOM_VERSION_MISMATCH message telling the client that the version is invalid.
143 */
144 void SendVersionMismatch(ENetPeer* client);
145
146 /**
147 * Sends a ID_ROOM_WRONG_PASSWORD message telling the client that the password is wrong.
148 */
149 void SendWrongPassword(ENetPeer* client);
150
151 /**
152 * Notifies the member that its connection attempt was successful,
153 * and it is now part of the room.
154 */
155 void SendJoinSuccess(ENetPeer* client, MacAddress mac_address);
156
157 /**
158 * 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.
160 */
161 void SendJoinSuccessAsMod(ENetPeer* client, MacAddress mac_address);
162
163 /**
164 * Sends a IdHostKicked message telling the client that they have been kicked.
165 */
166 void SendUserKicked(ENetPeer* client);
167
168 /**
169 * Sends a IdHostBanned message telling the client that they have been banned.
170 */
171 void SendUserBanned(ENetPeer* client);
172
173 /**
174 * Sends a IdModPermissionDenied message telling the client that they do not have mod
175 * permission.
176 */
177 void SendModPermissionDenied(ENetPeer* client);
178
179 /**
180 * Sends a IdModNoSuchUser message telling the client that the given user could not be found.
181 */
182 void SendModNoSuchUser(ENetPeer* client);
183
184 /**
185 * Sends the ban list in response to a client's request for getting ban list.
186 */
187 void SendModBanListResponse(ENetPeer* client);
188
189 /**
190 * Notifies the members that the room is closed,
191 */
192 void SendCloseMessage();
193
194 /**
195 * Sends a system message to all the connected clients.
196 */
197 void SendStatusMessage(StatusMessageTypes type, const std::string& nickname,
198 const std::string& username, const std::string& ip);
199
200 /**
201 * Sends the information about the room, along with the list of members
202 * to every connected client in the room.
203 * The packet has the structure:
204 * <MessageID>ID_ROOM_INFORMATION
205 * <String> room_name
206 * <String> room_description
207 * <u32> member_slots: The max number of clients allowed in this room
208 * <String> uid
209 * <u16> port
210 * <u32> num_members: the number of currently joined clients
211 * This is followed by the following three values for each member:
212 * <String> nickname of that member
213 * <MacAddress> mac_address of that member
214 * <String> game_name of that member
215 */
216 void BroadcastRoomInformation();
217
218 /**
219 * Generates a free MAC address to assign to a new client.
220 * The first 3 bytes are the NintendoOUI 0x00, 0x1F, 0x32
221 */
222 MacAddress GenerateMacAddress();
223
224 /**
225 * Broadcasts this packet to all members except the sender.
226 * @param event The ENet event containing the data
227 */
228 void HandleWifiPacket(const ENetEvent* event);
229
230 /**
231 * Extracts a chat entry from a received ENet packet and adds it to the chat queue.
232 * @param event The ENet event that was received.
233 */
234 void HandleChatPacket(const ENetEvent* event);
235
236 /**
237 * Extracts the game name from a received ENet packet and broadcasts it.
238 * @param event The ENet event that was received.
239 */
240 void HandleGameNamePacket(const ENetEvent* event);
241
242 /**
243 * Removes the client from the members list if it was in it and announces the change
244 * to all other clients.
245 */
246 void HandleClientDisconnection(ENetPeer* client);
247};
248
249// RoomImpl
250void Room::RoomImpl::ServerLoop() {
251 while (state != State::Closed) {
252 ENetEvent event;
253 if (enet_host_service(server, &event, 16) > 0) {
254 switch (event.type) {
255 case ENET_EVENT_TYPE_RECEIVE:
256 switch (event.packet->data[0]) {
257 case IdJoinRequest:
258 HandleJoinRequest(&event);
259 break;
260 case IdSetGameInfo:
261 HandleGameNamePacket(&event);
262 break;
263 case IdWifiPacket:
264 HandleWifiPacket(&event);
265 break;
266 case IdChatMessage:
267 HandleChatPacket(&event);
268 break;
269 // Moderation
270 case IdModKick:
271 HandleModKickPacket(&event);
272 break;
273 case IdModBan:
274 HandleModBanPacket(&event);
275 break;
276 case IdModUnban:
277 HandleModUnbanPacket(&event);
278 break;
279 case IdModGetBanList:
280 HandleModGetBanListPacket(&event);
281 break;
282 }
283 enet_packet_destroy(event.packet);
284 break;
285 case ENET_EVENT_TYPE_DISCONNECT:
286 HandleClientDisconnection(event.peer);
287 break;
288 case ENET_EVENT_TYPE_NONE:
289 case ENET_EVENT_TYPE_CONNECT:
290 break;
291 }
292 }
293 }
294 // Close the connection to all members:
295 SendCloseMessage();
296}
297
298void Room::RoomImpl::StartLoop() {
299 room_thread = std::make_unique<std::thread>(&Room::RoomImpl::ServerLoop, this);
300}
301
302void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
303 {
304 std::lock_guard lock(member_mutex);
305 if (members.size() >= room_information.member_slots) {
306 SendRoomIsFull(event->peer);
307 return;
308 }
309 }
310 Packet packet;
311 packet.Append(event->packet->data, event->packet->dataLength);
312 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
313 std::string nickname;
314 packet.Read(nickname);
315
316 std::string console_id_hash;
317 packet.Read(console_id_hash);
318
319 MacAddress preferred_mac;
320 packet.Read(preferred_mac);
321
322 u32 client_version;
323 packet.Read(client_version);
324
325 std::string pass;
326 packet.Read(pass);
327
328 std::string token;
329 packet.Read(token);
330
331 if (pass != password) {
332 SendWrongPassword(event->peer);
333 return;
334 }
335
336 if (!IsValidNickname(nickname)) {
337 SendNameCollision(event->peer);
338 return;
339 }
340
341 if (preferred_mac != NoPreferredMac) {
342 // Verify if the preferred mac is available
343 if (!IsValidMacAddress(preferred_mac)) {
344 SendMacCollision(event->peer);
345 return;
346 }
347 } else {
348 // Assign a MAC address of this client automatically
349 preferred_mac = GenerateMacAddress();
350 }
351
352 if (!IsValidConsoleId(console_id_hash)) {
353 SendConsoleIdCollision(event->peer);
354 return;
355 }
356
357 if (client_version != network_version) {
358 SendVersionMismatch(event->peer);
359 return;
360 }
361
362 // At this point the client is ready to be added to the room.
363 Member member{};
364 member.mac_address = preferred_mac;
365 member.console_id_hash = console_id_hash;
366 member.nickname = nickname;
367 member.peer = event->peer;
368
369 std::string uid;
370 {
371 std::lock_guard lock(verify_uid_mutex);
372 uid = verify_uid;
373 }
374 member.user_data = verify_backend->LoadUserData(uid, token);
375
376 std::string ip;
377 {
378 std::lock_guard lock(ban_list_mutex);
379
380 // Check username ban
381 if (!member.user_data.username.empty() &&
382 std::find(username_ban_list.begin(), username_ban_list.end(),
383 member.user_data.username) != username_ban_list.end()) {
384
385 SendUserBanned(event->peer);
386 return;
387 }
388
389 // Check IP ban
390 std::array<char, 256> ip_raw{};
391 enet_address_get_host_ip(&event->peer->address, ip_raw.data(), sizeof(ip_raw) - 1);
392 ip = ip_raw.data();
393
394 if (std::find(ip_ban_list.begin(), ip_ban_list.end(), ip) != ip_ban_list.end()) {
395 SendUserBanned(event->peer);
396 return;
397 }
398 }
399
400 // Notify everyone that the user has joined.
401 SendStatusMessage(IdMemberJoin, member.nickname, member.user_data.username, ip);
402
403 {
404 std::lock_guard lock(member_mutex);
405 members.push_back(std::move(member));
406 }
407
408 // Notify everyone that the room information has changed.
409 BroadcastRoomInformation();
410 if (HasModPermission(event->peer)) {
411 SendJoinSuccessAsMod(event->peer, preferred_mac);
412 } else {
413 SendJoinSuccess(event->peer, preferred_mac);
414 }
415}
416
417void Room::RoomImpl::HandleModKickPacket(const ENetEvent* event) {
418 if (!HasModPermission(event->peer)) {
419 SendModPermissionDenied(event->peer);
420 return;
421 }
422
423 Packet packet;
424 packet.Append(event->packet->data, event->packet->dataLength);
425 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
426
427 std::string nickname;
428 packet.Read(nickname);
429
430 std::string username, ip;
431 {
432 std::lock_guard lock(member_mutex);
433 const auto target_member =
434 std::find_if(members.begin(), members.end(),
435 [&nickname](const auto& member) { return member.nickname == nickname; });
436 if (target_member == members.end()) {
437 SendModNoSuchUser(event->peer);
438 return;
439 }
440
441 // Notify the kicked member
442 SendUserKicked(target_member->peer);
443
444 username = target_member->user_data.username;
445
446 std::array<char, 256> ip_raw{};
447 enet_address_get_host_ip(&target_member->peer->address, ip_raw.data(), sizeof(ip_raw) - 1);
448 ip = ip_raw.data();
449
450 enet_peer_disconnect(target_member->peer, 0);
451 members.erase(target_member);
452 }
453
454 // Announce the change to all clients.
455 SendStatusMessage(IdMemberKicked, nickname, username, ip);
456 BroadcastRoomInformation();
457}
458
459void Room::RoomImpl::HandleModBanPacket(const ENetEvent* event) {
460 if (!HasModPermission(event->peer)) {
461 SendModPermissionDenied(event->peer);
462 return;
463 }
464
465 Packet packet;
466 packet.Append(event->packet->data, event->packet->dataLength);
467 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
468
469 std::string nickname;
470 packet.Read(nickname);
471
472 std::string username, ip;
473 {
474 std::lock_guard lock(member_mutex);
475 const auto target_member =
476 std::find_if(members.begin(), members.end(),
477 [&nickname](const auto& member) { return member.nickname == nickname; });
478 if (target_member == members.end()) {
479 SendModNoSuchUser(event->peer);
480 return;
481 }
482
483 // Notify the banned member
484 SendUserBanned(target_member->peer);
485
486 nickname = target_member->nickname;
487 username = target_member->user_data.username;
488
489 std::array<char, 256> ip_raw{};
490 enet_address_get_host_ip(&target_member->peer->address, ip_raw.data(), sizeof(ip_raw) - 1);
491 ip = ip_raw.data();
492
493 enet_peer_disconnect(target_member->peer, 0);
494 members.erase(target_member);
495 }
496
497 {
498 std::lock_guard lock(ban_list_mutex);
499
500 if (!username.empty()) {
501 // Ban the forum username
502 if (std::find(username_ban_list.begin(), username_ban_list.end(), username) ==
503 username_ban_list.end()) {
504
505 username_ban_list.emplace_back(username);
506 }
507 }
508
509 // Ban the member's IP as well
510 if (std::find(ip_ban_list.begin(), ip_ban_list.end(), ip) == ip_ban_list.end()) {
511 ip_ban_list.emplace_back(ip);
512 }
513 }
514
515 // Announce the change to all clients.
516 SendStatusMessage(IdMemberBanned, nickname, username, ip);
517 BroadcastRoomInformation();
518}
519
520void Room::RoomImpl::HandleModUnbanPacket(const ENetEvent* event) {
521 if (!HasModPermission(event->peer)) {
522 SendModPermissionDenied(event->peer);
523 return;
524 }
525
526 Packet packet;
527 packet.Append(event->packet->data, event->packet->dataLength);
528 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
529
530 std::string address;
531 packet.Read(address);
532
533 bool unbanned = false;
534 {
535 std::lock_guard lock(ban_list_mutex);
536
537 auto it = std::find(username_ban_list.begin(), username_ban_list.end(), address);
538 if (it != username_ban_list.end()) {
539 unbanned = true;
540 username_ban_list.erase(it);
541 }
542
543 it = std::find(ip_ban_list.begin(), ip_ban_list.end(), address);
544 if (it != ip_ban_list.end()) {
545 unbanned = true;
546 ip_ban_list.erase(it);
547 }
548 }
549
550 if (unbanned) {
551 SendStatusMessage(IdAddressUnbanned, address, "", "");
552 } else {
553 SendModNoSuchUser(event->peer);
554 }
555}
556
557void Room::RoomImpl::HandleModGetBanListPacket(const ENetEvent* event) {
558 if (!HasModPermission(event->peer)) {
559 SendModPermissionDenied(event->peer);
560 return;
561 }
562
563 SendModBanListResponse(event->peer);
564}
565
566bool Room::RoomImpl::IsValidNickname(const std::string& nickname) const {
567 // A nickname is valid if it matches the regex and is not already taken by anybody else in the
568 // room.
569 const std::regex nickname_regex("^[ a-zA-Z0-9._-]{4,20}$");
570 if (!std::regex_match(nickname, nickname_regex))
571 return false;
572
573 std::lock_guard lock(member_mutex);
574 return std::all_of(members.begin(), members.end(),
575 [&nickname](const auto& member) { return member.nickname != nickname; });
576}
577
578bool Room::RoomImpl::IsValidMacAddress(const MacAddress& address) const {
579 // A MAC address is valid if it is not already taken by anybody else in the room.
580 std::lock_guard lock(member_mutex);
581 return std::all_of(members.begin(), members.end(),
582 [&address](const auto& member) { return member.mac_address != 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}
592
593bool Room::RoomImpl::HasModPermission(const ENetPeer* client) const {
594 std::lock_guard lock(member_mutex);
595 const auto sending_member =
596 std::find_if(members.begin(), members.end(),
597 [client](const auto& member) { return member.peer == client; });
598 if (sending_member == members.end()) {
599 return false;
600 }
601 if (room_information.enable_yuzu_mods &&
602 sending_member->user_data.moderator) { // Community moderator
603
604 return true;
605 }
606 if (!room_information.host_username.empty() &&
607 sending_member->user_data.username == room_information.host_username) { // Room host
608
609 return true;
610 }
611 return false;
612}
613
614void Room::RoomImpl::SendNameCollision(ENetPeer* client) {
615 Packet packet;
616 packet.Write(static_cast<u8>(IdNameCollision));
617
618 ENetPacket* enet_packet =
619 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
620 enet_peer_send(client, 0, enet_packet);
621 enet_host_flush(server);
622}
623
624void Room::RoomImpl::SendMacCollision(ENetPeer* client) {
625 Packet packet;
626 packet.Write(static_cast<u8>(IdMacCollision));
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
638 ENetPacket* enet_packet =
639 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
640 enet_peer_send(client, 0, enet_packet);
641 enet_host_flush(server);
642}
643
644void Room::RoomImpl::SendWrongPassword(ENetPeer* client) {
645 Packet packet;
646 packet.Write(static_cast<u8>(IdWrongPassword));
647
648 ENetPacket* enet_packet =
649 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
650 enet_peer_send(client, 0, enet_packet);
651 enet_host_flush(server);
652}
653
654void Room::RoomImpl::SendRoomIsFull(ENetPeer* client) {
655 Packet packet;
656 packet.Write(static_cast<u8>(IdRoomIsFull));
657
658 ENetPacket* enet_packet =
659 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
660 enet_peer_send(client, 0, enet_packet);
661 enet_host_flush(server);
662}
663
664void Room::RoomImpl::SendVersionMismatch(ENetPeer* client) {
665 Packet packet;
666 packet.Write(static_cast<u8>(IdVersionMismatch));
667 packet.Write(network_version);
668
669 ENetPacket* enet_packet =
670 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
671 enet_peer_send(client, 0, enet_packet);
672 enet_host_flush(server);
673}
674
675void Room::RoomImpl::SendJoinSuccess(ENetPeer* client, MacAddress mac_address) {
676 Packet packet;
677 packet.Write(static_cast<u8>(IdJoinSuccess));
678 packet.Write(mac_address);
679 ENetPacket* enet_packet =
680 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
681 enet_peer_send(client, 0, enet_packet);
682 enet_host_flush(server);
683}
684
685void Room::RoomImpl::SendJoinSuccessAsMod(ENetPeer* client, MacAddress mac_address) {
686 Packet packet;
687 packet.Write(static_cast<u8>(IdJoinSuccessAsMod));
688 packet.Write(mac_address);
689 ENetPacket* enet_packet =
690 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
691 enet_peer_send(client, 0, enet_packet);
692 enet_host_flush(server);
693}
694
695void Room::RoomImpl::SendUserKicked(ENetPeer* client) {
696 Packet packet;
697 packet.Write(static_cast<u8>(IdHostKicked));
698
699 ENetPacket* enet_packet =
700 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
701 enet_peer_send(client, 0, enet_packet);
702 enet_host_flush(server);
703}
704
705void Room::RoomImpl::SendUserBanned(ENetPeer* client) {
706 Packet packet;
707 packet.Write(static_cast<u8>(IdHostBanned));
708
709 ENetPacket* enet_packet =
710 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
711 enet_peer_send(client, 0, enet_packet);
712 enet_host_flush(server);
713}
714
715void Room::RoomImpl::SendModPermissionDenied(ENetPeer* client) {
716 Packet packet;
717 packet.Write(static_cast<u8>(IdModPermissionDenied));
718
719 ENetPacket* enet_packet =
720 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
721 enet_peer_send(client, 0, enet_packet);
722 enet_host_flush(server);
723}
724
725void Room::RoomImpl::SendModNoSuchUser(ENetPeer* client) {
726 Packet packet;
727 packet.Write(static_cast<u8>(IdModNoSuchUser));
728
729 ENetPacket* enet_packet =
730 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
731 enet_peer_send(client, 0, enet_packet);
732 enet_host_flush(server);
733}
734
735void Room::RoomImpl::SendModBanListResponse(ENetPeer* client) {
736 Packet packet;
737 packet.Write(static_cast<u8>(IdModBanListResponse));
738 {
739 std::lock_guard lock(ban_list_mutex);
740 packet.Write(username_ban_list);
741 packet.Write(ip_ban_list);
742 }
743
744 ENetPacket* enet_packet =
745 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
746 enet_peer_send(client, 0, enet_packet);
747 enet_host_flush(server);
748}
749
750void Room::RoomImpl::SendCloseMessage() {
751 Packet packet;
752 packet.Write(static_cast<u8>(IdCloseRoom));
753 std::lock_guard lock(member_mutex);
754 if (!members.empty()) {
755 ENetPacket* enet_packet =
756 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
757 for (auto& member : members) {
758 enet_peer_send(member.peer, 0, enet_packet);
759 }
760 }
761 enet_host_flush(server);
762 for (auto& member : members) {
763 enet_peer_disconnect(member.peer, 0);
764 }
765}
766
767void Room::RoomImpl::SendStatusMessage(StatusMessageTypes type, const std::string& nickname,
768 const std::string& username, const std::string& ip) {
769 Packet packet;
770 packet.Write(static_cast<u8>(IdStatusMessage));
771 packet.Write(static_cast<u8>(type));
772 packet.Write(nickname);
773 packet.Write(username);
774 std::lock_guard lock(member_mutex);
775 if (!members.empty()) {
776 ENetPacket* enet_packet =
777 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
778 for (auto& member : members) {
779 enet_peer_send(member.peer, 0, enet_packet);
780 }
781 }
782 enet_host_flush(server);
783
784 const std::string display_name =
785 username.empty() ? nickname : fmt::format("{} ({})", nickname, username);
786
787 switch (type) {
788 case IdMemberJoin:
789 LOG_INFO(Network, "[{}] {} has joined.", ip, display_name);
790 break;
791 case IdMemberLeave:
792 LOG_INFO(Network, "[{}] {} has left.", ip, display_name);
793 break;
794 case IdMemberKicked:
795 LOG_INFO(Network, "[{}] {} has been kicked.", ip, display_name);
796 break;
797 case IdMemberBanned:
798 LOG_INFO(Network, "[{}] {} has been banned.", ip, display_name);
799 break;
800 case IdAddressUnbanned:
801 LOG_INFO(Network, "{} has been unbanned.", display_name);
802 break;
803 }
804}
805
806void Room::RoomImpl::BroadcastRoomInformation() {
807 Packet packet;
808 packet.Write(static_cast<u8>(IdRoomInformation));
809 packet.Write(room_information.name);
810 packet.Write(room_information.description);
811 packet.Write(room_information.member_slots);
812 packet.Write(room_information.port);
813 packet.Write(room_information.preferred_game.name);
814 packet.Write(room_information.host_username);
815
816 packet.Write(static_cast<u32>(members.size()));
817 {
818 std::lock_guard lock(member_mutex);
819 for (const auto& member : members) {
820 packet.Write(member.nickname);
821 packet.Write(member.mac_address);
822 packet.Write(member.game_info.name);
823 packet.Write(member.game_info.id);
824 packet.Write(member.user_data.username);
825 packet.Write(member.user_data.display_name);
826 packet.Write(member.user_data.avatar_url);
827 }
828 }
829
830 ENetPacket* enet_packet =
831 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
832 enet_host_broadcast(server, 0, enet_packet);
833 enet_host_flush(server);
834}
835
836MacAddress Room::RoomImpl::GenerateMacAddress() {
837 MacAddress result_mac =
838 NintendoOUI; // The first three bytes of each MAC address will be the NintendoOUI
839 std::uniform_int_distribution<> dis(0x00, 0xFF); // Random byte between 0 and 0xFF
840 do {
841 for (std::size_t i = 3; i < result_mac.size(); ++i) {
842 result_mac[i] = dis(random_gen);
843 }
844 } while (!IsValidMacAddress(result_mac));
845 return result_mac;
846}
847
848void Room::RoomImpl::HandleWifiPacket(const ENetEvent* event) {
849 Packet in_packet;
850 in_packet.Append(event->packet->data, event->packet->dataLength);
851 in_packet.IgnoreBytes(sizeof(u8)); // Message type
852 in_packet.IgnoreBytes(sizeof(u8)); // WifiPacket Type
853 in_packet.IgnoreBytes(sizeof(u8)); // WifiPacket Channel
854 in_packet.IgnoreBytes(sizeof(MacAddress)); // WifiPacket Transmitter Address
855 MacAddress destination_address;
856 in_packet.Read(destination_address);
857
858 Packet out_packet;
859 out_packet.Append(event->packet->data, event->packet->dataLength);
860 ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(),
861 ENET_PACKET_FLAG_RELIABLE);
862
863 if (destination_address == BroadcastMac) { // Send the data to everyone except the sender
864 std::lock_guard lock(member_mutex);
865 bool sent_packet = false;
866 for (const auto& member : members) {
867 if (member.peer != event->peer) {
868 sent_packet = true;
869 enet_peer_send(member.peer, 0, enet_packet);
870 }
871 }
872
873 if (!sent_packet) {
874 enet_packet_destroy(enet_packet);
875 }
876 } else { // Send the data only to the destination client
877 std::lock_guard lock(member_mutex);
878 auto member = std::find_if(members.begin(), members.end(),
879 [destination_address](const Member& member_entry) -> bool {
880 return member_entry.mac_address == destination_address;
881 });
882 if (member != members.end()) {
883 enet_peer_send(member->peer, 0, enet_packet);
884 } else {
885 LOG_ERROR(Network,
886 "Attempting to send to unknown MAC address: "
887 "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
888 destination_address[0], destination_address[1], destination_address[2],
889 destination_address[3], destination_address[4], destination_address[5]);
890 enet_packet_destroy(enet_packet);
891 }
892 }
893 enet_host_flush(server);
894}
895
896void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) {
897 Packet in_packet;
898 in_packet.Append(event->packet->data, event->packet->dataLength);
899
900 in_packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
901 std::string message;
902 in_packet.Read(message);
903 auto CompareNetworkAddress = [event](const Member member) -> bool {
904 return member.peer == event->peer;
905 };
906
907 std::lock_guard lock(member_mutex);
908 const auto sending_member = std::find_if(members.begin(), members.end(), CompareNetworkAddress);
909 if (sending_member == members.end()) {
910 return; // Received a chat message from a unknown sender
911 }
912
913 // Limit the size of chat messages to MaxMessageSize
914 message.resize(std::min(static_cast<u32>(message.size()), MaxMessageSize));
915
916 Packet out_packet;
917 out_packet.Write(static_cast<u8>(IdChatMessage));
918 out_packet.Write(sending_member->nickname);
919 out_packet.Write(sending_member->user_data.username);
920 out_packet.Write(message);
921
922 ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(),
923 ENET_PACKET_FLAG_RELIABLE);
924 bool sent_packet = false;
925 for (const auto& member : members) {
926 if (member.peer != event->peer) {
927 sent_packet = true;
928 enet_peer_send(member.peer, 0, enet_packet);
929 }
930 }
931
932 if (!sent_packet) {
933 enet_packet_destroy(enet_packet);
934 }
935
936 enet_host_flush(server);
937
938 if (sending_member->user_data.username.empty()) {
939 LOG_INFO(Network, "{}: {}", sending_member->nickname, message);
940 } else {
941 LOG_INFO(Network, "{} ({}): {}", sending_member->nickname,
942 sending_member->user_data.username, message);
943 }
944}
945
946void Room::RoomImpl::HandleGameNamePacket(const ENetEvent* event) {
947 Packet in_packet;
948 in_packet.Append(event->packet->data, event->packet->dataLength);
949
950 in_packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
951 GameInfo game_info;
952 in_packet.Read(game_info.name);
953 in_packet.Read(game_info.id);
954
955 {
956 std::lock_guard lock(member_mutex);
957 auto member = std::find_if(members.begin(), members.end(),
958 [event](const Member& member_entry) -> bool {
959 return member_entry.peer == event->peer;
960 });
961 if (member != members.end()) {
962 member->game_info = game_info;
963
964 const std::string display_name =
965 member->user_data.username.empty()
966 ? member->nickname
967 : fmt::format("{} ({})", member->nickname, member->user_data.username);
968
969 if (game_info.name.empty()) {
970 LOG_INFO(Network, "{} is not playing", display_name);
971 } else {
972 LOG_INFO(Network, "{} is playing {}", display_name, game_info.name);
973 }
974 }
975 }
976 BroadcastRoomInformation();
977}
978
979void Room::RoomImpl::HandleClientDisconnection(ENetPeer* client) {
980 // Remove the client from the members list.
981 std::string nickname, username, ip;
982 {
983 std::lock_guard lock(member_mutex);
984 auto member =
985 std::find_if(members.begin(), members.end(), [client](const Member& member_entry) {
986 return member_entry.peer == client;
987 });
988 if (member != members.end()) {
989 nickname = member->nickname;
990 username = member->user_data.username;
991
992 std::array<char, 256> ip_raw{};
993 enet_address_get_host_ip(&member->peer->address, ip_raw.data(), sizeof(ip_raw) - 1);
994 ip = ip_raw.data();
995
996 members.erase(member);
997 }
998 }
999
1000 // Announce the change to all clients.
1001 enet_peer_disconnect(client, 0);
1002 if (!nickname.empty())
1003 SendStatusMessage(IdMemberLeave, nickname, username, ip);
1004 BroadcastRoomInformation();
1005}
1006
1007// Room
1008Room::Room() : room_impl{std::make_unique<RoomImpl>()} {}
1009
1010Room::~Room() = default;
1011
1012bool Room::Create(const std::string& name, const std::string& description,
1013 const std::string& server_address, u16 server_port, const std::string& password,
1014 const u32 max_connections, const std::string& host_username,
1015 const GameInfo preferred_game,
1016 std::unique_ptr<VerifyUser::Backend> verify_backend,
1017 const Room::BanList& ban_list, bool enable_yuzu_mods) {
1018 ENetAddress address;
1019 address.host = ENET_HOST_ANY;
1020 if (!server_address.empty()) {
1021 enet_address_set_host(&address, server_address.c_str());
1022 }
1023 address.port = server_port;
1024
1025 // In order to send the room is full message to the connecting client, we need to leave one
1026 // slot open so enet won't reject the incoming connection without telling us
1027 room_impl->server = enet_host_create(&address, max_connections + 1, NumChannels, 0, 0);
1028 if (!room_impl->server) {
1029 return false;
1030 }
1031 room_impl->state = State::Open;
1032
1033 room_impl->room_information.name = name;
1034 room_impl->room_information.description = description;
1035 room_impl->room_information.member_slots = max_connections;
1036 room_impl->room_information.port = server_port;
1037 room_impl->room_information.preferred_game = preferred_game;
1038 room_impl->room_information.host_username = host_username;
1039 room_impl->room_information.enable_yuzu_mods = enable_yuzu_mods;
1040 room_impl->password = password;
1041 room_impl->verify_backend = std::move(verify_backend);
1042 room_impl->username_ban_list = ban_list.first;
1043 room_impl->ip_ban_list = ban_list.second;
1044
1045 room_impl->StartLoop();
1046 return true;
1047}
1048
1049Room::State Room::GetState() const {
1050 return room_impl->state;
1051}
1052
1053const RoomInformation& Room::GetRoomInformation() const {
1054 return room_impl->room_information;
1055}
1056
1057std::string Room::GetVerifyUID() const {
1058 std::lock_guard lock(room_impl->verify_uid_mutex);
1059 return room_impl->verify_uid;
1060}
1061
1062Room::BanList Room::GetBanList() const {
1063 std::lock_guard lock(room_impl->ban_list_mutex);
1064 return {room_impl->username_ban_list, room_impl->ip_ban_list};
1065}
1066
1067std::vector<Member> Room::GetRoomMemberList() const {
1068 std::vector<Member> member_list;
1069 std::lock_guard lock(room_impl->member_mutex);
1070 for (const auto& member_impl : room_impl->members) {
1071 Member member;
1072 member.nickname = member_impl.nickname;
1073 member.username = member_impl.user_data.username;
1074 member.display_name = member_impl.user_data.display_name;
1075 member.avatar_url = member_impl.user_data.avatar_url;
1076 member.mac_address = member_impl.mac_address;
1077 member.game = member_impl.game_info;
1078 member_list.push_back(member);
1079 }
1080 return member_list;
1081}
1082
1083bool Room::HasPassword() const {
1084 return !room_impl->password.empty();
1085}
1086
1087void Room::SetVerifyUID(const std::string& uid) {
1088 std::lock_guard lock(room_impl->verify_uid_mutex);
1089 room_impl->verify_uid = uid;
1090}
1091
1092void Room::Destroy() {
1093 room_impl->state = State::Closed;
1094 room_impl->room_thread->join();
1095 room_impl->room_thread.reset();
1096
1097 if (room_impl->server) {
1098 enet_host_destroy(room_impl->server);
1099 }
1100 room_impl->room_information = {};
1101 room_impl->server = nullptr;
1102 {
1103 std::lock_guard lock(room_impl->member_mutex);
1104 room_impl->members.clear();
1105 }
1106 room_impl->room_information.member_slots = 0;
1107 room_impl->room_information.name.clear();
1108}
1109
1110} // namespace Network
diff --git a/src/network/room.h b/src/network/room.h
new file mode 100644
index 000000000..6f7e3b5b5
--- /dev/null
+++ b/src/network/room.h
@@ -0,0 +1,151 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <memory>
8#include <string>
9#include <vector>
10#include "common/announce_multiplayer_room.h"
11#include "common/common_types.h"
12#include "network/verify_user.h"
13
14namespace Network {
15
16using AnnounceMultiplayerRoom::GameInfo;
17using AnnounceMultiplayerRoom::MacAddress;
18using AnnounceMultiplayerRoom::Member;
19using AnnounceMultiplayerRoom::RoomInformation;
20
21constexpr u32 network_version = 1; ///< The version of this Room and RoomMember
22
23constexpr u16 DefaultRoomPort = 24872;
24
25constexpr u32 MaxMessageSize = 500;
26
27/// Maximum number of concurrent connections allowed to this room.
28static constexpr u32 MaxConcurrentConnections = 254;
29
30constexpr std::size_t NumChannels = 1; // Number of channels used for the connection
31
32/// A special MAC address that tells the room we're joining to assign us a MAC address
33/// automatically.
34constexpr MacAddress NoPreferredMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
35
36// 802.11 broadcast MAC address
37constexpr MacAddress BroadcastMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
38
39// The different types of messages that can be sent. The first byte of each packet defines the type
40enum RoomMessageTypes : u8 {
41 IdJoinRequest = 1,
42 IdJoinSuccess,
43 IdRoomInformation,
44 IdSetGameInfo,
45 IdWifiPacket,
46 IdChatMessage,
47 IdNameCollision,
48 IdMacCollision,
49 IdVersionMismatch,
50 IdWrongPassword,
51 IdCloseRoom,
52 IdRoomIsFull,
53 IdConsoleIdCollision,
54 IdStatusMessage,
55 IdHostKicked,
56 IdHostBanned,
57 /// Moderation requests
58 IdModKick,
59 IdModBan,
60 IdModUnban,
61 IdModGetBanList,
62 // Moderation responses
63 IdModBanListResponse,
64 IdModPermissionDenied,
65 IdModNoSuchUser,
66 IdJoinSuccessAsMod,
67};
68
69/// Types of system status messages
70enum StatusMessageTypes : u8 {
71 IdMemberJoin = 1, ///< Member joining
72 IdMemberLeave, ///< Member leaving
73 IdMemberKicked, ///< A member is kicked from the room
74 IdMemberBanned, ///< A member is banned from the room
75 IdAddressUnbanned, ///< A username / ip address is unbanned from the room
76};
77
78/// This is what a server [person creating a server] would use.
79class Room final {
80public:
81 enum class State : u8 {
82 Open, ///< The room is open and ready to accept connections.
83 Closed, ///< The room is not opened and can not accept connections.
84 };
85
86 Room();
87 ~Room();
88
89 /**
90 * Gets the current state of the room.
91 */
92 State GetState() const;
93
94 /**
95 * Gets the room information of the room.
96 */
97 const RoomInformation& GetRoomInformation() const;
98
99 /**
100 * Gets the verify UID of this room.
101 */
102 std::string GetVerifyUID() const;
103
104 /**
105 * Gets a list of the mbmers connected to the room.
106 */
107 std::vector<Member> GetRoomMemberList() const;
108
109 /**
110 * Checks if the room is password protected
111 */
112 bool HasPassword() const;
113
114 using UsernameBanList = std::vector<std::string>;
115 using IPBanList = std::vector<std::string>;
116
117 using BanList = std::pair<UsernameBanList, IPBanList>;
118
119 /**
120 * Creates the socket for this room. Will bind to default address if
121 * server is empty string.
122 */
123 bool Create(const std::string& name, const std::string& description = "",
124 const std::string& server = "", u16 server_port = DefaultRoomPort,
125 const std::string& password = "",
126 const u32 max_connections = MaxConcurrentConnections,
127 const std::string& host_username = "", const GameInfo = {},
128 std::unique_ptr<VerifyUser::Backend> verify_backend = nullptr,
129 const BanList& ban_list = {}, bool enable_yuzu_mods = false);
130
131 /**
132 * Sets the verification GUID of the room.
133 */
134 void SetVerifyUID(const std::string& uid);
135
136 /**
137 * Gets the ban list (including banned forum usernames and IPs) of the room.
138 */
139 BanList GetBanList() const;
140
141 /**
142 * Destroys the socket
143 */
144 void Destroy();
145
146private:
147 class RoomImpl;
148 std::unique_ptr<RoomImpl> room_impl;
149};
150
151} // namespace Network
diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp
new file mode 100644
index 000000000..e4f823e98
--- /dev/null
+++ b/src/network/room_member.cpp
@@ -0,0 +1,696 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <atomic>
5#include <list>
6#include <mutex>
7#include <set>
8#include <thread>
9#include "common/assert.h"
10#include "enet/enet.h"
11#include "network/packet.h"
12#include "network/room_member.h"
13
14namespace Network {
15
16constexpr u32 ConnectionTimeoutMs = 5000;
17
18class RoomMember::RoomMemberImpl {
19public:
20 ENetHost* client = nullptr; ///< ENet network interface.
21 ENetPeer* server = nullptr; ///< The server peer the client is connected to
22
23 /// Information about the clients connected to the same room as us.
24 MemberList member_information;
25 /// Information about the room we're connected to.
26 RoomInformation room_information;
27
28 /// The current game name, id and version
29 GameInfo current_game_info;
30
31 std::atomic<State> state{State::Idle}; ///< Current state of the RoomMember.
32 void SetState(const State new_state);
33 void SetError(const Error new_error);
34 bool IsConnected() const;
35
36 std::string nickname; ///< The nickname of this member.
37
38 std::string username; ///< The username of this member.
39 mutable std::mutex username_mutex; ///< Mutex for locking username.
40
41 MacAddress mac_address; ///< The mac_address of this member.
42
43 std::mutex network_mutex; ///< Mutex that controls access to the `client` variable.
44 /// Thread that receives and dispatches network packets
45 std::unique_ptr<std::thread> loop_thread;
46 std::mutex send_list_mutex; ///< Mutex that controls access to the `send_list` variable.
47 std::list<Packet> send_list; ///< A list that stores all packets to send the async
48
49 template <typename T>
50 using CallbackSet = std::set<CallbackHandle<T>>;
51 std::mutex callback_mutex; ///< The mutex used for handling callbacks
52
53 class Callbacks {
54 public:
55 template <typename T>
56 CallbackSet<T>& Get();
57
58 private:
59 CallbackSet<WifiPacket> callback_set_wifi_packet;
60 CallbackSet<ChatEntry> callback_set_chat_messages;
61 CallbackSet<StatusMessageEntry> callback_set_status_messages;
62 CallbackSet<RoomInformation> callback_set_room_information;
63 CallbackSet<State> callback_set_state;
64 CallbackSet<Error> callback_set_error;
65 CallbackSet<Room::BanList> callback_set_ban_list;
66 };
67 Callbacks callbacks; ///< All CallbackSets to all events
68
69 void MemberLoop();
70
71 void StartLoop();
72
73 /**
74 * Sends data to the room. It will be send on channel 0 with flag RELIABLE
75 * @param packet The data to send
76 */
77 void Send(Packet&& packet);
78
79 /**
80 * Sends a request to the server, asking for permission to join a room with the specified
81 * nickname and preferred mac.
82 * @params nickname The desired nickname.
83 * @params console_id_hash A hash of the Console ID.
84 * @params preferred_mac The preferred MAC address to use in the room, the NoPreferredMac tells
85 * @params password The password for the room
86 * the server to assign one for us.
87 */
88 void SendJoinRequest(const std::string& nickname_, const std::string& console_id_hash,
89 const MacAddress& preferred_mac = NoPreferredMac,
90 const std::string& password = "", const std::string& token = "");
91
92 /**
93 * Extracts a MAC Address from a received ENet packet.
94 * @param event The ENet event that was received.
95 */
96 void HandleJoinPacket(const ENetEvent* event);
97 /**
98 * Extracts RoomInformation and MemberInformation from a received ENet packet.
99 * @param event The ENet event that was received.
100 */
101 void HandleRoomInformationPacket(const ENetEvent* event);
102
103 /**
104 * Extracts a WifiPacket from a received ENet packet.
105 * @param event The ENet event that was received.
106 */
107 void HandleWifiPackets(const ENetEvent* event);
108
109 /**
110 * Extracts a chat entry from a received ENet packet and adds it to the chat queue.
111 * @param event The ENet event that was received.
112 */
113 void HandleChatPacket(const ENetEvent* event);
114
115 /**
116 * Extracts a system message entry from a received ENet packet and adds it to the system message
117 * queue.
118 * @param event The ENet event that was received.
119 */
120 void HandleStatusMessagePacket(const ENetEvent* event);
121
122 /**
123 * Extracts a ban list request response from a received ENet packet.
124 * @param event The ENet event that was received.
125 */
126 void HandleModBanListResponsePacket(const ENetEvent* event);
127
128 /**
129 * Disconnects the RoomMember from the Room
130 */
131 void Disconnect();
132
133 template <typename T>
134 void Invoke(const T& data);
135
136 template <typename T>
137 CallbackHandle<T> Bind(std::function<void(const T&)> callback);
138};
139
140// RoomMemberImpl
141void RoomMember::RoomMemberImpl::SetState(const State new_state) {
142 if (state != new_state) {
143 state = new_state;
144 Invoke<State>(state);
145 }
146}
147
148void RoomMember::RoomMemberImpl::SetError(const Error new_error) {
149 Invoke<Error>(new_error);
150}
151
152bool RoomMember::RoomMemberImpl::IsConnected() const {
153 return state == State::Joining || state == State::Joined || state == State::Moderator;
154}
155
156void RoomMember::RoomMemberImpl::MemberLoop() {
157 // Receive packets while the connection is open
158 while (IsConnected()) {
159 std::lock_guard lock(network_mutex);
160 ENetEvent event;
161 if (enet_host_service(client, &event, 16) > 0) {
162 switch (event.type) {
163 case ENET_EVENT_TYPE_RECEIVE:
164 switch (event.packet->data[0]) {
165 case IdWifiPacket:
166 HandleWifiPackets(&event);
167 break;
168 case IdChatMessage:
169 HandleChatPacket(&event);
170 break;
171 case IdStatusMessage:
172 HandleStatusMessagePacket(&event);
173 break;
174 case IdRoomInformation:
175 HandleRoomInformationPacket(&event);
176 break;
177 case IdJoinSuccess:
178 case IdJoinSuccessAsMod:
179 // The join request was successful, we are now in the room.
180 // If we joined successfully, there must be at least one client in the room: us.
181 ASSERT_MSG(member_information.size() > 0,
182 "We have not yet received member information.");
183 HandleJoinPacket(&event); // Get the MAC Address for the client
184 if (event.packet->data[0] == IdJoinSuccessAsMod) {
185 SetState(State::Moderator);
186 } else {
187 SetState(State::Joined);
188 }
189 break;
190 case IdModBanListResponse:
191 HandleModBanListResponsePacket(&event);
192 break;
193 case IdRoomIsFull:
194 SetState(State::Idle);
195 SetError(Error::RoomIsFull);
196 break;
197 case IdNameCollision:
198 SetState(State::Idle);
199 SetError(Error::NameCollision);
200 break;
201 case IdMacCollision:
202 SetState(State::Idle);
203 SetError(Error::MacCollision);
204 break;
205 case IdConsoleIdCollision:
206 SetState(State::Idle);
207 SetError(Error::ConsoleIdCollision);
208 break;
209 case IdVersionMismatch:
210 SetState(State::Idle);
211 SetError(Error::WrongVersion);
212 break;
213 case IdWrongPassword:
214 SetState(State::Idle);
215 SetError(Error::WrongPassword);
216 break;
217 case IdCloseRoom:
218 SetState(State::Idle);
219 SetError(Error::LostConnection);
220 break;
221 case IdHostKicked:
222 SetState(State::Idle);
223 SetError(Error::HostKicked);
224 break;
225 case IdHostBanned:
226 SetState(State::Idle);
227 SetError(Error::HostBanned);
228 break;
229 case IdModPermissionDenied:
230 SetError(Error::PermissionDenied);
231 break;
232 case IdModNoSuchUser:
233 SetError(Error::NoSuchUser);
234 break;
235 }
236 enet_packet_destroy(event.packet);
237 break;
238 case ENET_EVENT_TYPE_DISCONNECT:
239 if (state == State::Joined || state == State::Moderator) {
240 SetState(State::Idle);
241 SetError(Error::LostConnection);
242 }
243 break;
244 case ENET_EVENT_TYPE_NONE:
245 break;
246 case ENET_EVENT_TYPE_CONNECT:
247 // The ENET_EVENT_TYPE_CONNECT event can not possibly happen here because we're
248 // already connected
249 ASSERT_MSG(false, "Received unexpected connect event while already connected");
250 break;
251 }
252 }
253 std::list<Packet> packets;
254 {
255 std::lock_guard send_lock(send_list_mutex);
256 packets.swap(send_list);
257 }
258 for (const auto& packet : packets) {
259 ENetPacket* enetPacket = enet_packet_create(packet.GetData(), packet.GetDataSize(),
260 ENET_PACKET_FLAG_RELIABLE);
261 enet_peer_send(server, 0, enetPacket);
262 }
263 enet_host_flush(client);
264 }
265 Disconnect();
266};
267
268void RoomMember::RoomMemberImpl::StartLoop() {
269 loop_thread = std::make_unique<std::thread>(&RoomMember::RoomMemberImpl::MemberLoop, this);
270}
271
272void RoomMember::RoomMemberImpl::Send(Packet&& packet) {
273 std::lock_guard lock(send_list_mutex);
274 send_list.push_back(std::move(packet));
275}
276
277void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname_,
278 const std::string& console_id_hash,
279 const MacAddress& preferred_mac,
280 const std::string& password,
281 const std::string& token) {
282 Packet packet;
283 packet.Write(static_cast<u8>(IdJoinRequest));
284 packet.Write(nickname_);
285 packet.Write(console_id_hash);
286 packet.Write(preferred_mac);
287 packet.Write(network_version);
288 packet.Write(password);
289 packet.Write(token);
290 Send(std::move(packet));
291}
292
293void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* event) {
294 Packet packet;
295 packet.Append(event->packet->data, event->packet->dataLength);
296
297 // Ignore the first byte, which is the message id.
298 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
299
300 RoomInformation info{};
301 packet.Read(info.name);
302 packet.Read(info.description);
303 packet.Read(info.member_slots);
304 packet.Read(info.port);
305 packet.Read(info.preferred_game.name);
306 packet.Read(info.host_username);
307 room_information.name = info.name;
308 room_information.description = info.description;
309 room_information.member_slots = info.member_slots;
310 room_information.port = info.port;
311 room_information.preferred_game = info.preferred_game;
312 room_information.host_username = info.host_username;
313
314 u32 num_members;
315 packet.Read(num_members);
316 member_information.resize(num_members);
317
318 for (auto& member : member_information) {
319 packet.Read(member.nickname);
320 packet.Read(member.mac_address);
321 packet.Read(member.game_info.name);
322 packet.Read(member.game_info.id);
323 packet.Read(member.username);
324 packet.Read(member.display_name);
325 packet.Read(member.avatar_url);
326
327 {
328 std::lock_guard lock(username_mutex);
329 if (member.nickname == nickname) {
330 username = member.username;
331 }
332 }
333 }
334 Invoke(room_information);
335}
336
337void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) {
338 Packet packet;
339 packet.Append(event->packet->data, event->packet->dataLength);
340
341 // Ignore the first byte, which is the message id.
342 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
343
344 // Parse the MAC Address from the packet
345 packet.Read(mac_address);
346}
347
348void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) {
349 WifiPacket wifi_packet{};
350 Packet packet;
351 packet.Append(event->packet->data, event->packet->dataLength);
352
353 // Ignore the first byte, which is the message id.
354 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
355
356 // Parse the WifiPacket from the packet
357 u8 frame_type;
358 packet.Read(frame_type);
359 WifiPacket::PacketType type = static_cast<WifiPacket::PacketType>(frame_type);
360
361 wifi_packet.type = type;
362 packet.Read(wifi_packet.channel);
363 packet.Read(wifi_packet.transmitter_address);
364 packet.Read(wifi_packet.destination_address);
365 packet.Read(wifi_packet.data);
366
367 Invoke<WifiPacket>(wifi_packet);
368}
369
370void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) {
371 Packet packet;
372 packet.Append(event->packet->data, event->packet->dataLength);
373
374 // Ignore the first byte, which is the message id.
375 packet.IgnoreBytes(sizeof(u8));
376
377 ChatEntry chat_entry{};
378 packet.Read(chat_entry.nickname);
379 packet.Read(chat_entry.username);
380 packet.Read(chat_entry.message);
381 Invoke<ChatEntry>(chat_entry);
382}
383
384void RoomMember::RoomMemberImpl::HandleStatusMessagePacket(const ENetEvent* event) {
385 Packet packet;
386 packet.Append(event->packet->data, event->packet->dataLength);
387
388 // Ignore the first byte, which is the message id.
389 packet.IgnoreBytes(sizeof(u8));
390
391 StatusMessageEntry status_message_entry{};
392 u8 type{};
393 packet.Read(type);
394 status_message_entry.type = static_cast<StatusMessageTypes>(type);
395 packet.Read(status_message_entry.nickname);
396 packet.Read(status_message_entry.username);
397 Invoke<StatusMessageEntry>(status_message_entry);
398}
399
400void RoomMember::RoomMemberImpl::HandleModBanListResponsePacket(const ENetEvent* event) {
401 Packet packet;
402 packet.Append(event->packet->data, event->packet->dataLength);
403
404 // Ignore the first byte, which is the message id.
405 packet.IgnoreBytes(sizeof(u8));
406
407 Room::BanList ban_list = {};
408 packet.Read(ban_list.first);
409 packet.Read(ban_list.second);
410 Invoke<Room::BanList>(ban_list);
411}
412
413void RoomMember::RoomMemberImpl::Disconnect() {
414 member_information.clear();
415 room_information.member_slots = 0;
416 room_information.name.clear();
417
418 if (!server) {
419 return;
420 }
421 enet_peer_disconnect(server, 0);
422
423 ENetEvent event;
424 while (enet_host_service(client, &event, ConnectionTimeoutMs) > 0) {
425 switch (event.type) {
426 case ENET_EVENT_TYPE_RECEIVE:
427 enet_packet_destroy(event.packet); // Ignore all incoming data
428 break;
429 case ENET_EVENT_TYPE_DISCONNECT:
430 server = nullptr;
431 return;
432 case ENET_EVENT_TYPE_NONE:
433 case ENET_EVENT_TYPE_CONNECT:
434 break;
435 }
436 }
437 // didn't disconnect gracefully force disconnect
438 enet_peer_reset(server);
439 server = nullptr;
440}
441
442template <>
443RoomMember::RoomMemberImpl::CallbackSet<WifiPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() {
444 return callback_set_wifi_packet;
445}
446
447template <>
448RoomMember::RoomMemberImpl::CallbackSet<RoomMember::State>&
449RoomMember::RoomMemberImpl::Callbacks::Get() {
450 return callback_set_state;
451}
452
453template <>
454RoomMember::RoomMemberImpl::CallbackSet<RoomMember::Error>&
455RoomMember::RoomMemberImpl::Callbacks::Get() {
456 return callback_set_error;
457}
458
459template <>
460RoomMember::RoomMemberImpl::CallbackSet<RoomInformation>&
461RoomMember::RoomMemberImpl::Callbacks::Get() {
462 return callback_set_room_information;
463}
464
465template <>
466RoomMember::RoomMemberImpl::CallbackSet<ChatEntry>& RoomMember::RoomMemberImpl::Callbacks::Get() {
467 return callback_set_chat_messages;
468}
469
470template <>
471RoomMember::RoomMemberImpl::CallbackSet<StatusMessageEntry>&
472RoomMember::RoomMemberImpl::Callbacks::Get() {
473 return callback_set_status_messages;
474}
475
476template <>
477RoomMember::RoomMemberImpl::CallbackSet<Room::BanList>&
478RoomMember::RoomMemberImpl::Callbacks::Get() {
479 return callback_set_ban_list;
480}
481
482template <typename T>
483void RoomMember::RoomMemberImpl::Invoke(const T& data) {
484 std::lock_guard lock(callback_mutex);
485 CallbackSet<T> callback_set = callbacks.Get<T>();
486 for (auto const& callback : callback_set) {
487 (*callback)(data);
488 }
489}
490
491template <typename T>
492RoomMember::CallbackHandle<T> RoomMember::RoomMemberImpl::Bind(
493 std::function<void(const T&)> callback) {
494 std::lock_guard lock(callback_mutex);
495 CallbackHandle<T> handle;
496 handle = std::make_shared<std::function<void(const T&)>>(callback);
497 callbacks.Get<T>().insert(handle);
498 return handle;
499}
500
501// RoomMember
502RoomMember::RoomMember() : room_member_impl{std::make_unique<RoomMemberImpl>()} {}
503
504RoomMember::~RoomMember() {
505 ASSERT_MSG(!IsConnected(), "RoomMember is being destroyed while connected");
506 if (room_member_impl->loop_thread) {
507 Leave();
508 }
509}
510
511RoomMember::State RoomMember::GetState() const {
512 return room_member_impl->state;
513}
514
515const RoomMember::MemberList& RoomMember::GetMemberInformation() const {
516 return room_member_impl->member_information;
517}
518
519const std::string& RoomMember::GetNickname() const {
520 return room_member_impl->nickname;
521}
522
523const std::string& RoomMember::GetUsername() const {
524 std::lock_guard lock(room_member_impl->username_mutex);
525 return room_member_impl->username;
526}
527
528const MacAddress& RoomMember::GetMacAddress() const {
529 ASSERT_MSG(IsConnected(), "Tried to get MAC address while not connected");
530 return room_member_impl->mac_address;
531}
532
533RoomInformation RoomMember::GetRoomInformation() const {
534 return room_member_impl->room_information;
535}
536
537void RoomMember::Join(const std::string& nick, const std::string& console_id_hash,
538 const char* server_addr, u16 server_port, u16 client_port,
539 const MacAddress& preferred_mac, const std::string& password,
540 const std::string& token) {
541 // If the member is connected, kill the connection first
542 if (room_member_impl->loop_thread && room_member_impl->loop_thread->joinable()) {
543 Leave();
544 }
545 // If the thread isn't running but the ptr still exists, reset it
546 else if (room_member_impl->loop_thread) {
547 room_member_impl->loop_thread.reset();
548 }
549
550 if (!room_member_impl->client) {
551 room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0);
552 ASSERT_MSG(room_member_impl->client != nullptr, "Could not create client");
553 }
554
555 room_member_impl->SetState(State::Joining);
556
557 ENetAddress address{};
558 enet_address_set_host(&address, server_addr);
559 address.port = server_port;
560 room_member_impl->server =
561 enet_host_connect(room_member_impl->client, &address, NumChannels, 0);
562
563 if (!room_member_impl->server) {
564 room_member_impl->SetState(State::Idle);
565 room_member_impl->SetError(Error::UnknownError);
566 return;
567 }
568
569 ENetEvent event{};
570 int net = enet_host_service(room_member_impl->client, &event, ConnectionTimeoutMs);
571 if (net > 0 && event.type == ENET_EVENT_TYPE_CONNECT) {
572 room_member_impl->nickname = nick;
573 room_member_impl->StartLoop();
574 room_member_impl->SendJoinRequest(nick, console_id_hash, preferred_mac, password, token);
575 SendGameInfo(room_member_impl->current_game_info);
576 } else {
577 enet_peer_disconnect(room_member_impl->server, 0);
578 room_member_impl->SetState(State::Idle);
579 room_member_impl->SetError(Error::CouldNotConnect);
580 }
581}
582
583bool RoomMember::IsConnected() const {
584 return room_member_impl->IsConnected();
585}
586
587void RoomMember::SendWifiPacket(const WifiPacket& wifi_packet) {
588 Packet packet;
589 packet.Write(static_cast<u8>(IdWifiPacket));
590 packet.Write(static_cast<u8>(wifi_packet.type));
591 packet.Write(wifi_packet.channel);
592 packet.Write(wifi_packet.transmitter_address);
593 packet.Write(wifi_packet.destination_address);
594 packet.Write(wifi_packet.data);
595 room_member_impl->Send(std::move(packet));
596}
597
598void RoomMember::SendChatMessage(const std::string& message) {
599 Packet packet;
600 packet.Write(static_cast<u8>(IdChatMessage));
601 packet.Write(message);
602 room_member_impl->Send(std::move(packet));
603}
604
605void RoomMember::SendGameInfo(const GameInfo& game_info) {
606 room_member_impl->current_game_info = game_info;
607 if (!IsConnected())
608 return;
609
610 Packet packet;
611 packet.Write(static_cast<u8>(IdSetGameInfo));
612 packet.Write(game_info.name);
613 packet.Write(game_info.id);
614 room_member_impl->Send(std::move(packet));
615}
616
617void RoomMember::SendModerationRequest(RoomMessageTypes type, const std::string& nickname) {
618 ASSERT_MSG(type == IdModKick || type == IdModBan || type == IdModUnban,
619 "type is not a moderation request");
620 if (!IsConnected())
621 return;
622
623 Packet packet;
624 packet.Write(static_cast<u8>(type));
625 packet.Write(nickname);
626 room_member_impl->Send(std::move(packet));
627}
628
629void RoomMember::RequestBanList() {
630 if (!IsConnected())
631 return;
632
633 Packet packet;
634 packet.Write(static_cast<u8>(IdModGetBanList));
635 room_member_impl->Send(std::move(packet));
636}
637
638RoomMember::CallbackHandle<RoomMember::State> RoomMember::BindOnStateChanged(
639 std::function<void(const RoomMember::State&)> callback) {
640 return room_member_impl->Bind(callback);
641}
642
643RoomMember::CallbackHandle<RoomMember::Error> RoomMember::BindOnError(
644 std::function<void(const RoomMember::Error&)> callback) {
645 return room_member_impl->Bind(callback);
646}
647
648RoomMember::CallbackHandle<WifiPacket> RoomMember::BindOnWifiPacketReceived(
649 std::function<void(const WifiPacket&)> callback) {
650 return room_member_impl->Bind(callback);
651}
652
653RoomMember::CallbackHandle<RoomInformation> RoomMember::BindOnRoomInformationChanged(
654 std::function<void(const RoomInformation&)> callback) {
655 return room_member_impl->Bind(callback);
656}
657
658RoomMember::CallbackHandle<ChatEntry> RoomMember::BindOnChatMessageRecieved(
659 std::function<void(const ChatEntry&)> callback) {
660 return room_member_impl->Bind(callback);
661}
662
663RoomMember::CallbackHandle<StatusMessageEntry> RoomMember::BindOnStatusMessageReceived(
664 std::function<void(const StatusMessageEntry&)> callback) {
665 return room_member_impl->Bind(callback);
666}
667
668RoomMember::CallbackHandle<Room::BanList> RoomMember::BindOnBanListReceived(
669 std::function<void(const Room::BanList&)> callback) {
670 return room_member_impl->Bind(callback);
671}
672
673template <typename T>
674void RoomMember::Unbind(CallbackHandle<T> handle) {
675 std::lock_guard lock(room_member_impl->callback_mutex);
676 room_member_impl->callbacks.Get<T>().erase(handle);
677}
678
679void RoomMember::Leave() {
680 room_member_impl->SetState(State::Idle);
681 room_member_impl->loop_thread->join();
682 room_member_impl->loop_thread.reset();
683
684 enet_host_destroy(room_member_impl->client);
685 room_member_impl->client = nullptr;
686}
687
688template void RoomMember::Unbind(CallbackHandle<WifiPacket>);
689template void RoomMember::Unbind(CallbackHandle<RoomMember::State>);
690template void RoomMember::Unbind(CallbackHandle<RoomMember::Error>);
691template void RoomMember::Unbind(CallbackHandle<RoomInformation>);
692template void RoomMember::Unbind(CallbackHandle<ChatEntry>);
693template void RoomMember::Unbind(CallbackHandle<StatusMessageEntry>);
694template void RoomMember::Unbind(CallbackHandle<Room::BanList>);
695
696} // namespace Network
diff --git a/src/network/room_member.h b/src/network/room_member.h
new file mode 100644
index 000000000..bbb7d13d4
--- /dev/null
+++ b/src/network/room_member.h
@@ -0,0 +1,318 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <functional>
7#include <memory>
8#include <string>
9#include <vector>
10#include "common/announce_multiplayer_room.h"
11#include "common/common_types.h"
12#include "network/room.h"
13
14namespace Network {
15
16using AnnounceMultiplayerRoom::GameInfo;
17using AnnounceMultiplayerRoom::RoomInformation;
18
19/// Information about the received WiFi packets.
20/// Acts as our own 802.11 header.
21struct WifiPacket {
22 enum class PacketType : u8 {
23 Beacon,
24 Data,
25 Authentication,
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};
37
38/// Represents a chat message.
39struct ChatEntry {
40 std::string nickname; ///< Nickname of the client who sent this message.
41 /// Web services username of the client who sent this message, can be empty.
42 std::string username;
43 std::string message; ///< Body of the message.
44};
45
46/// Represents a system status message.
47struct StatusMessageEntry {
48 StatusMessageTypes type; ///< Type of the message
49 /// Subject of the message. i.e. the user who is joining/leaving/being banned, etc.
50 std::string nickname;
51 std::string username;
52};
53
54/**
55 * This is what a client [person joining a server] would use.
56 * It also has to be used if you host a game yourself (You'd create both, a Room and a
57 * RoomMembership for yourself)
58 */
59class RoomMember final {
60public:
61 enum class State : u8 {
62 Uninitialized, ///< Not initialized
63 Idle, ///< Default state (i.e. not connected)
64 Joining, ///< The client is attempting to join a room.
65 Joined, ///< The client is connected to the room and is ready to send/receive packets.
66 Moderator, ///< The client is connnected to the room and is granted mod permissions.
67 };
68
69 enum class Error : u8 {
70 // Reasons why connection was closed
71 LostConnection, ///< Connection closed
72 HostKicked, ///< Kicked by the host
73
74 // Reasons why connection was rejected
75 UnknownError, ///< Some error [permissions to network device missing or something]
76 NameCollision, ///< Somebody is already using this name
77 MacCollision, ///< Somebody is already using that mac-address
78 ConsoleIdCollision, ///< Somebody in the room has the same Console ID
79 WrongVersion, ///< The room version is not the same as for this RoomMember
80 WrongPassword, ///< The password doesn't match the one from the Room
81 CouldNotConnect, ///< The room is not responding to a connection attempt
82 RoomIsFull, ///< Room is already at the maximum number of players
83 HostBanned, ///< The user is banned by the host
84
85 // Reasons why moderation request failed
86 PermissionDenied, ///< The user does not have mod permissions
87 NoSuchUser, ///< The nickname the user attempts to kick/ban does not exist
88 };
89
90 struct MemberInformation {
91 std::string nickname; ///< Nickname of the member.
92 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.
94 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
96 /// not playing anything.
97 MacAddress mac_address; ///< MAC address associated with this member.
98 };
99 using MemberList = std::vector<MemberInformation>;
100
101 // The handle for the callback functions
102 template <typename T>
103 using CallbackHandle = std::shared_ptr<std::function<void(const T&)>>;
104
105 /**
106 * Unbinds a callback function from the events.
107 * @param handle The connection handle to disconnect
108 */
109 template <typename T>
110 void Unbind(CallbackHandle<T> handle);
111
112 RoomMember();
113 ~RoomMember();
114
115 /**
116 * Returns the status of our connection to the room.
117 */
118 State GetState() const;
119
120 /**
121 * Returns information about the members in the room we're currently connected to.
122 */
123 const MemberList& GetMemberInformation() const;
124
125 /**
126 * Returns the nickname of the RoomMember.
127 */
128 const std::string& GetNickname() const;
129
130 /**
131 * Returns the username of the RoomMember.
132 */
133 const std::string& GetUsername() const;
134
135 /**
136 * Returns the MAC address of the RoomMember.
137 */
138 const MacAddress& GetMacAddress() const;
139
140 /**
141 * Returns information about the room we're currently connected to.
142 */
143 RoomInformation GetRoomInformation() const;
144
145 /**
146 * Returns whether we're connected to a server or not.
147 */
148 bool IsConnected() const;
149
150 /**
151 * 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 */
155 void Join(const std::string& nickname, const std::string& console_id_hash,
156 const char* server_addr = "127.0.0.1", u16 server_port = DefaultRoomPort,
157 u16 client_port = 0, const MacAddress& preferred_mac = NoPreferredMac,
158 const std::string& password = "", const std::string& token = "");
159
160 /**
161 * Sends a WiFi packet to the room.
162 * @param packet The WiFi packet to send.
163 */
164 void SendWifiPacket(const WifiPacket& packet);
165
166 /**
167 * Sends a chat message to the room.
168 * @param message The contents of the message.
169 */
170 void SendChatMessage(const std::string& message);
171
172 /**
173 * Sends the current game info to the room.
174 * @param game_info The game information.
175 */
176 void SendGameInfo(const GameInfo& game_info);
177
178 /**
179 * Sends a moderation request to the room.
180 * @param type Moderation request type.
181 * @param nickname The subject of the request. (i.e. the user you want to kick/ban)
182 */
183 void SendModerationRequest(RoomMessageTypes type, const std::string& nickname);
184
185 /**
186 * Attempts to retrieve ban list from the room.
187 * If success, the ban list callback would be called. Otherwise an error would be emitted.
188 */
189 void RequestBanList();
190
191 /**
192 * Binds a function to an event that will be triggered every time the State of the member
193 * changed. The function wil be called every time the event is triggered. The callback function
194 * must not bind or unbind a function. Doing so will cause a deadlock
195 * @param callback The function to call
196 * @return A handle used for removing the function from the registered list
197 */
198 CallbackHandle<State> BindOnStateChanged(std::function<void(const State&)> callback);
199
200 /**
201 * Binds a function to an event that will be triggered every time an error happened. The
202 * function wil be called every time the event is triggered. The callback function must not bind
203 * or unbind a function. Doing so will cause a deadlock
204 * @param callback The function to call
205 * @return A handle used for removing the function from the registered list
206 */
207 CallbackHandle<Error> BindOnError(std::function<void(const Error&)> callback);
208
209 /**
210 * Binds a function to an event that will be triggered every time a WifiPacket is received.
211 * 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
213 * @param callback The function to call
214 * @return A handle used for removing the function from the registered list
215 */
216 CallbackHandle<WifiPacket> BindOnWifiPacketReceived(
217 std::function<void(const WifiPacket&)> callback);
218
219 /**
220 * Binds a function to an event that will be triggered every time the RoomInformation changes.
221 * The function wil be called every time the event is triggered.
222 * The callback function must not bind or unbind a function. Doing so will cause a deadlock
223 * @param callback The function to call
224 * @return A handle used for removing the function from the registered list
225 */
226 CallbackHandle<RoomInformation> BindOnRoomInformationChanged(
227 std::function<void(const RoomInformation&)> callback);
228
229 /**
230 * Binds a function to an event that will be triggered every time a ChatMessage is received.
231 * The function wil be called every time the event is triggered.
232 * The callback function must not bind or unbind a function. Doing so will cause a deadlock
233 * @param callback The function to call
234 * @return A handle used for removing the function from the registered list
235 */
236 CallbackHandle<ChatEntry> BindOnChatMessageRecieved(
237 std::function<void(const ChatEntry&)> callback);
238
239 /**
240 * Binds a function to an event that will be triggered every time a StatusMessage is
241 * received. The function will be called every time the event is triggered. The callback
242 * function must not bind or unbind a function. Doing so will cause a deadlock
243 * @param callback The function to call
244 * @return A handle used for removing the function from the registered list
245 */
246 CallbackHandle<StatusMessageEntry> BindOnStatusMessageReceived(
247 std::function<void(const StatusMessageEntry&)> callback);
248
249 /**
250 * Binds a function to an event that will be triggered every time a requested ban list
251 * received. The function will be called every time the event is triggered. The callback
252 * function must not bind or unbind a function. Doing so will cause a deadlock
253 * @param callback The function to call
254 * @return A handle used for removing the function from the registered list
255 */
256 CallbackHandle<Room::BanList> BindOnBanListReceived(
257 std::function<void(const Room::BanList&)> callback);
258
259 /**
260 * Leaves the current room.
261 */
262 void Leave();
263
264private:
265 class RoomMemberImpl;
266 std::unique_ptr<RoomMemberImpl> room_member_impl;
267};
268
269inline const char* GetStateStr(const RoomMember::State& s) {
270 switch (s) {
271 case RoomMember::State::Uninitialized:
272 return "Uninitialized";
273 case RoomMember::State::Idle:
274 return "Idle";
275 case RoomMember::State::Joining:
276 return "Joining";
277 case RoomMember::State::Joined:
278 return "Joined";
279 case RoomMember::State::Moderator:
280 return "Moderator";
281 }
282 return "Unknown";
283}
284
285inline const char* GetErrorStr(const RoomMember::Error& e) {
286 switch (e) {
287 case RoomMember::Error::LostConnection:
288 return "LostConnection";
289 case RoomMember::Error::HostKicked:
290 return "HostKicked";
291 case RoomMember::Error::UnknownError:
292 return "UnknownError";
293 case RoomMember::Error::NameCollision:
294 return "NameCollision";
295 case RoomMember::Error::MacCollision:
296 return "MaxCollision";
297 case RoomMember::Error::ConsoleIdCollision:
298 return "ConsoleIdCollision";
299 case RoomMember::Error::WrongVersion:
300 return "WrongVersion";
301 case RoomMember::Error::WrongPassword:
302 return "WrongPassword";
303 case RoomMember::Error::CouldNotConnect:
304 return "CouldNotConnect";
305 case RoomMember::Error::RoomIsFull:
306 return "RoomIsFull";
307 case RoomMember::Error::HostBanned:
308 return "HostBanned";
309 case RoomMember::Error::PermissionDenied:
310 return "PermissionDenied";
311 case RoomMember::Error::NoSuchUser:
312 return "NoSuchUser";
313 default:
314 return "Unknown";
315 }
316}
317
318} // namespace Network
diff --git a/src/network/verify_user.cpp b/src/network/verify_user.cpp
new file mode 100644
index 000000000..f84cfe59b
--- /dev/null
+++ b/src/network/verify_user.cpp
@@ -0,0 +1,17 @@
1// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "network/verify_user.h"
5
6namespace Network::VerifyUser {
7
8Backend::~Backend() = default;
9
10NullBackend::~NullBackend() = default;
11
12UserData NullBackend::LoadUserData([[maybe_unused]] const std::string& verify_uid,
13 [[maybe_unused]] const std::string& token) {
14 return {};
15}
16
17} // namespace Network::VerifyUser
diff --git a/src/network/verify_user.h b/src/network/verify_user.h
new file mode 100644
index 000000000..6fc64d8a3
--- /dev/null
+++ b/src/network/verify_user.h
@@ -0,0 +1,45 @@
1// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7#include "common/logging/log.h"
8
9namespace Network::VerifyUser {
10
11struct UserData {
12 std::string username;
13 std::string display_name;
14 std::string avatar_url;
15 bool moderator = false; ///< Whether the user is a yuzu Moderator.
16};
17
18/**
19 * A backend used for verifying users and loading user data.
20 */
21class Backend {
22public:
23 virtual ~Backend();
24
25 /**
26 * Verifies the given token and loads the information into a UserData struct.
27 * @param verify_uid A GUID that may be used for verification.
28 * @param token A token that contains user data and verification data. The format and content is
29 * decided by backends.
30 */
31 virtual UserData LoadUserData(const std::string& verify_uid, const std::string& token) = 0;
32};
33
34/**
35 * A null backend where the token is ignored.
36 * No verification is performed here and the function returns an empty UserData.
37 */
38class NullBackend final : public Backend {
39public:
40 ~NullBackend();
41
42 UserData LoadUserData(const std::string& verify_uid, const std::string& token) override;
43};
44
45} // namespace Network::VerifyUser
diff --git a/src/shader_recompiler/CMakeLists.txt b/src/shader_recompiler/CMakeLists.txt
index ae1dbe619..af8e51fe8 100644
--- a/src/shader_recompiler/CMakeLists.txt
+++ b/src/shader_recompiler/CMakeLists.txt
@@ -1,3 +1,6 @@
1# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-2.0-or-later
3
1add_library(shader_recompiler STATIC 4add_library(shader_recompiler STATIC
2 backend/bindings.h 5 backend/bindings.h
3 backend/glasm/emit_glasm.cpp 6 backend/glasm/emit_glasm.cpp
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/logic_operation_three_input_lut3.py b/src/shader_recompiler/frontend/maxwell/translate/impl/logic_operation_three_input_lut3.py
index 8f547c266..e66d50d61 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/logic_operation_three_input_lut3.py
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/logic_operation_three_input_lut3.py
@@ -1,7 +1,5 @@
1# Copyright © 2022 degasus <markus@selfnet.de> 1# SPDX-FileCopyrightText: 2022 degasus <markus@selfnet.de>
2# This work is free. You can redistribute it and/or modify it under the 2# SPDX-License-Identifier: WTFPL
3# terms of the Do What The Fuck You Want To Public License, Version 2,
4# as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
5 3
6from itertools import product 4from itertools import product
7 5
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index a69ccb264..43ad2c7ff 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,3 +1,6 @@
1# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-2.0-or-later
3
1add_executable(tests 4add_executable(tests
2 common/bit_field.cpp 5 common/bit_field.cpp
3 common/cityhash.cpp 6 common/cityhash.cpp
@@ -7,7 +10,7 @@ add_executable(tests
7 common/ring_buffer.cpp 10 common/ring_buffer.cpp
8 common/unique_function.cpp 11 common/unique_function.cpp
9 core/core_timing.cpp 12 core/core_timing.cpp
10 core/network/network.cpp 13 core/internal_network/network.cpp
11 tests.cpp 14 tests.cpp
12 video_core/buffer_base.cpp 15 video_core/buffer_base.cpp
13 input_common/calibration_configuration_job.cpp 16 input_common/calibration_configuration_job.cpp
diff --git a/src/tests/common/bit_field.cpp b/src/tests/common/bit_field.cpp
index 182638000..0071ae52e 100644
--- a/src/tests/common/bit_field.cpp
+++ b/src/tests/common/bit_field.cpp
@@ -1,6 +1,5 @@
1// Copyright 2019 Citra Emulator Project 1// SPDX-FileCopyrightText: 2019 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <array> 4#include <array>
6#include <cstring> 5#include <cstring>
diff --git a/src/tests/common/param_package.cpp b/src/tests/common/param_package.cpp
index e31ca3544..d036cc83a 100644
--- a/src/tests/common/param_package.cpp
+++ b/src/tests/common/param_package.cpp
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <catch2/catch.hpp> 4#include <catch2/catch.hpp>
6#include <math.h> 5#include <math.h>
diff --git a/src/tests/core/network/network.cpp b/src/tests/core/internal_network/network.cpp
index 1bbb8372f..164b0ff24 100644
--- a/src/tests/core/network/network.cpp
+++ b/src/tests/core/internal_network/network.cpp
@@ -3,8 +3,8 @@
3 3
4#include <catch2/catch.hpp> 4#include <catch2/catch.hpp>
5 5
6#include "core/network/network.h" 6#include "core/internal_network/network.h"
7#include "core/network/sockets.h" 7#include "core/internal_network/sockets.h"
8 8
9TEST_CASE("Network::Errors", "[core]") { 9TEST_CASE("Network::Errors", "[core]") {
10 Network::NetworkInstance network_instance; // initialize network 10 Network::NetworkInstance network_instance; // initialize network
diff --git a/src/tests/tests.cpp b/src/tests/tests.cpp
index 275b430d9..3f905c05c 100644
--- a/src/tests/tests.cpp
+++ b/src/tests/tests.cpp
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#define CATCH_CONFIG_MAIN 4#define CATCH_CONFIG_MAIN
6#include <catch2/catch.hpp> 5#include <catch2/catch.hpp>
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 14de7bc89..5b3808351 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -1,3 +1,6 @@
1# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-2.0-or-later
3
1add_subdirectory(host_shaders) 4add_subdirectory(host_shaders)
2 5
3if(LIBVA_FOUND) 6if(LIBVA_FOUND)
diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt
index 190fc6aea..2149ab93e 100644
--- a/src/video_core/host_shaders/CMakeLists.txt
+++ b/src/video_core/host_shaders/CMakeLists.txt
@@ -1,3 +1,6 @@
1# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-2.0-or-later
3
1set(FIDELITYFX_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/externals/FidelityFX-FSR/ffx-fsr) 4set(FIDELITYFX_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/externals/FidelityFX-FSR/ffx-fsr)
2 5
3set(GLSL_INCLUDES 6set(GLSL_INCLUDES
diff --git a/src/video_core/host_shaders/StringShaderHeader.cmake b/src/video_core/host_shaders/StringShaderHeader.cmake
index 1b4bc6103..9f7525535 100644
--- a/src/video_core/host_shaders/StringShaderHeader.cmake
+++ b/src/video_core/host_shaders/StringShaderHeader.cmake
@@ -1,3 +1,6 @@
1# SPDX-FileCopyrightText: 2020 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-2.0-or-later
3
1set(SOURCE_FILE ${CMAKE_ARGV3}) 4set(SOURCE_FILE ${CMAKE_ARGV3})
2set(HEADER_FILE ${CMAKE_ARGV4}) 5set(HEADER_FILE ${CMAKE_ARGV4})
3set(INPUT_FILE ${CMAKE_ARGV5}) 6set(INPUT_FILE ${CMAKE_ARGV5})
diff --git a/src/video_core/host_shaders/source_shader.h.in b/src/video_core/host_shaders/source_shader.h.in
index 929dec39b..f189ee06b 100644
--- a/src/video_core/host_shaders/source_shader.h.in
+++ b/src/video_core/host_shaders/source_shader.h.in
@@ -1,3 +1,6 @@
1// SPDX-FileCopyrightText: 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
1#pragma once 4#pragma once
2 5
3#include <string_view> 6#include <string_view>
diff --git a/src/video_core/host_shaders/vulkan_present_scaleforce_fp16.frag b/src/video_core/host_shaders/vulkan_present_scaleforce_fp16.frag
index 924c03060..3dc9c0df5 100644
--- a/src/video_core/host_shaders/vulkan_present_scaleforce_fp16.frag
+++ b/src/video_core/host_shaders/vulkan_present_scaleforce_fp16.frag
@@ -1,3 +1,6 @@
1// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
1#version 460 4#version 460
2 5
3#extension GL_GOOGLE_include_directive : enable 6#extension GL_GOOGLE_include_directive : enable
diff --git a/src/video_core/host_shaders/vulkan_present_scaleforce_fp32.frag b/src/video_core/host_shaders/vulkan_present_scaleforce_fp32.frag
index a594b83ca..77ed07552 100644
--- a/src/video_core/host_shaders/vulkan_present_scaleforce_fp32.frag
+++ b/src/video_core/host_shaders/vulkan_present_scaleforce_fp32.frag
@@ -1,3 +1,6 @@
1// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
1#version 460 4#version 460
2 5
3#extension GL_GOOGLE_include_directive : enable 6#extension GL_GOOGLE_include_directive : enable
diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp
index 9756a81d6..45791aa75 100644
--- a/src/video_core/renderer_base.cpp
+++ b/src/video_core/renderer_base.cpp
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include "common/logging/log.h" 4#include "common/logging/log.h"
6#include "core/frontend/emu_window.h" 5#include "core/frontend/emu_window.h"
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 30d19b178..8d20cbece 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 159b71161..a0d048b0b 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <algorithm> 4#include <algorithm>
6#include <array> 5#include <array>
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index c79461d59..31a16fcba 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp
index f6839a657..3a664fdec 100644
--- a/src/video_core/renderer_opengl/gl_resource_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <string_view> 4#include <string_view>
6#include <glad/glad.h> 5#include <glad/glad.h>
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h
index 84e07f8bd..bc05ba4bd 100644
--- a/src/video_core/renderer_opengl/gl_resource_manager.h
+++ b/src/video_core/renderer_opengl/gl_resource_manager.h
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp
index 129966e72..f83ad0a5b 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_util.cpp
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <string_view> 4#include <string_view>
6#include <vector> 5#include <vector>
diff --git a/src/video_core/renderer_opengl/gl_shader_util.h b/src/video_core/renderer_opengl/gl_shader_util.h
index a64ef37dc..43ebcdeba 100644
--- a/src/video_core/renderer_opengl/gl_shader_util.h
+++ b/src/video_core/renderer_opengl/gl_shader_util.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 9a9243544..01028cee0 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <algorithm> 4#include <algorithm>
6#include <cstddef> 5#include <cstddef>
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index ae9558a33..1a32e739d 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
index eecd0deff..079d5f028 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include "common/common_types.h" 4#include "common/common_types.h"
6#include "common/math_util.h" 5#include "common/math_util.h"
diff --git a/src/video_core/surface.h b/src/video_core/surface.h
index 0175432ff..16273f185 100644
--- a/src/video_core/surface.h
+++ b/src/video_core/surface.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp
index 2f2594585..04ac4af11 100644
--- a/src/video_core/video_core.cpp
+++ b/src/video_core/video_core.cpp
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <memory> 4#include <memory>
6 5
diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h
index 084df641f..f8e2444f3 100644
--- a/src/video_core/video_core.h
+++ b/src/video_core/video_core.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/web_service/CMakeLists.txt b/src/web_service/CMakeLists.txt
index ae85a72ea..3f75d97d1 100644
--- a/src/web_service/CMakeLists.txt
+++ b/src/web_service/CMakeLists.txt
@@ -1,12 +1,19 @@
1# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-2.0-or-later
3
1add_library(web_service STATIC 4add_library(web_service STATIC
5 announce_room_json.cpp
6 announce_room_json.h
2 telemetry_json.cpp 7 telemetry_json.cpp
3 telemetry_json.h 8 telemetry_json.h
4 verify_login.cpp 9 verify_login.cpp
5 verify_login.h 10 verify_login.h
11 verify_user_jwt.cpp
12 verify_user_jwt.h
6 web_backend.cpp 13 web_backend.cpp
7 web_backend.h 14 web_backend.h
8 web_result.h 15 web_result.h
9) 16)
10 17
11create_target_directory_groups(web_service) 18create_target_directory_groups(web_service)
12target_link_libraries(web_service PRIVATE common nlohmann_json::nlohmann_json httplib) 19target_link_libraries(web_service PRIVATE common network nlohmann_json::nlohmann_json httplib cpp-jwt)
diff --git a/src/web_service/announce_room_json.cpp b/src/web_service/announce_room_json.cpp
new file mode 100644
index 000000000..4c3195efd
--- /dev/null
+++ b/src/web_service/announce_room_json.cpp
@@ -0,0 +1,145 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <future>
5#include <nlohmann/json.hpp>
6#include "common/detached_tasks.h"
7#include "common/logging/log.h"
8#include "web_service/announce_room_json.h"
9#include "web_service/web_backend.h"
10
11namespace AnnounceMultiplayerRoom {
12
13static void to_json(nlohmann::json& json, const Member& member) {
14 if (!member.username.empty()) {
15 json["username"] = member.username;
16 }
17 json["nickname"] = member.nickname;
18 if (!member.avatar_url.empty()) {
19 json["avatarUrl"] = member.avatar_url;
20 }
21 json["gameName"] = member.game.name;
22 json["gameId"] = member.game.id;
23}
24
25static void from_json(const nlohmann::json& json, Member& member) {
26 member.nickname = json.at("nickname").get<std::string>();
27 member.game.name = json.at("gameName").get<std::string>();
28 member.game.id = json.at("gameId").get<u64>();
29 try {
30 member.username = json.at("username").get<std::string>();
31 member.avatar_url = json.at("avatarUrl").get<std::string>();
32 } catch (const nlohmann::detail::out_of_range&) {
33 member.username = member.avatar_url = "";
34 LOG_DEBUG(Network, "Member \'{}\' isn't authenticated", member.nickname);
35 }
36}
37
38static void to_json(nlohmann::json& json, const Room& room) {
39 json["port"] = room.information.port;
40 json["name"] = room.information.name;
41 if (!room.information.description.empty()) {
42 json["description"] = room.information.description;
43 }
44 json["preferredGameName"] = room.information.preferred_game.name;
45 json["preferredGameId"] = room.information.preferred_game.id;
46 json["maxPlayers"] = room.information.member_slots;
47 json["netVersion"] = room.net_version;
48 json["hasPassword"] = room.has_password;
49 if (room.members.size() > 0) {
50 nlohmann::json member_json = room.members;
51 json["players"] = member_json;
52 }
53}
54
55static void from_json(const nlohmann::json& json, Room& room) {
56 room.verify_uid = json.at("externalGuid").get<std::string>();
57 room.ip = json.at("address").get<std::string>();
58 room.information.name = json.at("name").get<std::string>();
59 try {
60 room.information.description = json.at("description").get<std::string>();
61 } catch (const nlohmann::detail::out_of_range&) {
62 room.information.description = "";
63 LOG_DEBUG(Network, "Room \'{}\' doesn't contain a description", room.information.name);
64 }
65 room.information.host_username = json.at("owner").get<std::string>();
66 room.information.port = json.at("port").get<u16>();
67 room.information.preferred_game.name = json.at("preferredGameName").get<std::string>();
68 room.information.preferred_game.id = json.at("preferredGameId").get<u64>();
69 room.information.member_slots = json.at("maxPlayers").get<u32>();
70 room.net_version = json.at("netVersion").get<u32>();
71 room.has_password = json.at("hasPassword").get<bool>();
72 try {
73 room.members = json.at("players").get<std::vector<Member>>();
74 } catch (const nlohmann::detail::out_of_range& e) {
75 LOG_DEBUG(Network, "Out of range {}", e.what());
76 }
77}
78
79} // namespace AnnounceMultiplayerRoom
80
81namespace WebService {
82
83void RoomJson::SetRoomInformation(const std::string& name, const std::string& description,
84 const u16 port, const u32 max_player, const u32 net_version,
85 const bool has_password,
86 const AnnounceMultiplayerRoom::GameInfo& preferred_game) {
87 room.information.name = name;
88 room.information.description = description;
89 room.information.port = port;
90 room.information.member_slots = max_player;
91 room.net_version = net_version;
92 room.has_password = has_password;
93 room.information.preferred_game = preferred_game;
94}
95void RoomJson::AddPlayer(const AnnounceMultiplayerRoom::Member& member) {
96 room.members.push_back(member);
97}
98
99WebService::WebResult RoomJson::Update() {
100 if (room_id.empty()) {
101 LOG_ERROR(WebService, "Room must be registered to be updated");
102 return WebService::WebResult{WebService::WebResult::Code::LibError,
103 "Room is not registered", ""};
104 }
105 nlohmann::json json{{"players", room.members}};
106 return client.PostJson(fmt::format("/lobby/{}", room_id), json.dump(), false);
107}
108
109WebService::WebResult RoomJson::Register() {
110 nlohmann::json json = room;
111 auto result = client.PostJson("/lobby", json.dump(), false);
112 if (result.result_code != WebService::WebResult::Code::Success) {
113 return result;
114 }
115 auto reply_json = nlohmann::json::parse(result.returned_data);
116 room = reply_json.get<AnnounceMultiplayerRoom::Room>();
117 room_id = reply_json.at("id").get<std::string>();
118 return WebService::WebResult{WebService::WebResult::Code::Success, "", room.verify_uid};
119}
120
121void RoomJson::ClearPlayers() {
122 room.members.clear();
123}
124
125AnnounceMultiplayerRoom::RoomList RoomJson::GetRoomList() {
126 auto reply = client.GetJson("/lobby", true).returned_data;
127 if (reply.empty()) {
128 return {};
129 }
130 return nlohmann::json::parse(reply).at("rooms").get<AnnounceMultiplayerRoom::RoomList>();
131}
132
133void RoomJson::Delete() {
134 if (room_id.empty()) {
135 LOG_ERROR(WebService, "Room must be registered to be deleted");
136 return;
137 }
138 Common::DetachedTasks::AddTask(
139 [host{this->host}, username{this->username}, token{this->token}, room_id{this->room_id}]() {
140 // create a new client here because the this->client might be destroyed.
141 Client{host, username, token}.DeleteJson(fmt::format("/lobby/{}", room_id), "", false);
142 });
143}
144
145} // namespace WebService
diff --git a/src/web_service/announce_room_json.h b/src/web_service/announce_room_json.h
new file mode 100644
index 000000000..32c08858d
--- /dev/null
+++ b/src/web_service/announce_room_json.h
@@ -0,0 +1,41 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <functional>
7#include <string>
8#include "common/announce_multiplayer_room.h"
9#include "web_service/web_backend.h"
10
11namespace WebService {
12
13/**
14 * Implementation of AnnounceMultiplayerRoom::Backend that (de)serializes room information into/from
15 * JSON, and submits/gets it to/from the yuzu web service
16 */
17class RoomJson : public AnnounceMultiplayerRoom::Backend {
18public:
19 RoomJson(const std::string& host_, const std::string& username_, const std::string& token_)
20 : client(host_, username_, token_), host(host_), username(username_), token(token_) {}
21 ~RoomJson() = default;
22 void SetRoomInformation(const std::string& name, const std::string& description, const u16 port,
23 const u32 max_player, const u32 net_version, const bool has_password,
24 const AnnounceMultiplayerRoom::GameInfo& preferred_game) override;
25 void AddPlayer(const AnnounceMultiplayerRoom::Member& member) override;
26 WebResult Update() override;
27 WebResult Register() override;
28 void ClearPlayers() override;
29 AnnounceMultiplayerRoom::RoomList GetRoomList() override;
30 void Delete() override;
31
32private:
33 AnnounceMultiplayerRoom::Room room;
34 Client client;
35 std::string host;
36 std::string username;
37 std::string token;
38 std::string room_id;
39};
40
41} // namespace WebService
diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp
index 46faddb61..51c792004 100644
--- a/src/web_service/telemetry_json.cpp
+++ b/src/web_service/telemetry_json.cpp
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <nlohmann/json.hpp> 4#include <nlohmann/json.hpp>
6#include "common/detached_tasks.h" 5#include "common/detached_tasks.h"
diff --git a/src/web_service/telemetry_json.h b/src/web_service/telemetry_json.h
index df51e00f8..504002c04 100644
--- a/src/web_service/telemetry_json.h
+++ b/src/web_service/telemetry_json.h
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/web_service/verify_login.cpp b/src/web_service/verify_login.cpp
index ceb55ca6b..050080278 100644
--- a/src/web_service/verify_login.cpp
+++ b/src/web_service/verify_login.cpp
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <nlohmann/json.hpp> 4#include <nlohmann/json.hpp>
6#include "web_service/verify_login.h" 5#include "web_service/verify_login.h"
diff --git a/src/web_service/verify_login.h b/src/web_service/verify_login.h
index 821b345d7..8d0adce74 100644
--- a/src/web_service/verify_login.h
+++ b/src/web_service/verify_login.h
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/web_service/verify_user_jwt.cpp b/src/web_service/verify_user_jwt.cpp
new file mode 100644
index 000000000..3bff46f0a
--- /dev/null
+++ b/src/web_service/verify_user_jwt.cpp
@@ -0,0 +1,67 @@
1// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#if defined(__GNUC__) || defined(__clang__)
5#pragma GCC diagnostic push
6#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
7#endif
8#include <jwt/jwt.hpp>
9#if defined(__GNUC__) || defined(__clang__)
10#pragma GCC diagnostic pop
11#endif
12
13#include <system_error>
14#include "common/logging/log.h"
15#include "web_service/verify_user_jwt.h"
16#include "web_service/web_backend.h"
17#include "web_service/web_result.h"
18
19namespace WebService {
20
21static std::string public_key;
22std::string GetPublicKey(const std::string& host) {
23 if (public_key.empty()) {
24 Client client(host, "", ""); // no need for credentials here
25 public_key = client.GetPlain("/jwt/external/key.pem", true).returned_data;
26 if (public_key.empty()) {
27 LOG_ERROR(WebService, "Could not fetch external JWT public key, verification may fail");
28 } else {
29 LOG_INFO(WebService, "Fetched external JWT public key (size={})", public_key.size());
30 }
31 }
32 return public_key;
33}
34
35VerifyUserJWT::VerifyUserJWT(const std::string& host) : pub_key(GetPublicKey(host)) {}
36
37Network::VerifyUser::UserData VerifyUserJWT::LoadUserData(const std::string& verify_uid,
38 const std::string& token) {
39 const std::string audience = fmt::format("external-{}", verify_uid);
40 using namespace jwt::params;
41 std::error_code error;
42 auto decoded =
43 jwt::decode(token, algorithms({"rs256"}), error, secret(pub_key), issuer("yuzu-core"),
44 aud(audience), validate_iat(true), validate_jti(true));
45 if (error) {
46 LOG_INFO(WebService, "Verification failed: category={}, code={}, message={}",
47 error.category().name(), error.value(), error.message());
48 return {};
49 }
50 Network::VerifyUser::UserData user_data{};
51 if (decoded.payload().has_claim("username")) {
52 user_data.username = decoded.payload().get_claim_value<std::string>("username");
53 }
54 if (decoded.payload().has_claim("displayName")) {
55 user_data.display_name = decoded.payload().get_claim_value<std::string>("displayName");
56 }
57 if (decoded.payload().has_claim("avatarUrl")) {
58 user_data.avatar_url = decoded.payload().get_claim_value<std::string>("avatarUrl");
59 }
60 if (decoded.payload().has_claim("roles")) {
61 auto roles = decoded.payload().get_claim_value<std::vector<std::string>>("roles");
62 user_data.moderator = std::find(roles.begin(), roles.end(), "moderator") != roles.end();
63 }
64 return user_data;
65}
66
67} // namespace WebService
diff --git a/src/web_service/verify_user_jwt.h b/src/web_service/verify_user_jwt.h
new file mode 100644
index 000000000..27b0a100c
--- /dev/null
+++ b/src/web_service/verify_user_jwt.h
@@ -0,0 +1,26 @@
1// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <fmt/format.h>
7#include "network/verify_user.h"
8#include "web_service/web_backend.h"
9
10namespace WebService {
11
12std::string GetPublicKey(const std::string& host);
13
14class VerifyUserJWT final : public Network::VerifyUser::Backend {
15public:
16 VerifyUserJWT(const std::string& host);
17 ~VerifyUserJWT() = default;
18
19 Network::VerifyUser::UserData LoadUserData(const std::string& verify_uid,
20 const std::string& token) override;
21
22private:
23 std::string pub_key;
24};
25
26} // namespace WebService
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
index dce9772fe..378804c08 100644
--- a/src/web_service/web_backend.cpp
+++ b/src/web_service/web_backend.cpp
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <array> 4#include <array>
6#include <mutex> 5#include <mutex>
diff --git a/src/web_service/web_backend.h b/src/web_service/web_backend.h
index 81f58583c..11b5f558c 100644
--- a/src/web_service/web_backend.h
+++ b/src/web_service/web_backend.h
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 242867a4f..f6b389ede 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -1,3 +1,6 @@
1# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-2.0-or-later
3
1set(CMAKE_AUTOMOC ON) 4set(CMAKE_AUTOMOC ON)
2set(CMAKE_AUTORCC ON) 5set(CMAKE_AUTORCC ON)
3set(CMAKE_AUTOUIC ON) 6set(CMAKE_AUTOUIC ON)
@@ -30,8 +33,6 @@ add_executable(yuzu
30 applets/qt_web_browser_scripts.h 33 applets/qt_web_browser_scripts.h
31 bootmanager.cpp 34 bootmanager.cpp
32 bootmanager.h 35 bootmanager.h
33 check_vulkan.cpp
34 check_vulkan.h
35 compatdb.ui 36 compatdb.ui
36 compatibility_list.cpp 37 compatibility_list.cpp
37 compatibility_list.h 38 compatibility_list.h
@@ -43,6 +44,9 @@ add_executable(yuzu
43 configuration/configure_audio.cpp 44 configuration/configure_audio.cpp
44 configuration/configure_audio.h 45 configuration/configure_audio.h
45 configuration/configure_audio.ui 46 configuration/configure_audio.ui
47 configuration/configure_camera.cpp
48 configuration/configure_camera.h
49 configuration/configure_camera.ui
46 configuration/configure_cpu.cpp 50 configuration/configure_cpu.cpp
47 configuration/configure_cpu.h 51 configuration/configure_cpu.h
48 configuration/configure_cpu.ui 52 configuration/configure_cpu.ui
@@ -155,8 +159,36 @@ add_executable(yuzu
155 main.cpp 159 main.cpp
156 main.h 160 main.h
157 main.ui 161 main.ui
162 multiplayer/chat_room.cpp
163 multiplayer/chat_room.h
164 multiplayer/chat_room.ui
165 multiplayer/client_room.h
166 multiplayer/client_room.cpp
167 multiplayer/client_room.ui
168 multiplayer/direct_connect.cpp
169 multiplayer/direct_connect.h
170 multiplayer/direct_connect.ui
171 multiplayer/host_room.cpp
172 multiplayer/host_room.h
173 multiplayer/host_room.ui
174 multiplayer/lobby.cpp
175 multiplayer/lobby.h
176 multiplayer/lobby.ui
177 multiplayer/lobby_p.h
178 multiplayer/message.cpp
179 multiplayer/message.h
180 multiplayer/moderation_dialog.cpp
181 multiplayer/moderation_dialog.h
182 multiplayer/moderation_dialog.ui
183 multiplayer/state.cpp
184 multiplayer/state.h
185 multiplayer/validation.h
186 startup_checks.cpp
187 startup_checks.h
158 uisettings.cpp 188 uisettings.cpp
159 uisettings.h 189 uisettings.h
190 util/clickable_label.cpp
191 util/clickable_label.h
160 util/controller_navigation.cpp 192 util/controller_navigation.cpp
161 util/controller_navigation.h 193 util/controller_navigation.h
162 util/limitable_input_dialog.cpp 194 util/limitable_input_dialog.cpp
@@ -253,8 +285,8 @@ endif()
253 285
254create_target_directory_groups(yuzu) 286create_target_directory_groups(yuzu)
255 287
256target_link_libraries(yuzu PRIVATE common core input_common video_core) 288target_link_libraries(yuzu PRIVATE common core input_common network video_core)
257target_link_libraries(yuzu PRIVATE Boost::boost glad Qt::Widgets) 289target_link_libraries(yuzu PRIVATE Boost::boost glad Qt::Widgets Qt::Multimedia)
258target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) 290target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
259 291
260target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include) 292target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include)
@@ -297,6 +329,10 @@ if (USE_DISCORD_PRESENCE)
297 target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE) 329 target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)
298endif() 330endif()
299 331
332if (ENABLE_WEB_SERVICE)
333 target_compile_definitions(yuzu PRIVATE -DENABLE_WEB_SERVICE)
334endif()
335
300if (YUZU_USE_QT_WEB_ENGINE) 336if (YUZU_USE_QT_WEB_ENGINE)
301 target_link_libraries(yuzu PRIVATE Qt::WebEngineCore Qt::WebEngineWidgets) 337 target_link_libraries(yuzu PRIVATE Qt::WebEngineCore Qt::WebEngineWidgets)
302 target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE) 338 target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE)
diff --git a/src/yuzu/Info.plist b/src/yuzu/Info.plist
index 5f1c95d54..0eb377926 100644
--- a/src/yuzu/Info.plist
+++ b/src/yuzu/Info.plist
@@ -1,4 +1,10 @@
1<?xml version="1.0" encoding="UTF-8"?> 1<?xml version="1.0" encoding="UTF-8"?>
2
3<!--
4SPDX-FileCopyrightText: 2015 Pierre de La Morinerie <kemenaran@gmail.com>
5SPDX-License-Identifier: GPL-2.0-or-later
6-->
7
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 8<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3<plist version="1.0"> 9<plist version="1.0">
4<dict> 10<dict>
diff --git a/src/yuzu/aboutdialog.ui b/src/yuzu/aboutdialog.ui
index 1dd7b74bf..c4ffb293e 100644
--- a/src/yuzu/aboutdialog.ui
+++ b/src/yuzu/aboutdialog.ui
@@ -127,7 +127,7 @@ p, li { white-space: pre-wrap; }
127 <item> 127 <item>
128 <widget class="QLabel" name="labelLinks"> 128 <widget class="QLabel" name="labelLinks">
129 <property name="text"> 129 <property name="text">
130 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 130 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/LICENSE.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
131 </property> 131 </property>
132 <property name="openExternalLinks"> 132 <property name="openExternalLinks">
133 <bool>true</bool> 133 <bool>true</bool>
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 01acda22b..6a7f48b6f 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -1,10 +1,11 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <glad/glad.h> 4#include <glad/glad.h>
6 5
7#include <QApplication> 6#include <QApplication>
7#include <QCameraImageCapture>
8#include <QCameraInfo>
8#include <QHBoxLayout> 9#include <QHBoxLayout>
9#include <QMessageBox> 10#include <QMessageBox>
10#include <QPainter> 11#include <QPainter>
@@ -31,6 +32,7 @@
31#include "core/core.h" 32#include "core/core.h"
32#include "core/cpu_manager.h" 33#include "core/cpu_manager.h"
33#include "core/frontend/framebuffer_layout.h" 34#include "core/frontend/framebuffer_layout.h"
35#include "input_common/drivers/camera.h"
34#include "input_common/drivers/keyboard.h" 36#include "input_common/drivers/keyboard.h"
35#include "input_common/drivers/mouse.h" 37#include "input_common/drivers/mouse.h"
36#include "input_common/drivers/tas_input.h" 38#include "input_common/drivers/tas_input.h"
@@ -801,6 +803,85 @@ void GRenderWindow::TouchEndEvent() {
801 input_subsystem->GetTouchScreen()->ReleaseAllTouch(); 803 input_subsystem->GetTouchScreen()->ReleaseAllTouch();
802} 804}
803 805
806void GRenderWindow::InitializeCamera() {
807 if (!Settings::values.enable_ir_sensor) {
808 return;
809 }
810
811 bool camera_found = false;
812 const QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
813 for (const QCameraInfo& cameraInfo : cameras) {
814 if (Settings::values.ir_sensor_device.GetValue() == cameraInfo.deviceName().toStdString() ||
815 Settings::values.ir_sensor_device.GetValue() == "Auto") {
816 camera = std::make_unique<QCamera>(cameraInfo);
817 camera_found = true;
818 break;
819 }
820 }
821
822 if (!camera_found) {
823 return;
824 }
825
826 camera_capture = std::make_unique<QCameraImageCapture>(camera.get());
827 connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this,
828 &GRenderWindow::OnCameraCapture);
829 camera->unload();
830 camera->setCaptureMode(QCamera::CaptureViewfinder);
831 camera->load();
832 camera->start();
833
834 pending_camera_snapshots = 0;
835 is_virtual_camera = false;
836
837 camera_timer = std::make_unique<QTimer>();
838 connect(camera_timer.get(), &QTimer::timeout, [this] { RequestCameraCapture(); });
839 // This timer should be dependent of camera resolution 5ms for every 100 pixels
840 camera_timer->start(100);
841}
842
843void GRenderWindow::FinalizeCamera() {
844 if (camera_timer) {
845 camera_timer->stop();
846 }
847 if (camera) {
848 camera->unload();
849 }
850}
851
852void GRenderWindow::RequestCameraCapture() {
853 if (!Settings::values.enable_ir_sensor) {
854 return;
855 }
856
857 // If the camera doesn't capture, test for virtual cameras
858 if (pending_camera_snapshots > 5) {
859 is_virtual_camera = true;
860 }
861 // Virtual cameras like obs need to reset the camera every capture
862 if (is_virtual_camera) {
863 camera->stop();
864 camera->start();
865 }
866
867 pending_camera_snapshots++;
868 camera_capture->capture();
869}
870
871void GRenderWindow::OnCameraCapture(int requestId, const QImage& img) {
872 constexpr std::size_t camera_width = 320;
873 constexpr std::size_t camera_height = 240;
874 const auto converted =
875 img.scaled(camera_width, camera_height, Qt::AspectRatioMode::IgnoreAspectRatio,
876 Qt::TransformationMode::SmoothTransformation)
877 .mirrored(false, true);
878 std::vector<u32> camera_data{};
879 camera_data.resize(camera_width * camera_height);
880 std::memcpy(camera_data.data(), converted.bits(), camera_width * camera_height * sizeof(u32));
881 input_subsystem->GetCamera()->SetCameraData(camera_width, camera_height, camera_data);
882 pending_camera_snapshots = 0;
883}
884
804bool GRenderWindow::event(QEvent* event) { 885bool GRenderWindow::event(QEvent* event) {
805 if (event->type() == QEvent::TouchBegin) { 886 if (event->type() == QEvent::TouchBegin) {
806 TouchBeginEvent(static_cast<QTouchEvent*>(event)); 887 TouchBeginEvent(static_cast<QTouchEvent*>(event));
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 81fe52c0e..c45ebf1a2 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
@@ -20,6 +19,8 @@
20 19
21class GRenderWindow; 20class GRenderWindow;
22class GMainWindow; 21class GMainWindow;
22class QCamera;
23class QCameraImageCapture;
23class QKeyEvent; 24class QKeyEvent;
24 25
25namespace Core { 26namespace Core {
@@ -164,6 +165,9 @@ public:
164 void mouseReleaseEvent(QMouseEvent* event) override; 165 void mouseReleaseEvent(QMouseEvent* event) override;
165 void wheelEvent(QWheelEvent* event) override; 166 void wheelEvent(QWheelEvent* event) override;
166 167
168 void InitializeCamera();
169 void FinalizeCamera();
170
167 bool event(QEvent* event) override; 171 bool event(QEvent* event) override;
168 172
169 void focusOutEvent(QFocusEvent* event) override; 173 void focusOutEvent(QFocusEvent* event) override;
@@ -207,6 +211,9 @@ private:
207 void TouchUpdateEvent(const QTouchEvent* event); 211 void TouchUpdateEvent(const QTouchEvent* event);
208 void TouchEndEvent(); 212 void TouchEndEvent();
209 213
214 void RequestCameraCapture();
215 void OnCameraCapture(int requestId, const QImage& img);
216
210 void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override; 217 void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
211 218
212 bool InitializeOpenGL(); 219 bool InitializeOpenGL();
@@ -232,6 +239,12 @@ private:
232 bool first_frame = false; 239 bool first_frame = false;
233 InputCommon::TasInput::TasState last_tas_state; 240 InputCommon::TasInput::TasState last_tas_state;
234 241
242 bool is_virtual_camera;
243 int pending_camera_snapshots;
244 std::unique_ptr<QCamera> camera;
245 std::unique_ptr<QCameraImageCapture> camera_capture;
246 std::unique_ptr<QTimer> camera_timer;
247
235 Core::System& system; 248 Core::System& system;
236 249
237protected: 250protected:
diff --git a/src/yuzu/check_vulkan.cpp b/src/yuzu/check_vulkan.cpp
deleted file mode 100644
index e6d66ab34..000000000
--- a/src/yuzu/check_vulkan.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "video_core/vulkan_common/vulkan_wrapper.h"
5
6#include <filesystem>
7#include <fstream>
8#include "common/fs/fs.h"
9#include "common/fs/path_util.h"
10#include "common/logging/log.h"
11#include "video_core/vulkan_common/vulkan_instance.h"
12#include "video_core/vulkan_common/vulkan_library.h"
13#include "yuzu/check_vulkan.h"
14#include "yuzu/uisettings.h"
15
16constexpr char TEMP_FILE_NAME[] = "vulkan_check";
17
18bool CheckVulkan() {
19 if (UISettings::values.has_broken_vulkan) {
20 return true;
21 }
22
23 LOG_DEBUG(Frontend, "Checking presence of Vulkan");
24
25 const auto fs_config_loc = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir);
26 const auto temp_file_loc = fs_config_loc / TEMP_FILE_NAME;
27
28 if (std::filesystem::exists(temp_file_loc)) {
29 LOG_WARNING(Frontend, "Detected recovery from previous failed Vulkan initialization");
30
31 UISettings::values.has_broken_vulkan = true;
32 std::filesystem::remove(temp_file_loc);
33 return false;
34 }
35
36 std::ofstream temp_file_handle(temp_file_loc);
37 temp_file_handle.close();
38
39 try {
40 Vulkan::vk::InstanceDispatch dld;
41 const Common::DynamicLibrary library = Vulkan::OpenLibrary();
42 const Vulkan::vk::Instance instance =
43 Vulkan::CreateInstance(library, dld, VK_API_VERSION_1_0);
44
45 } catch (const Vulkan::vk::Exception& exception) {
46 LOG_ERROR(Frontend, "Failed to initialize Vulkan: {}", exception.what());
47 // Don't set has_broken_vulkan to true here: we care when loading Vulkan crashes the
48 // application, not when we can handle it.
49 }
50
51 std::filesystem::remove(temp_file_loc);
52 return true;
53}
diff --git a/src/yuzu/check_vulkan.h b/src/yuzu/check_vulkan.h
deleted file mode 100644
index e4ea93582..000000000
--- a/src/yuzu/check_vulkan.h
+++ /dev/null
@@ -1,6 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6bool CheckVulkan();
diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp
index 2442bb3c3..f46fff340 100644
--- a/src/yuzu/compatdb.cpp
+++ b/src/yuzu/compatdb.cpp
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <QButtonGroup> 4#include <QButtonGroup>
6#include <QMessageBox> 5#include <QMessageBox>
diff --git a/src/yuzu/compatdb.h b/src/yuzu/compatdb.h
index e2b2522bd..3252fc47a 100644
--- a/src/yuzu/compatdb.h
+++ b/src/yuzu/compatdb.h
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 2840bc5eb..58f1239bf 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <array> 4#include <array>
6#include <QKeySequence> 5#include <QKeySequence>
@@ -11,6 +10,7 @@
11#include "core/hle/service/acc/profile_manager.h" 10#include "core/hle/service/acc/profile_manager.h"
12#include "core/hle/service/hid/controllers/npad.h" 11#include "core/hle/service/hid/controllers/npad.h"
13#include "input_common/main.h" 12#include "input_common/main.h"
13#include "network/network.h"
14#include "yuzu/configuration/config.h" 14#include "yuzu/configuration/config.h"
15 15
16namespace FS = Common::FS; 16namespace FS = Common::FS;
@@ -368,6 +368,11 @@ void Config::ReadHidbusValues() {
368 } 368 }
369} 369}
370 370
371void Config::ReadIrCameraValues() {
372 ReadBasicSetting(Settings::values.enable_ir_sensor);
373 ReadBasicSetting(Settings::values.ir_sensor_device);
374}
375
371void Config::ReadAudioValues() { 376void Config::ReadAudioValues() {
372 qt_config->beginGroup(QStringLiteral("Audio")); 377 qt_config->beginGroup(QStringLiteral("Audio"));
373 378
@@ -393,6 +398,7 @@ void Config::ReadControlValues() {
393 ReadTouchscreenValues(); 398 ReadTouchscreenValues();
394 ReadMotionTouchValues(); 399 ReadMotionTouchValues();
395 ReadHidbusValues(); 400 ReadHidbusValues();
401 ReadIrCameraValues();
396 402
397#ifdef _WIN32 403#ifdef _WIN32
398 ReadBasicSetting(Settings::values.enable_raw_input); 404 ReadBasicSetting(Settings::values.enable_raw_input);
@@ -682,12 +688,6 @@ void Config::ReadRendererValues() {
682 ReadGlobalSetting(Settings::values.bg_green); 688 ReadGlobalSetting(Settings::values.bg_green);
683 ReadGlobalSetting(Settings::values.bg_blue); 689 ReadGlobalSetting(Settings::values.bg_blue);
684 690
685 if (!global && UISettings::values.has_broken_vulkan &&
686 Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::Vulkan &&
687 !Settings::values.renderer_backend.UsingGlobal()) {
688 Settings::values.renderer_backend.SetGlobal(true);
689 }
690
691 if (global) { 691 if (global) {
692 ReadBasicSetting(Settings::values.renderer_debug); 692 ReadBasicSetting(Settings::values.renderer_debug);
693 ReadBasicSetting(Settings::values.renderer_shader_feedback); 693 ReadBasicSetting(Settings::values.renderer_shader_feedback);
@@ -794,6 +794,7 @@ void Config::ReadUIValues() {
794 ReadPathValues(); 794 ReadPathValues();
795 ReadScreenshotValues(); 795 ReadScreenshotValues();
796 ReadShortcutValues(); 796 ReadShortcutValues();
797 ReadMultiplayerValues();
797 798
798 ReadBasicSetting(UISettings::values.single_window_mode); 799 ReadBasicSetting(UISettings::values.single_window_mode);
799 ReadBasicSetting(UISettings::values.fullscreen); 800 ReadBasicSetting(UISettings::values.fullscreen);
@@ -807,7 +808,6 @@ void Config::ReadUIValues() {
807 ReadBasicSetting(UISettings::values.pause_when_in_background); 808 ReadBasicSetting(UISettings::values.pause_when_in_background);
808 ReadBasicSetting(UISettings::values.mute_when_in_background); 809 ReadBasicSetting(UISettings::values.mute_when_in_background);
809 ReadBasicSetting(UISettings::values.hide_mouse); 810 ReadBasicSetting(UISettings::values.hide_mouse);
810 ReadBasicSetting(UISettings::values.has_broken_vulkan);
811 ReadBasicSetting(UISettings::values.disable_web_applet); 811 ReadBasicSetting(UISettings::values.disable_web_applet);
812 812
813 qt_config->endGroup(); 813 qt_config->endGroup();
@@ -861,6 +861,42 @@ void Config::ReadWebServiceValues() {
861 qt_config->endGroup(); 861 qt_config->endGroup();
862} 862}
863 863
864void Config::ReadMultiplayerValues() {
865 qt_config->beginGroup(QStringLiteral("Multiplayer"));
866
867 ReadBasicSetting(UISettings::values.multiplayer_nickname);
868 ReadBasicSetting(UISettings::values.multiplayer_ip);
869 ReadBasicSetting(UISettings::values.multiplayer_port);
870 ReadBasicSetting(UISettings::values.multiplayer_room_nickname);
871 ReadBasicSetting(UISettings::values.multiplayer_room_name);
872 ReadBasicSetting(UISettings::values.multiplayer_room_port);
873 ReadBasicSetting(UISettings::values.multiplayer_host_type);
874 ReadBasicSetting(UISettings::values.multiplayer_port);
875 ReadBasicSetting(UISettings::values.multiplayer_max_player);
876 ReadBasicSetting(UISettings::values.multiplayer_game_id);
877 ReadBasicSetting(UISettings::values.multiplayer_room_description);
878
879 // Read ban list back
880 int size = qt_config->beginReadArray(QStringLiteral("username_ban_list"));
881 UISettings::values.multiplayer_ban_list.first.resize(size);
882 for (int i = 0; i < size; ++i) {
883 qt_config->setArrayIndex(i);
884 UISettings::values.multiplayer_ban_list.first[i] =
885 ReadSetting(QStringLiteral("username")).toString().toStdString();
886 }
887 qt_config->endArray();
888 size = qt_config->beginReadArray(QStringLiteral("ip_ban_list"));
889 UISettings::values.multiplayer_ban_list.second.resize(size);
890 for (int i = 0; i < size; ++i) {
891 qt_config->setArrayIndex(i);
892 UISettings::values.multiplayer_ban_list.second[i] =
893 ReadSetting(QStringLiteral("ip")).toString().toStdString();
894 }
895 qt_config->endArray();
896
897 qt_config->endGroup();
898}
899
864void Config::ReadValues() { 900void Config::ReadValues() {
865 if (global) { 901 if (global) {
866 ReadControlValues(); 902 ReadControlValues();
@@ -877,6 +913,7 @@ void Config::ReadValues() {
877 ReadRendererValues(); 913 ReadRendererValues();
878 ReadAudioValues(); 914 ReadAudioValues();
879 ReadSystemValues(); 915 ReadSystemValues();
916 ReadMultiplayerValues();
880} 917}
881 918
882void Config::SavePlayerValue(std::size_t player_index) { 919void Config::SavePlayerValue(std::size_t player_index) {
@@ -1005,6 +1042,11 @@ void Config::SaveHidbusValues() {
1005 QString::fromStdString(default_param)); 1042 QString::fromStdString(default_param));
1006} 1043}
1007 1044
1045void Config::SaveIrCameraValues() {
1046 WriteBasicSetting(Settings::values.enable_ir_sensor);
1047 WriteBasicSetting(Settings::values.ir_sensor_device);
1048}
1049
1008void Config::SaveValues() { 1050void Config::SaveValues() {
1009 if (global) { 1051 if (global) {
1010 SaveControlValues(); 1052 SaveControlValues();
@@ -1021,6 +1063,7 @@ void Config::SaveValues() {
1021 SaveRendererValues(); 1063 SaveRendererValues();
1022 SaveAudioValues(); 1064 SaveAudioValues();
1023 SaveSystemValues(); 1065 SaveSystemValues();
1066 SaveMultiplayerValues();
1024} 1067}
1025 1068
1026void Config::SaveAudioValues() { 1069void Config::SaveAudioValues() {
@@ -1047,6 +1090,7 @@ void Config::SaveControlValues() {
1047 SaveTouchscreenValues(); 1090 SaveTouchscreenValues();
1048 SaveMotionTouchValues(); 1091 SaveMotionTouchValues();
1049 SaveHidbusValues(); 1092 SaveHidbusValues();
1093 SaveIrCameraValues();
1050 1094
1051 WriteGlobalSetting(Settings::values.use_docked_mode); 1095 WriteGlobalSetting(Settings::values.use_docked_mode);
1052 WriteGlobalSetting(Settings::values.vibration_enabled); 1096 WriteGlobalSetting(Settings::values.vibration_enabled);
@@ -1342,6 +1386,7 @@ void Config::SaveUIValues() {
1342 SavePathValues(); 1386 SavePathValues();
1343 SaveScreenshotValues(); 1387 SaveScreenshotValues();
1344 SaveShortcutValues(); 1388 SaveShortcutValues();
1389 SaveMultiplayerValues();
1345 1390
1346 WriteBasicSetting(UISettings::values.single_window_mode); 1391 WriteBasicSetting(UISettings::values.single_window_mode);
1347 WriteBasicSetting(UISettings::values.fullscreen); 1392 WriteBasicSetting(UISettings::values.fullscreen);
@@ -1355,7 +1400,6 @@ void Config::SaveUIValues() {
1355 WriteBasicSetting(UISettings::values.pause_when_in_background); 1400 WriteBasicSetting(UISettings::values.pause_when_in_background);
1356 WriteBasicSetting(UISettings::values.mute_when_in_background); 1401 WriteBasicSetting(UISettings::values.mute_when_in_background);
1357 WriteBasicSetting(UISettings::values.hide_mouse); 1402 WriteBasicSetting(UISettings::values.hide_mouse);
1358 WriteBasicSetting(UISettings::values.has_broken_vulkan);
1359 WriteBasicSetting(UISettings::values.disable_web_applet); 1403 WriteBasicSetting(UISettings::values.disable_web_applet);
1360 1404
1361 qt_config->endGroup(); 1405 qt_config->endGroup();
@@ -1407,6 +1451,40 @@ void Config::SaveWebServiceValues() {
1407 qt_config->endGroup(); 1451 qt_config->endGroup();
1408} 1452}
1409 1453
1454void Config::SaveMultiplayerValues() {
1455 qt_config->beginGroup(QStringLiteral("Multiplayer"));
1456
1457 WriteBasicSetting(UISettings::values.multiplayer_nickname);
1458 WriteBasicSetting(UISettings::values.multiplayer_ip);
1459 WriteBasicSetting(UISettings::values.multiplayer_port);
1460 WriteBasicSetting(UISettings::values.multiplayer_room_nickname);
1461 WriteBasicSetting(UISettings::values.multiplayer_room_name);
1462 WriteBasicSetting(UISettings::values.multiplayer_room_port);
1463 WriteBasicSetting(UISettings::values.multiplayer_host_type);
1464 WriteBasicSetting(UISettings::values.multiplayer_port);
1465 WriteBasicSetting(UISettings::values.multiplayer_max_player);
1466 WriteBasicSetting(UISettings::values.multiplayer_game_id);
1467 WriteBasicSetting(UISettings::values.multiplayer_room_description);
1468
1469 // Write ban list
1470 qt_config->beginWriteArray(QStringLiteral("username_ban_list"));
1471 for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.first.size(); ++i) {
1472 qt_config->setArrayIndex(static_cast<int>(i));
1473 WriteSetting(QStringLiteral("username"),
1474 QString::fromStdString(UISettings::values.multiplayer_ban_list.first[i]));
1475 }
1476 qt_config->endArray();
1477 qt_config->beginWriteArray(QStringLiteral("ip_ban_list"));
1478 for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.second.size(); ++i) {
1479 qt_config->setArrayIndex(static_cast<int>(i));
1480 WriteSetting(QStringLiteral("ip"),
1481 QString::fromStdString(UISettings::values.multiplayer_ban_list.second[i]));
1482 }
1483 qt_config->endArray();
1484
1485 qt_config->endGroup();
1486}
1487
1410QVariant Config::ReadSetting(const QString& name) const { 1488QVariant Config::ReadSetting(const QString& name) const {
1411 return qt_config->value(name); 1489 return qt_config->value(name);
1412} 1490}
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index d511b3dbd..486ceea94 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
@@ -68,6 +67,7 @@ private:
68 void ReadTouchscreenValues(); 67 void ReadTouchscreenValues();
69 void ReadMotionTouchValues(); 68 void ReadMotionTouchValues();
70 void ReadHidbusValues(); 69 void ReadHidbusValues();
70 void ReadIrCameraValues();
71 71
72 // Read functions bases off the respective config section names. 72 // Read functions bases off the respective config section names.
73 void ReadAudioValues(); 73 void ReadAudioValues();
@@ -88,6 +88,7 @@ private:
88 void ReadUIGamelistValues(); 88 void ReadUIGamelistValues();
89 void ReadUILayoutValues(); 89 void ReadUILayoutValues();
90 void ReadWebServiceValues(); 90 void ReadWebServiceValues();
91 void ReadMultiplayerValues();
91 92
92 void SaveValues(); 93 void SaveValues();
93 void SavePlayerValue(std::size_t player_index); 94 void SavePlayerValue(std::size_t player_index);
@@ -96,6 +97,7 @@ private:
96 void SaveTouchscreenValues(); 97 void SaveTouchscreenValues();
97 void SaveMotionTouchValues(); 98 void SaveMotionTouchValues();
98 void SaveHidbusValues(); 99 void SaveHidbusValues();
100 void SaveIrCameraValues();
99 101
100 // Save functions based off the respective config section names. 102 // Save functions based off the respective config section names.
101 void SaveAudioValues(); 103 void SaveAudioValues();
@@ -116,6 +118,7 @@ private:
116 void SaveUIGamelistValues(); 118 void SaveUIGamelistValues();
117 void SaveUILayoutValues(); 119 void SaveUILayoutValues();
118 void SaveWebServiceValues(); 120 void SaveWebServiceValues();
121 void SaveMultiplayerValues();
119 122
120 /** 123 /**
121 * Reads a setting from the qt_config. 124 * Reads a setting from the qt_config.
diff --git a/src/yuzu/configuration/configuration_shared.cpp b/src/yuzu/configuration/configuration_shared.cpp
index dd4959417..97fb664bf 100644
--- a/src/yuzu/configuration/configuration_shared.cpp
+++ b/src/yuzu/configuration/configuration_shared.cpp
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <QCheckBox> 4#include <QCheckBox>
6#include <QObject> 5#include <QObject>
diff --git a/src/yuzu/configuration/configuration_shared.h b/src/yuzu/configuration/configuration_shared.h
index 56800b6ff..e597dcdb5 100644
--- a/src/yuzu/configuration/configuration_shared.h
+++ b/src/yuzu/configuration/configuration_shared.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/configuration/configure_camera.cpp b/src/yuzu/configuration/configure_camera.cpp
new file mode 100644
index 000000000..73cdcf3f2
--- /dev/null
+++ b/src/yuzu/configuration/configure_camera.cpp
@@ -0,0 +1,138 @@
1// Text : Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include <memory>
5#include <QCameraImageCapture>
6#include <QCameraInfo>
7#include <QStandardItemModel>
8#include <QTimer>
9
10#include "input_common/drivers/camera.h"
11#include "input_common/main.h"
12#include "ui_configure_camera.h"
13#include "yuzu/configuration/config.h"
14#include "yuzu/configuration/configure_camera.h"
15
16ConfigureCamera::ConfigureCamera(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_)
17 : QDialog(parent), input_subsystem{input_subsystem_},
18 ui(std::make_unique<Ui::ConfigureCamera>()) {
19 ui->setupUi(this);
20
21 connect(ui->restore_defaults_button, &QPushButton::clicked, this,
22 &ConfigureCamera::RestoreDefaults);
23 connect(ui->preview_button, &QPushButton::clicked, this, &ConfigureCamera::PreviewCamera);
24
25 auto blank_image = QImage(320, 240, QImage::Format::Format_RGB32);
26 blank_image.fill(Qt::black);
27 DisplayCapturedFrame(0, blank_image);
28
29 LoadConfiguration();
30 resize(0, 0);
31}
32
33ConfigureCamera::~ConfigureCamera() = default;
34
35void ConfigureCamera::PreviewCamera() {
36 const auto index = ui->ir_sensor_combo_box->currentIndex();
37 bool camera_found = false;
38 const QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
39 for (const QCameraInfo& cameraInfo : cameras) {
40 if (input_devices[index] == cameraInfo.deviceName().toStdString() ||
41 input_devices[index] == "Auto") {
42 LOG_INFO(Frontend, "Selected Camera {} {}", cameraInfo.description().toStdString(),
43 cameraInfo.deviceName().toStdString());
44 camera = std::make_unique<QCamera>(cameraInfo);
45 camera_found = true;
46 break;
47 }
48 }
49
50 // Clear previous frame
51 auto blank_image = QImage(320, 240, QImage::Format::Format_RGB32);
52 blank_image.fill(Qt::black);
53 DisplayCapturedFrame(0, blank_image);
54
55 if (!camera_found) {
56 return;
57 }
58
59 camera_capture = std::make_unique<QCameraImageCapture>(camera.get());
60 connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this,
61 &ConfigureCamera::DisplayCapturedFrame);
62 camera->unload();
63 camera->setCaptureMode(QCamera::CaptureViewfinder);
64 camera->load();
65 camera->start();
66
67 pending_snapshots = 0;
68 is_virtual_camera = false;
69
70 camera_timer = std::make_unique<QTimer>();
71 connect(camera_timer.get(), &QTimer::timeout, [this] {
72 // If the camera doesn't capture, test for virtual cameras
73 if (pending_snapshots > 5) {
74 is_virtual_camera = true;
75 }
76 // Virtual cameras like obs need to reset the camera every capture
77 if (is_virtual_camera) {
78 camera->stop();
79 camera->start();
80 }
81 pending_snapshots++;
82 camera_capture->capture();
83 });
84
85 camera_timer->start(250);
86}
87
88void ConfigureCamera::DisplayCapturedFrame(int requestId, const QImage& img) {
89 LOG_INFO(Frontend, "ImageCaptured {} {}", img.width(), img.height());
90 const auto converted = img.scaled(320, 240, Qt::AspectRatioMode::IgnoreAspectRatio,
91 Qt::TransformationMode::SmoothTransformation);
92 ui->preview_box->setPixmap(QPixmap::fromImage(converted));
93 pending_snapshots = 0;
94}
95
96void ConfigureCamera::changeEvent(QEvent* event) {
97 if (event->type() == QEvent::LanguageChange) {
98 RetranslateUI();
99 }
100
101 QDialog::changeEvent(event);
102}
103
104void ConfigureCamera::RetranslateUI() {
105 ui->retranslateUi(this);
106}
107
108void ConfigureCamera::ApplyConfiguration() {
109 const auto index = ui->ir_sensor_combo_box->currentIndex();
110 Settings::values.ir_sensor_device.SetValue(input_devices[index]);
111}
112
113void ConfigureCamera::LoadConfiguration() {
114 input_devices.clear();
115 ui->ir_sensor_combo_box->clear();
116 input_devices.push_back("Auto");
117 ui->ir_sensor_combo_box->addItem(tr("Auto"));
118 const auto cameras = QCameraInfo::availableCameras();
119 for (const QCameraInfo& cameraInfo : cameras) {
120 input_devices.push_back(cameraInfo.deviceName().toStdString());
121 ui->ir_sensor_combo_box->addItem(cameraInfo.description());
122 }
123
124 const auto current_device = Settings::values.ir_sensor_device.GetValue();
125
126 const auto devices_it = std::find_if(
127 input_devices.begin(), input_devices.end(),
128 [current_device](const std::string& device) { return device == current_device; });
129 const int device_index =
130 devices_it != input_devices.end()
131 ? static_cast<int>(std::distance(input_devices.begin(), devices_it))
132 : 0;
133 ui->ir_sensor_combo_box->setCurrentIndex(device_index);
134}
135
136void ConfigureCamera::RestoreDefaults() {
137 ui->ir_sensor_combo_box->setCurrentIndex(0);
138}
diff --git a/src/yuzu/configuration/configure_camera.h b/src/yuzu/configuration/configure_camera.h
new file mode 100644
index 000000000..db9833b5c
--- /dev/null
+++ b/src/yuzu/configuration/configure_camera.h
@@ -0,0 +1,54 @@
1// Text : Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <QDialog>
8
9class QTimer;
10class QCamera;
11class QCameraImageCapture;
12
13namespace InputCommon {
14class InputSubsystem;
15} // namespace InputCommon
16
17namespace Ui {
18class ConfigureCamera;
19}
20
21class ConfigureCamera : public QDialog {
22 Q_OBJECT
23
24public:
25 explicit ConfigureCamera(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_);
26 ~ConfigureCamera() override;
27
28 void ApplyConfiguration();
29
30private:
31 void changeEvent(QEvent* event) override;
32 void RetranslateUI();
33
34 /// Load configuration settings.
35 void LoadConfiguration();
36
37 /// Restore all buttons to their default values.
38 void RestoreDefaults();
39
40 void DisplayCapturedFrame(int requestId, const QImage& img);
41
42 /// Loads and signals the current selected camera to display a frame
43 void PreviewCamera();
44
45 InputCommon::InputSubsystem* input_subsystem;
46
47 bool is_virtual_camera;
48 int pending_snapshots;
49 std::unique_ptr<QCamera> camera;
50 std::unique_ptr<QCameraImageCapture> camera_capture;
51 std::unique_ptr<QTimer> camera_timer;
52 std::vector<std::string> input_devices;
53 std::unique_ptr<Ui::ConfigureCamera> ui;
54};
diff --git a/src/yuzu/configuration/configure_camera.ui b/src/yuzu/configuration/configure_camera.ui
new file mode 100644
index 000000000..976a9b1ec
--- /dev/null
+++ b/src/yuzu/configuration/configure_camera.ui
@@ -0,0 +1,170 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigureCamera</class>
4 <widget class="QDialog" name="ConfigureCamera">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>298</width>
10 <height>339</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Configure Infrared Camera</string>
15 </property>
16 <layout class="QVBoxLayout" name="verticalLayout">
17 <item>
18 <widget class="QLabel" name="label_2">
19 <property name="minimumSize">
20 <size>
21 <width>280</width>
22 <height>0</height>
23 </size>
24 </property>
25 <property name="text">
26 <string>Select where the image of the emulated camera comes from. It may be a virtual camera or a real camera.</string>
27 </property>
28 <property name="wordWrap">
29 <bool>true</bool>
30 </property>
31 </widget>
32 </item>
33 <item>
34 <spacer name="verticalSpacer_2">
35 <property name="orientation">
36 <enum>Qt::Vertical</enum>
37 </property>
38 <property name="sizeType">
39 <enum>QSizePolicy::Fixed</enum>
40 </property>
41 <property name="sizeHint" stdset="0">
42 <size>
43 <width>20</width>
44 <height>20</height>
45 </size>
46 </property>
47 </spacer>
48 </item>
49 <item>
50 <widget class="QGroupBox" name="gridGroupBox">
51 <property name="title">
52 <string>Camera Image Source:</string>
53 </property>
54 <layout class="QGridLayout" name="gridLayout">
55 <item row="0" column="0">
56 <spacer name="horizontalSpacer">
57 <property name="orientation">
58 <enum>Qt::Horizontal</enum>
59 </property>
60 <property name="sizeHint" stdset="0">
61 <size>
62 <width>40</width>
63 <height>20</height>
64 </size>
65 </property>
66 </spacer>
67 </item>
68 <item row="0" column="1">
69 <widget class="QLabel" name="label_3">
70 <property name="text">
71 <string>Input device:</string>
72 </property>
73 </widget>
74 </item>
75 <item row="0" column="2">
76 <widget class="QComboBox" name="ir_sensor_combo_box"/>
77 </item>
78 <item row="0" column="3">
79 <spacer name="horizontalSpacer_2">
80 <property name="orientation">
81 <enum>Qt::Horizontal</enum>
82 </property>
83 <property name="sizeHint" stdset="0">
84 <size>
85 <width>40</width>
86 <height>20</height>
87 </size>
88 </property>
89 </spacer>
90 </item>
91 </layout>
92 </widget>
93 </item><item>
94 <widget class="QGroupBox" name="previewBox">
95 <property name="title">
96 <string>Preview</string>
97 </property>
98 <layout class="QVBoxLayout" name="verticalLayout_3">
99 <item>
100 <widget class="QLabel" name="preview_box">
101 <property name="minimumSize">
102 <size>
103 <width>320</width>
104 <height>240</height>
105 </size>
106 </property>
107 <property name="toolTip">
108 <string>Resolution: 320*240</string>
109 </property>
110 </widget>
111 </item>
112 <item>
113 <widget class="QPushButton" name="preview_button">
114 <property name="text">
115 <string>Click to preview</string>
116 </property>
117 </widget>
118 </item>
119 </layout>
120 </widget>
121 </item>
122 <item>
123 <spacer name="verticalSpacer">
124 <property name="orientation">
125 <enum>Qt::Vertical</enum>
126 </property>
127 <property name="sizeHint" stdset="0">
128 <size>
129 <width>20</width>
130 <height>40</height>
131 </size>
132 </property>
133 </spacer>
134 </item>
135 <item>
136 <layout class="QHBoxLayout" name="horizontalLayout">
137 <item>
138 <widget class="QPushButton" name="restore_defaults_button">
139 <property name="text">
140 <string>Restore Defaults</string>
141 </property>
142 </widget>
143 </item>
144 <item>
145 <widget class="QDialogButtonBox" name="buttonBox">
146 <property name="standardButtons">
147 <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
148 </property>
149 </widget>
150 </item>
151 </layout>
152 </item>
153 </layout>
154 </widget>
155 <resources/>
156 <connections>
157 <connection>
158 <sender>buttonBox</sender>
159 <signal>accepted()</signal>
160 <receiver>ConfigureCamera</receiver>
161 <slot>accept()</slot>
162 </connection>
163 <connection>
164 <sender>buttonBox</sender>
165 <signal>rejected()</signal>
166 <receiver>ConfigureCamera</receiver>
167 <slot>reject()</slot>
168 </connection>
169 </connections>
170</ui>
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index 84808f678..e16d127a8 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <QDesktopServices> 4#include <QDesktopServices>
6#include <QUrl> 5#include <QUrl>
diff --git a/src/yuzu/configuration/configure_debug.h b/src/yuzu/configuration/configure_debug.h
index 73f71c9e3..64d68ab8f 100644
--- a/src/yuzu/configuration/configure_debug.h
+++ b/src/yuzu/configuration/configure_debug.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index e99657bd6..4301313cf 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <memory> 4#include <memory>
6#include "common/logging/log.h" 5#include "common/logging/log.h"
@@ -29,9 +28,10 @@
29 28
30ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, 29ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
31 InputCommon::InputSubsystem* input_subsystem, 30 InputCommon::InputSubsystem* input_subsystem,
32 Core::System& system_) 31 Core::System& system_, bool enable_web_config)
33 : QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()}, registry{registry_}, 32 : QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()},
34 system{system_}, audio_tab{std::make_unique<ConfigureAudio>(system_, this)}, 33 registry(registry_), system{system_}, audio_tab{std::make_unique<ConfigureAudio>(system_,
34 this)},
35 cpu_tab{std::make_unique<ConfigureCpu>(system_, this)}, 35 cpu_tab{std::make_unique<ConfigureCpu>(system_, this)},
36 debug_tab_tab{std::make_unique<ConfigureDebugTab>(system_, this)}, 36 debug_tab_tab{std::make_unique<ConfigureDebugTab>(system_, this)},
37 filesystem_tab{std::make_unique<ConfigureFilesystem>(this)}, 37 filesystem_tab{std::make_unique<ConfigureFilesystem>(this)},
@@ -64,6 +64,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
64 ui->tabWidget->addTab(ui_tab.get(), tr("Game List")); 64 ui->tabWidget->addTab(ui_tab.get(), tr("Game List"));
65 ui->tabWidget->addTab(web_tab.get(), tr("Web")); 65 ui->tabWidget->addTab(web_tab.get(), tr("Web"));
66 66
67 web_tab->SetWebServiceConfigEnabled(enable_web_config);
67 hotkeys_tab->Populate(registry); 68 hotkeys_tab->Populate(registry);
68 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); 69 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
69 70
diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h
index 12cf25daf..1f724834a 100644
--- a/src/yuzu/configuration/configure_dialog.h
+++ b/src/yuzu/configuration/configure_dialog.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
@@ -41,7 +40,8 @@ class ConfigureDialog : public QDialog {
41 40
42public: 41public:
43 explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, 42 explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
44 InputCommon::InputSubsystem* input_subsystem, Core::System& system_); 43 InputCommon::InputSubsystem* input_subsystem, Core::System& system_,
44 bool enable_web_config = true);
45 ~ConfigureDialog() override; 45 ~ConfigureDialog() override;
46 46
47 void ApplyConfiguration(); 47 void ApplyConfiguration();
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 2a446205b..7ade01ba6 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <functional> 4#include <functional>
6#include <utility> 5#include <utility>
diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h
index b6f3bb5ed..a090c1a3f 100644
--- a/src/yuzu/configuration/configure_general.h
+++ b/src/yuzu/configuration/configure_general.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index 85f34dc35..87e5d0f48 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5// Include this early to include Vulkan headers how we want to 4// Include this early to include Vulkan headers how we want to
6#include "video_core/vulkan_common/vulkan_wrapper.h" 5#include "video_core/vulkan_common/vulkan_wrapper.h"
@@ -58,24 +57,9 @@ ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* paren
58 UpdateBackgroundColorButton(new_bg_color); 57 UpdateBackgroundColorButton(new_bg_color);
59 }); 58 });
60 59
61 connect(ui->button_check_vulkan, &QAbstractButton::clicked, this, [this] { 60 ui->api->setEnabled(!UISettings::values.has_broken_vulkan);
62 UISettings::values.has_broken_vulkan = false; 61 ui->api_widget->setEnabled(!UISettings::values.has_broken_vulkan ||
63 62 Settings::IsConfiguringGlobal());
64 if (RetrieveVulkanDevices()) {
65 ui->api->setEnabled(true);
66 ui->button_check_vulkan->hide();
67
68 for (const auto& device : vulkan_devices) {
69 ui->device->addItem(device);
70 }
71 } else {
72 UISettings::values.has_broken_vulkan = true;
73 }
74 });
75
76 ui->api->setEnabled(!UISettings::values.has_broken_vulkan.GetValue());
77 ui->button_check_vulkan->setVisible(UISettings::values.has_broken_vulkan.GetValue());
78
79 ui->bg_label->setVisible(Settings::IsConfiguringGlobal()); 63 ui->bg_label->setVisible(Settings::IsConfiguringGlobal());
80 ui->bg_combobox->setVisible(!Settings::IsConfiguringGlobal()); 64 ui->bg_combobox->setVisible(!Settings::IsConfiguringGlobal());
81} 65}
@@ -315,7 +299,7 @@ void ConfigureGraphics::UpdateAPILayout() {
315 vulkan_device = Settings::values.vulkan_device.GetValue(true); 299 vulkan_device = Settings::values.vulkan_device.GetValue(true);
316 shader_backend = Settings::values.shader_backend.GetValue(true); 300 shader_backend = Settings::values.shader_backend.GetValue(true);
317 ui->device_widget->setEnabled(false); 301 ui->device_widget->setEnabled(false);
318 ui->backend_widget->setEnabled(UISettings::values.has_broken_vulkan.GetValue()); 302 ui->backend_widget->setEnabled(false);
319 } else { 303 } else {
320 vulkan_device = Settings::values.vulkan_device.GetValue(); 304 vulkan_device = Settings::values.vulkan_device.GetValue();
321 shader_backend = Settings::values.shader_backend.GetValue(); 305 shader_backend = Settings::values.shader_backend.GetValue();
@@ -337,9 +321,9 @@ void ConfigureGraphics::UpdateAPILayout() {
337 } 321 }
338} 322}
339 323
340bool ConfigureGraphics::RetrieveVulkanDevices() try { 324void ConfigureGraphics::RetrieveVulkanDevices() try {
341 if (UISettings::values.has_broken_vulkan) { 325 if (UISettings::values.has_broken_vulkan) {
342 return false; 326 return;
343 } 327 }
344 328
345 using namespace Vulkan; 329 using namespace Vulkan;
@@ -355,11 +339,8 @@ bool ConfigureGraphics::RetrieveVulkanDevices() try {
355 const std::string name = vk::PhysicalDevice(device, dld).GetProperties().deviceName; 339 const std::string name = vk::PhysicalDevice(device, dld).GetProperties().deviceName;
356 vulkan_devices.push_back(QString::fromStdString(name)); 340 vulkan_devices.push_back(QString::fromStdString(name));
357 } 341 }
358
359 return true;
360} catch (const Vulkan::vk::Exception& exception) { 342} catch (const Vulkan::vk::Exception& exception) {
361 LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what()); 343 LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what());
362 return false;
363} 344}
364 345
365Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { 346Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
@@ -440,11 +421,4 @@ void ConfigureGraphics::SetupPerGameUI() {
440 ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true))); 421 ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true)));
441 ConfigurationShared::InsertGlobalItem( 422 ConfigurationShared::InsertGlobalItem(
442 ui->nvdec_emulation, static_cast<int>(Settings::values.nvdec_emulation.GetValue(true))); 423 ui->nvdec_emulation, static_cast<int>(Settings::values.nvdec_emulation.GetValue(true)));
443
444 if (UISettings::values.has_broken_vulkan) {
445 ui->backend_widget->setEnabled(true);
446 ConfigurationShared::SetColoredComboBox(
447 ui->backend, ui->backend_widget,
448 static_cast<int>(Settings::values.shader_backend.GetValue(true)));
449 }
450} 424}
diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h
index 8438f0187..70034eb1b 100644
--- a/src/yuzu/configuration/configure_graphics.h
+++ b/src/yuzu/configuration/configure_graphics.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
@@ -41,7 +40,7 @@ private:
41 void UpdateDeviceSelection(int device); 40 void UpdateDeviceSelection(int device);
42 void UpdateShaderBackendSelection(int backend); 41 void UpdateShaderBackendSelection(int backend);
43 42
44 bool RetrieveVulkanDevices(); 43 void RetrieveVulkanDevices();
45 44
46 void SetupPerGameUI(); 45 void SetupPerGameUI();
47 46
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index 2f94c94bc..1e4f74704 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -6,7 +6,7 @@
6 <rect> 6 <rect>
7 <x>0</x> 7 <x>0</x>
8 <y>0</y> 8 <y>0</y>
9 <width>471</width> 9 <width>541</width>
10 <height>759</height> 10 <height>759</height>
11 </rect> 11 </rect>
12 </property> 12 </property>
@@ -574,13 +574,6 @@
574 </property> 574 </property>
575 </spacer> 575 </spacer>
576 </item> 576 </item>
577 <item>
578 <widget class="QPushButton" name="button_check_vulkan">
579 <property name="text">
580 <string>Check for Working Vulkan</string>
581 </property>
582 </widget>
583 </item>
584 </layout> 577 </layout>
585 </widget> 578 </widget>
586 <resources/> 579 <resources/>
diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp
index edf0893c4..daa77a8f8 100644
--- a/src/yuzu/configuration/configure_hotkeys.cpp
+++ b/src/yuzu/configuration/configure_hotkeys.cpp
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <QMenu> 4#include <QMenu>
6#include <QMessageBox> 5#include <QMessageBox>
diff --git a/src/yuzu/configuration/configure_hotkeys.h b/src/yuzu/configuration/configure_hotkeys.h
index f943ec538..b45ecb185 100644
--- a/src/yuzu/configuration/configure_hotkeys.h
+++ b/src/yuzu/configuration/configure_hotkeys.h
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 73d7ba24b..16fba3deb 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <memory> 4#include <memory>
6#include <thread> 5#include <thread>
@@ -15,6 +14,7 @@
15#include "ui_configure_input.h" 14#include "ui_configure_input.h"
16#include "ui_configure_input_advanced.h" 15#include "ui_configure_input_advanced.h"
17#include "ui_configure_input_player.h" 16#include "ui_configure_input_player.h"
17#include "yuzu/configuration/configure_camera.h"
18#include "yuzu/configuration/configure_debug_controller.h" 18#include "yuzu/configuration/configure_debug_controller.h"
19#include "yuzu/configuration/configure_input.h" 19#include "yuzu/configuration/configure_input.h"
20#include "yuzu/configuration/configure_input_advanced.h" 20#include "yuzu/configuration/configure_input_advanced.h"
@@ -163,6 +163,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
163 [this, input_subsystem, &hid_core] { 163 [this, input_subsystem, &hid_core] {
164 CallConfigureDialog<ConfigureRingController>(*this, input_subsystem, hid_core); 164 CallConfigureDialog<ConfigureRingController>(*this, input_subsystem, hid_core);
165 }); 165 });
166 connect(advanced, &ConfigureInputAdvanced::CallCameraDialog,
167 [this, input_subsystem, &hid_core] {
168 CallConfigureDialog<ConfigureCamera>(*this, input_subsystem);
169 });
166 170
167 connect(ui->vibrationButton, &QPushButton::clicked, 171 connect(ui->vibrationButton, &QPushButton::clicked,
168 [this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); }); 172 [this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); });
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index 4cafa3dab..c89189c36 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp
index f14bdc831..10f841b98 100644
--- a/src/yuzu/configuration/configure_input_advanced.cpp
+++ b/src/yuzu/configuration/configure_input_advanced.cpp
@@ -89,6 +89,7 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent)
89 [this] { CallMotionTouchConfigDialog(); }); 89 [this] { CallMotionTouchConfigDialog(); });
90 connect(ui->ring_controller_configure, &QPushButton::clicked, this, 90 connect(ui->ring_controller_configure, &QPushButton::clicked, this,
91 [this] { CallRingControllerDialog(); }); 91 [this] { CallRingControllerDialog(); });
92 connect(ui->camera_configure, &QPushButton::clicked, this, [this] { CallCameraDialog(); });
92 93
93#ifndef _WIN32 94#ifndef _WIN32
94 ui->enable_raw_input->setVisible(false); 95 ui->enable_raw_input->setVisible(false);
@@ -136,6 +137,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
136 Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked(); 137 Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked();
137 Settings::values.controller_navigation = ui->controller_navigation->isChecked(); 138 Settings::values.controller_navigation = ui->controller_navigation->isChecked();
138 Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked(); 139 Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked();
140 Settings::values.enable_ir_sensor = ui->enable_ir_sensor->isChecked();
139} 141}
140 142
141void ConfigureInputAdvanced::LoadConfiguration() { 143void ConfigureInputAdvanced::LoadConfiguration() {
@@ -169,6 +171,7 @@ void ConfigureInputAdvanced::LoadConfiguration() {
169 ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue()); 171 ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue());
170 ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue()); 172 ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue());
171 ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue()); 173 ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue());
174 ui->enable_ir_sensor->setChecked(Settings::values.enable_ir_sensor.GetValue());
172 175
173 UpdateUIEnabled(); 176 UpdateUIEnabled();
174} 177}
diff --git a/src/yuzu/configuration/configure_input_advanced.h b/src/yuzu/configuration/configure_input_advanced.h
index 644e56dd8..fc1230284 100644
--- a/src/yuzu/configuration/configure_input_advanced.h
+++ b/src/yuzu/configuration/configure_input_advanced.h
@@ -29,6 +29,7 @@ signals:
29 void CallTouchscreenConfigDialog(); 29 void CallTouchscreenConfigDialog();
30 void CallMotionTouchConfigDialog(); 30 void CallMotionTouchConfigDialog();
31 void CallRingControllerDialog(); 31 void CallRingControllerDialog();
32 void CallCameraDialog();
32 33
33private: 34private:
34 void changeEvent(QEvent* event) override; 35 void changeEvent(QEvent* event) override;
diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui
index 14403cb10..fac8cf827 100644
--- a/src/yuzu/configuration/configure_input_advanced.ui
+++ b/src/yuzu/configuration/configure_input_advanced.ui
@@ -2617,6 +2617,20 @@
2617 </property> 2617 </property>
2618 </widget> 2618 </widget>
2619 </item> 2619 </item>
2620 <item row="5" column="0">
2621 <widget class="QCheckBox" name="enable_ir_sensor">
2622 <property name="text">
2623 <string>Infrared Camera</string>
2624 </property>
2625 </widget>
2626 </item>
2627 <item row="5" column="2">
2628 <widget class="QPushButton" name="camera_configure">
2629 <property name="text">
2630 <string>Configure</string>
2631 </property>
2632 </widget>
2633 </item>
2620 </layout> 2634 </layout>
2621 </widget> 2635 </widget>
2622 </item> 2636 </item>
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index f3be9a374..00bee85b2 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <algorithm> 4#include <algorithm>
6#include <memory> 5#include <memory>
diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h
index 47df6b3d3..79434fdd8 100644
--- a/src/yuzu/configuration/configure_input_player.h
+++ b/src/yuzu/configuration/configure_input_player.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/configuration/configure_motion_touch.cpp b/src/yuzu/configuration/configure_motion_touch.cpp
index c313b0919..d1b870c72 100644
--- a/src/yuzu/configuration/configure_motion_touch.cpp
+++ b/src/yuzu/configuration/configure_motion_touch.cpp
@@ -1,6 +1,5 @@
1// Copyright 2018 Citra Emulator Project 1// SPDX-FileCopyrightText: 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <sstream> 4#include <sstream>
6 5
diff --git a/src/yuzu/configuration/configure_motion_touch.h b/src/yuzu/configuration/configure_motion_touch.h
index 91d1ae671..7dcc9318e 100644
--- a/src/yuzu/configuration/configure_motion_touch.h
+++ b/src/yuzu/configuration/configure_motion_touch.h
@@ -1,6 +1,5 @@
1// Copyright 2018 Citra Emulator Project 1// SPDX-FileCopyrightText: 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/configuration/configure_network.cpp b/src/yuzu/configuration/configure_network.cpp
index 8ed08fa6a..ba1986eb1 100644
--- a/src/yuzu/configuration/configure_network.cpp
+++ b/src/yuzu/configuration/configure_network.cpp
@@ -4,7 +4,7 @@
4#include <QtConcurrent/QtConcurrent> 4#include <QtConcurrent/QtConcurrent>
5#include "common/settings.h" 5#include "common/settings.h"
6#include "core/core.h" 6#include "core/core.h"
7#include "core/network/network_interface.h" 7#include "core/internal_network/network_interface.h"
8#include "ui_configure_network.h" 8#include "ui_configure_network.h"
9#include "yuzu/configuration/configure_network.h" 9#include "yuzu/configuration/configure_network.h"
10 10
diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp
index 4906997ab..674a75a62 100644
--- a/src/yuzu/configuration/configure_per_game_addons.cpp
+++ b/src/yuzu/configuration/configure_per_game_addons.cpp
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <algorithm> 4#include <algorithm>
6#include <memory> 5#include <memory>
diff --git a/src/yuzu/configuration/configure_per_game_addons.h b/src/yuzu/configuration/configure_per_game_addons.h
index 14690fba8..53db405c1 100644
--- a/src/yuzu/configuration/configure_per_game_addons.h
+++ b/src/yuzu/configuration/configure_per_game_addons.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp
index 5442fe328..5c0217ba8 100644
--- a/src/yuzu/configuration/configure_profile_manager.cpp
+++ b/src/yuzu/configuration/configure_profile_manager.cpp
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <algorithm> 4#include <algorithm>
6#include <QFileDialog> 5#include <QFileDialog>
diff --git a/src/yuzu/configuration/configure_profile_manager.h b/src/yuzu/configuration/configure_profile_manager.h
index 575cb89d5..fe9033779 100644
--- a/src/yuzu/configuration/configure_profile_manager.h
+++ b/src/yuzu/configuration/configure_profile_manager.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index ecebb0fb7..bc9d9d77a 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <chrono> 4#include <chrono>
6#include <optional> 5#include <optional>
diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h
index 5a1633192..8f02880a7 100644
--- a/src/yuzu/configuration/configure_system.h
+++ b/src/yuzu/configuration/configure_system.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/configuration/configure_touch_from_button.cpp b/src/yuzu/configuration/configure_touch_from_button.cpp
index 06cc452c3..18e2eba69 100644
--- a/src/yuzu/configuration/configure_touch_from_button.cpp
+++ b/src/yuzu/configuration/configure_touch_from_button.cpp
@@ -1,6 +1,5 @@
1// Copyright 2020 Citra Emulator Project 1// SPDX-FileCopyrightText: 2020 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <QInputDialog> 4#include <QInputDialog>
6#include <QKeyEvent> 5#include <QKeyEvent>
diff --git a/src/yuzu/configuration/configure_touch_from_button.h b/src/yuzu/configuration/configure_touch_from_button.h
index b8c55db66..5a1416d00 100644
--- a/src/yuzu/configuration/configure_touch_from_button.h
+++ b/src/yuzu/configuration/configure_touch_from_button.h
@@ -1,6 +1,5 @@
1// Copyright 2020 Citra Emulator Project 1// SPDX-FileCopyrightText: 2020 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/configuration/configure_touch_widget.h b/src/yuzu/configuration/configure_touch_widget.h
index 347b46583..49f533afe 100644
--- a/src/yuzu/configuration/configure_touch_widget.h
+++ b/src/yuzu/configuration/configure_touch_widget.h
@@ -1,6 +1,5 @@
1// Copyright 2020 Citra Emulator Project 1// SPDX-FileCopyrightText: 2020 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.cpp b/src/yuzu/configuration/configure_touchscreen_advanced.cpp
index 29c86c7bc..5a03e48df 100644
--- a/src/yuzu/configuration/configure_touchscreen_advanced.cpp
+++ b/src/yuzu/configuration/configure_touchscreen_advanced.cpp
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <memory> 4#include <memory>
6#include "ui_configure_touchscreen_advanced.h" 5#include "ui_configure_touchscreen_advanced.h"
diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.h b/src/yuzu/configuration/configure_touchscreen_advanced.h
index 72061492c..034dc0d46 100644
--- a/src/yuzu/configuration/configure_touchscreen_advanced.h
+++ b/src/yuzu/configuration/configure_touchscreen_advanced.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index d3a60cdd1..2e98ede8e 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <array> 4#include <array>
6#include <utility> 5#include <utility>
diff --git a/src/yuzu/configuration/configure_ui.h b/src/yuzu/configuration/configure_ui.h
index 48b6e6d82..95af8370e 100644
--- a/src/yuzu/configuration/configure_ui.h
+++ b/src/yuzu/configuration/configure_ui.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp
index d779251b4..d668c992b 100644
--- a/src/yuzu/configuration/configure_web.cpp
+++ b/src/yuzu/configuration/configure_web.cpp
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <QIcon> 4#include <QIcon>
6#include <QMessageBox> 5#include <QMessageBox>
@@ -169,3 +168,8 @@ void ConfigureWeb::OnLoginVerified() {
169 "correctly, and that your internet connection is working.")); 168 "correctly, and that your internet connection is working."));
170 } 169 }
171} 170}
171
172void ConfigureWeb::SetWebServiceConfigEnabled(bool enabled) {
173 ui->label_disable_info->setVisible(!enabled);
174 ui->groupBoxWebConfig->setEnabled(enabled);
175}
diff --git a/src/yuzu/configuration/configure_web.h b/src/yuzu/configuration/configure_web.h
index 9054711ea..03feb55f8 100644
--- a/src/yuzu/configuration/configure_web.h
+++ b/src/yuzu/configuration/configure_web.h
@@ -1,6 +1,5 @@
1// Copyright 2017 Citra Emulator Project 1// SPDX-FileCopyrightText: 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
@@ -20,6 +19,7 @@ public:
20 ~ConfigureWeb() override; 19 ~ConfigureWeb() override;
21 20
22 void ApplyConfiguration(); 21 void ApplyConfiguration();
22 void SetWebServiceConfigEnabled(bool enabled);
23 23
24private: 24private:
25 void changeEvent(QEvent* event) override; 25 void changeEvent(QEvent* event) override;
diff --git a/src/yuzu/configuration/configure_web.ui b/src/yuzu/configuration/configure_web.ui
index 35b4274b0..3ac3864be 100644
--- a/src/yuzu/configuration/configure_web.ui
+++ b/src/yuzu/configuration/configure_web.ui
@@ -113,6 +113,16 @@
113 </widget> 113 </widget>
114 </item> 114 </item>
115 <item> 115 <item>
116 <widget class="QLabel" name="label_disable_info">
117 <property name="text">
118 <string>Web Service configuration can only be changed when a public room isn't being hosted.</string>
119 </property>
120 <property name="wordWrap">
121 <bool>true</bool>
122 </property>
123 </widget>
124 </item>
125 <item>
116 <widget class="QGroupBox" name="groupBox"> 126 <widget class="QGroupBox" name="groupBox">
117 <property name="title"> 127 <property name="title">
118 <string>Telemetry</string> 128 <string>Telemetry</string>
diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp
index 6b834c42e..e4bf16a04 100644
--- a/src/yuzu/debugger/controller.cpp
+++ b/src/yuzu/debugger/controller.cpp
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <QAction> 4#include <QAction>
6#include <QLayout> 5#include <QLayout>
diff --git a/src/yuzu/debugger/controller.h b/src/yuzu/debugger/controller.h
index 52cea3326..9651dfaa9 100644
--- a/src/yuzu/debugger/controller.h
+++ b/src/yuzu/debugger/controller.h
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp
index 33110685a..d3e2d3c12 100644
--- a/src/yuzu/debugger/profiler.cpp
+++ b/src/yuzu/debugger/profiler.cpp
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <QAction> 4#include <QAction>
6#include <QLayout> 5#include <QLayout>
diff --git a/src/yuzu/debugger/profiler.h b/src/yuzu/debugger/profiler.h
index 8e69fdb06..4c8ccd3c2 100644
--- a/src/yuzu/debugger/profiler.h
+++ b/src/yuzu/debugger/profiler.h
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index 0ea31cd33..7f7c5fc42 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <array> 4#include <array>
6#include <fmt/format.h> 5#include <fmt/format.h>
diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h
index f21b9f467..7e528b592 100644
--- a/src/yuzu/debugger/wait_tree.h
+++ b/src/yuzu/debugger/wait_tree.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/discord.h b/src/yuzu/discord.h
index a867cc4d6..e08784498 100644
--- a/src/yuzu/discord.h
+++ b/src/yuzu/discord.h
@@ -1,6 +1,5 @@
1// Copyright 2018 Citra Emulator Project 1// SPDX-FileCopyrightText: 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/discord_impl.cpp b/src/yuzu/discord_impl.cpp
index 66f928af6..c351e9b83 100644
--- a/src/yuzu/discord_impl.cpp
+++ b/src/yuzu/discord_impl.cpp
@@ -1,6 +1,5 @@
1// Copyright 2018 Citra Emulator Project 1// SPDX-FileCopyrightText: 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <chrono> 4#include <chrono>
6#include <string> 5#include <string>
diff --git a/src/yuzu/discord_impl.h b/src/yuzu/discord_impl.h
index 03ad42681..84710b9c6 100644
--- a/src/yuzu/discord_impl.h
+++ b/src/yuzu/discord_impl.h
@@ -1,6 +1,5 @@
1// Copyright 2018 Citra Emulator Project 1// SPDX-FileCopyrightText: 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 05d309827..041e6ac11 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <regex> 4#include <regex>
6#include <QApplication> 5#include <QApplication>
@@ -499,6 +498,8 @@ void GameList::DonePopulating(const QStringList& watch_list) {
499 } 498 }
500 item_model->sort(tree_view->header()->sortIndicatorSection(), 499 item_model->sort(tree_view->header()->sortIndicatorSection(),
501 tree_view->header()->sortIndicatorOrder()); 500 tree_view->header()->sortIndicatorOrder());
501
502 emit PopulatingCompleted();
502} 503}
503 504
504void GameList::PopupContextMenu(const QPoint& menu_location) { 505void GameList::PopupContextMenu(const QPoint& menu_location) {
@@ -752,6 +753,10 @@ void GameList::LoadCompatibilityList() {
752 } 753 }
753} 754}
754 755
756QStandardItemModel* GameList::GetModel() const {
757 return item_model;
758}
759
755void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) { 760void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
756 tree_view->setEnabled(false); 761 tree_view->setEnabled(false);
757 762
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index bc36d015a..f783283c9 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
@@ -16,9 +15,14 @@
16#include <QWidget> 15#include <QWidget>
17 16
18#include "common/common_types.h" 17#include "common/common_types.h"
18#include "core/core.h"
19#include "uisettings.h" 19#include "uisettings.h"
20#include "yuzu/compatibility_list.h" 20#include "yuzu/compatibility_list.h"
21 21
22namespace Core {
23class System;
24}
25
22class ControllerNavigation; 26class ControllerNavigation;
23class GameListWorker; 27class GameListWorker;
24class GameListSearchField; 28class GameListSearchField;
@@ -84,6 +88,8 @@ public:
84 void SaveInterfaceLayout(); 88 void SaveInterfaceLayout();
85 void LoadInterfaceLayout(); 89 void LoadInterfaceLayout();
86 90
91 QStandardItemModel* GetModel() const;
92
87 /// Disables events from the emulated controller 93 /// Disables events from the emulated controller
88 void UnloadController(); 94 void UnloadController();
89 95
@@ -108,6 +114,7 @@ signals:
108 void OpenDirectory(const QString& directory); 114 void OpenDirectory(const QString& directory);
109 void AddDirectory(); 115 void AddDirectory();
110 void ShowList(bool show); 116 void ShowList(bool show);
117 void PopulatingCompleted();
111 118
112private slots: 119private slots:
113 void OnItemExpanded(const QModelIndex& item); 120 void OnItemExpanded(const QModelIndex& item);
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index cd7d63536..e7667cf60 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp
index d59aa5d18..13723f6e5 100644
--- a/src/yuzu/hotkeys.cpp
+++ b/src/yuzu/hotkeys.cpp
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <sstream> 4#include <sstream>
6#include <QShortcut> 5#include <QShortcut>
diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h
index 57a7c7da5..dc5b7f628 100644
--- a/src/yuzu/hotkeys.h
+++ b/src/yuzu/hotkeys.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index a120f2662..ef91ef19c 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <cinttypes> 4#include <cinttypes>
6#include <clocale> 5#include <clocale>
@@ -9,6 +8,10 @@
9#ifdef __APPLE__ 8#ifdef __APPLE__
10#include <unistd.h> // for chdir 9#include <unistd.h> // for chdir
11#endif 10#endif
11#ifdef __linux__
12#include <csignal>
13#include <sys/socket.h>
14#endif
12 15
13// VFS includes must be before glad as they will conflict with Windows file api, which uses defines. 16// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
14#include "applets/qt_controller.h" 17#include "applets/qt_controller.h"
@@ -32,6 +35,7 @@
32#include "core/hle/service/am/applet_ae.h" 35#include "core/hle/service/am/applet_ae.h"
33#include "core/hle/service/am/applet_oe.h" 36#include "core/hle/service/am/applet_oe.h"
34#include "core/hle/service/am/applets/applets.h" 37#include "core/hle/service/am/applets/applets.h"
38#include "yuzu/multiplayer/state.h"
35#include "yuzu/util/controller_navigation.h" 39#include "yuzu/util/controller_navigation.h"
36 40
37// These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows 41// These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows
@@ -115,7 +119,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
115#include "video_core/shader_notify.h" 119#include "video_core/shader_notify.h"
116#include "yuzu/about_dialog.h" 120#include "yuzu/about_dialog.h"
117#include "yuzu/bootmanager.h" 121#include "yuzu/bootmanager.h"
118#include "yuzu/check_vulkan.h"
119#include "yuzu/compatdb.h" 122#include "yuzu/compatdb.h"
120#include "yuzu/compatibility_list.h" 123#include "yuzu/compatibility_list.h"
121#include "yuzu/configuration/config.h" 124#include "yuzu/configuration/config.h"
@@ -131,7 +134,9 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
131#include "yuzu/install_dialog.h" 134#include "yuzu/install_dialog.h"
132#include "yuzu/loading_screen.h" 135#include "yuzu/loading_screen.h"
133#include "yuzu/main.h" 136#include "yuzu/main.h"
137#include "yuzu/startup_checks.h"
134#include "yuzu/uisettings.h" 138#include "yuzu/uisettings.h"
139#include "yuzu/util/clickable_label.h"
135 140
136using namespace Common::Literals; 141using namespace Common::Literals;
137 142
@@ -252,12 +257,16 @@ static QString PrettyProductName() {
252 return QSysInfo::prettyProductName(); 257 return QSysInfo::prettyProductName();
253} 258}
254 259
255GMainWindow::GMainWindow() 260GMainWindow::GMainWindow(bool has_broken_vulkan)
256 : ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()}, 261 : ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()},
257 input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, 262 input_subsystem{std::make_shared<InputCommon::InputSubsystem>()},
258 config{std::make_unique<Config>(*system)}, 263 config{std::make_unique<Config>(*system)},
259 vfs{std::make_shared<FileSys::RealVfsFilesystem>()}, 264 vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
260 provider{std::make_unique<FileSys::ManualContentProvider>()} { 265 provider{std::make_unique<FileSys::ManualContentProvider>()} {
266#ifdef __linux__
267 SetupSigInterrupts();
268#endif
269
261 Common::Log::Initialize(); 270 Common::Log::Initialize();
262 LoadTranslation(); 271 LoadTranslation();
263 272
@@ -271,6 +280,8 @@ GMainWindow::GMainWindow()
271 SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); 280 SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
272 discord_rpc->Update(); 281 discord_rpc->Update();
273 282
283 system->GetRoomNetwork().Init();
284
274 RegisterMetaTypes(); 285 RegisterMetaTypes();
275 286
276 InitializeWidgets(); 287 InitializeWidgets();
@@ -352,17 +363,15 @@ GMainWindow::GMainWindow()
352 363
353 MigrateConfigFiles(); 364 MigrateConfigFiles();
354 365
355 if (!CheckVulkan()) { 366 if (has_broken_vulkan) {
356 config->Save(); 367 UISettings::values.has_broken_vulkan = true;
368
369 QMessageBox::warning(this, tr("Broken Vulkan Installation Detected"),
370 tr("Vulkan initialization failed during boot.<br><br>Click <a "
371 "href='https://yuzu-emu.org/wiki/faq/"
372 "#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>"
373 "here for instructions to fix the issue</a>."));
357 374
358 QMessageBox::warning(
359 this, tr("Broken Vulkan Installation Detected"),
360 tr("Vulkan initialization failed on the previous boot.<br><br>Click <a "
361 "href='https://yuzu-emu.org/wiki/faq/"
362 "#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>here for "
363 "instructions to fix the issue</a>."));
364 }
365 if (UISettings::values.has_broken_vulkan) {
366 Settings::values.renderer_backend = Settings::RendererBackend::OpenGL; 375 Settings::values.renderer_backend = Settings::RendererBackend::OpenGL;
367 376
368 renderer_status_button->setDisabled(true); 377 renderer_status_button->setDisabled(true);
@@ -461,6 +470,13 @@ GMainWindow::~GMainWindow() {
461 if (render_window->parent() == nullptr) { 470 if (render_window->parent() == nullptr) {
462 delete render_window; 471 delete render_window;
463 } 472 }
473
474 system->GetRoomNetwork().Shutdown();
475
476#ifdef __linux__
477 ::close(sig_interrupt_fds[0]);
478 ::close(sig_interrupt_fds[1]);
479#endif
464} 480}
465 481
466void GMainWindow::RegisterMetaTypes() { 482void GMainWindow::RegisterMetaTypes() {
@@ -824,6 +840,10 @@ void GMainWindow::InitializeWidgets() {
824 } 840 }
825 }); 841 });
826 842
843 multiplayer_state = new MultiplayerState(this, game_list->GetModel(), ui->action_Leave_Room,
844 ui->action_Show_Room, system->GetRoomNetwork());
845 multiplayer_state->setVisible(false);
846
827 // Create status bar 847 // Create status bar
828 message_label = new QLabel(); 848 message_label = new QLabel();
829 // Configured separately for left alignment 849 // Configured separately for left alignment
@@ -856,6 +876,10 @@ void GMainWindow::InitializeWidgets() {
856 statusBar()->addPermanentWidget(label); 876 statusBar()->addPermanentWidget(label);
857 } 877 }
858 878
879 // TODO (flTobi): Add the widget when multiplayer is fully implemented
880 // statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0);
881 // statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0);
882
859 tas_label = new QLabel(); 883 tas_label = new QLabel();
860 tas_label->setObjectName(QStringLiteral("TASlabel")); 884 tas_label->setObjectName(QStringLiteral("TASlabel"));
861 tas_label->setFocusPolicy(Qt::NoFocus); 885 tas_label->setFocusPolicy(Qt::NoFocus);
@@ -1165,6 +1189,8 @@ void GMainWindow::ConnectWidgetEvents() {
1165 connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this, 1189 connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this,
1166 &GMainWindow::OnGameListAddDirectory); 1190 &GMainWindow::OnGameListAddDirectory);
1167 connect(game_list, &GameList::ShowList, this, &GMainWindow::OnGameListShowList); 1191 connect(game_list, &GameList::ShowList, this, &GMainWindow::OnGameListShowList);
1192 connect(game_list, &GameList::PopulatingCompleted,
1193 [this] { multiplayer_state->UpdateGameList(game_list->GetModel()); });
1168 1194
1169 connect(game_list, &GameList::OpenPerGameGeneralRequested, this, 1195 connect(game_list, &GameList::OpenPerGameGeneralRequested, this,
1170 &GMainWindow::OnGameListOpenPerGameProperties); 1196 &GMainWindow::OnGameListOpenPerGameProperties);
@@ -1182,6 +1208,9 @@ void GMainWindow::ConnectWidgetEvents() {
1182 connect(this, &GMainWindow::EmulationStopping, this, &GMainWindow::SoftwareKeyboardExit); 1208 connect(this, &GMainWindow::EmulationStopping, this, &GMainWindow::SoftwareKeyboardExit);
1183 1209
1184 connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar); 1210 connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar);
1211
1212 connect(this, &GMainWindow::UpdateThemedIcons, multiplayer_state,
1213 &MultiplayerState::UpdateThemedIcons);
1185} 1214}
1186 1215
1187void GMainWindow::ConnectMenuEvents() { 1216void GMainWindow::ConnectMenuEvents() {
@@ -1225,6 +1254,18 @@ void GMainWindow::ConnectMenuEvents() {
1225 ui->action_Reset_Window_Size_900, 1254 ui->action_Reset_Window_Size_900,
1226 ui->action_Reset_Window_Size_1080}); 1255 ui->action_Reset_Window_Size_1080});
1227 1256
1257 // Multiplayer
1258 connect(ui->action_View_Lobby, &QAction::triggered, multiplayer_state,
1259 &MultiplayerState::OnViewLobby);
1260 connect(ui->action_Start_Room, &QAction::triggered, multiplayer_state,
1261 &MultiplayerState::OnCreateRoom);
1262 connect(ui->action_Leave_Room, &QAction::triggered, multiplayer_state,
1263 &MultiplayerState::OnCloseRoom);
1264 connect(ui->action_Connect_To_Room, &QAction::triggered, multiplayer_state,
1265 &MultiplayerState::OnDirectConnectToRoom);
1266 connect(ui->action_Show_Room, &QAction::triggered, multiplayer_state,
1267 &MultiplayerState::OnOpenNetworkRoom);
1268
1228 // Tools 1269 // Tools
1229 connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this, 1270 connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this,
1230 ReinitializeKeyBehavior::Warning)); 1271 ReinitializeKeyBehavior::Warning));
@@ -1325,6 +1366,52 @@ static void ReleaseWakeLockLinux(QDBusObjectPath lock) {
1325 QString::fromLatin1("org.freedesktop.portal.Request")); 1366 QString::fromLatin1("org.freedesktop.portal.Request"));
1326 unlocker.call(QString::fromLatin1("Close")); 1367 unlocker.call(QString::fromLatin1("Close"));
1327} 1368}
1369
1370std::array<int, 3> GMainWindow::sig_interrupt_fds{0, 0, 0};
1371
1372void GMainWindow::SetupSigInterrupts() {
1373 if (sig_interrupt_fds[2] == 1) {
1374 return;
1375 }
1376 socketpair(AF_UNIX, SOCK_STREAM, 0, sig_interrupt_fds.data());
1377 sig_interrupt_fds[2] = 1;
1378
1379 struct sigaction sa;
1380 sa.sa_handler = &GMainWindow::HandleSigInterrupt;
1381 sigemptyset(&sa.sa_mask);
1382 sa.sa_flags = SA_RESETHAND;
1383 sigaction(SIGINT, &sa, nullptr);
1384 sigaction(SIGTERM, &sa, nullptr);
1385
1386 sig_interrupt_notifier = new QSocketNotifier(sig_interrupt_fds[1], QSocketNotifier::Read, this);
1387 connect(sig_interrupt_notifier, &QSocketNotifier::activated, this,
1388 &GMainWindow::OnSigInterruptNotifierActivated);
1389 connect(this, &GMainWindow::SigInterrupt, this, &GMainWindow::close);
1390}
1391
1392void GMainWindow::HandleSigInterrupt(int sig) {
1393 if (sig == SIGINT) {
1394 exit(1);
1395 }
1396
1397 // Calling into Qt directly from a signal handler is not safe,
1398 // so wake up a QSocketNotifier with this hacky write call instead.
1399 char a = 1;
1400 int ret = write(sig_interrupt_fds[0], &a, sizeof(a));
1401 (void)ret;
1402}
1403
1404void GMainWindow::OnSigInterruptNotifierActivated() {
1405 sig_interrupt_notifier->setEnabled(false);
1406
1407 char a;
1408 int ret = read(sig_interrupt_fds[1], &a, sizeof(a));
1409 (void)ret;
1410
1411 sig_interrupt_notifier->setEnabled(true);
1412
1413 emit SigInterrupt();
1414}
1328#endif // __linux__ 1415#endif // __linux__
1329 1416
1330void GMainWindow::PreventOSSleep() { 1417void GMainWindow::PreventOSSleep() {
@@ -1542,6 +1629,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
1542 mouse_hide_timer.start(); 1629 mouse_hide_timer.start();
1543 } 1630 }
1544 1631
1632 render_window->InitializeCamera();
1633
1545 std::string title_name; 1634 std::string title_name;
1546 std::string title_version; 1635 std::string title_version;
1547 const auto res = system->GetGameName(title_name); 1636 const auto res = system->GetGameName(title_name);
@@ -1623,6 +1712,7 @@ void GMainWindow::ShutdownGame() {
1623 tas_label->clear(); 1712 tas_label->clear();
1624 input_subsystem->GetTas()->Stop(); 1713 input_subsystem->GetTas()->Stop();
1625 OnTasStateChanged(); 1714 OnTasStateChanged();
1715 render_window->FinalizeCamera();
1626 1716
1627 // Enable all controllers 1717 // Enable all controllers
1628 system->HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All}); 1718 system->HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All});
@@ -2782,7 +2872,8 @@ void GMainWindow::OnConfigure() {
2782 const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue(); 2872 const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue();
2783 2873
2784 Settings::SetConfiguringGlobal(true); 2874 Settings::SetConfiguringGlobal(true);
2785 ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), *system); 2875 ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), *system,
2876 !multiplayer_state->IsHostingPublicRoom());
2786 connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this, 2877 connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this,
2787 &GMainWindow::OnLanguageChanged); 2878 &GMainWindow::OnLanguageChanged);
2788 2879
@@ -2839,6 +2930,11 @@ void GMainWindow::OnConfigure() {
2839 if (UISettings::values.enable_discord_presence.GetValue() != old_discord_presence) { 2930 if (UISettings::values.enable_discord_presence.GetValue() != old_discord_presence) {
2840 SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); 2931 SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
2841 } 2932 }
2933
2934 if (!multiplayer_state->IsHostingPublicRoom()) {
2935 multiplayer_state->UpdateCredentials();
2936 }
2937
2842 emit UpdateThemedIcons(); 2938 emit UpdateThemedIcons();
2843 2939
2844 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); 2940 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
@@ -2862,6 +2958,12 @@ void GMainWindow::OnConfigure() {
2862 mouse_hide_timer.start(); 2958 mouse_hide_timer.start();
2863 } 2959 }
2864 2960
2961 // Restart camera config
2962 if (emulation_running) {
2963 render_window->FinalizeCamera();
2964 render_window->InitializeCamera();
2965 }
2966
2865 if (!UISettings::values.has_broken_vulkan) { 2967 if (!UISettings::values.has_broken_vulkan) {
2866 renderer_status_button->setEnabled(!emulation_running); 2968 renderer_status_button->setEnabled(!emulation_running);
2867 } 2969 }
@@ -3653,6 +3755,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
3653 } 3755 }
3654 3756
3655 render_window->close(); 3757 render_window->close();
3758 multiplayer_state->Close();
3656 3759
3657 QWidget::closeEvent(event); 3760 QWidget::closeEvent(event);
3658} 3761}
@@ -3849,6 +3952,7 @@ void GMainWindow::OnLanguageChanged(const QString& locale) {
3849 UISettings::values.language = locale; 3952 UISettings::values.language = locale;
3850 LoadTranslation(); 3953 LoadTranslation();
3851 ui->retranslateUi(this); 3954 ui->retranslateUi(this);
3955 multiplayer_state->retranslateUi();
3852 UpdateWindowTitle(); 3956 UpdateWindowTitle();
3853} 3957}
3854 3958
@@ -3870,6 +3974,11 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
3870#endif 3974#endif
3871 3975
3872int main(int argc, char* argv[]) { 3976int main(int argc, char* argv[]) {
3977 bool has_broken_vulkan = false;
3978 if (StartupChecks(argv[0], &has_broken_vulkan)) {
3979 return 0;
3980 }
3981
3873 Common::DetachedTasks detached_tasks; 3982 Common::DetachedTasks detached_tasks;
3874 MicroProfileOnThreadCreate("Frontend"); 3983 MicroProfileOnThreadCreate("Frontend");
3875 SCOPE_EXIT({ MicroProfileShutdown(); }); 3984 SCOPE_EXIT({ MicroProfileShutdown(); });
@@ -3909,7 +4018,7 @@ int main(int argc, char* argv[]) {
3909 // generating shaders 4018 // generating shaders
3910 setlocale(LC_ALL, "C"); 4019 setlocale(LC_ALL, "C");
3911 4020
3912 GMainWindow main_window{}; 4021 GMainWindow main_window{has_broken_vulkan};
3913 // After settings have been loaded by GMainWindow, apply the filter 4022 // After settings have been loaded by GMainWindow, apply the filter
3914 main_window.show(); 4023 main_window.show();
3915 4024
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 09e37f152..509bb91df 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
@@ -11,6 +10,7 @@
11#include <QTimer> 10#include <QTimer>
12#include <QTranslator> 11#include <QTranslator>
13 12
13#include "common/announce_multiplayer_room.h"
14#include "common/common_types.h" 14#include "common/common_types.h"
15#include "yuzu/compatibility_list.h" 15#include "yuzu/compatibility_list.h"
16#include "yuzu/hotkeys.h" 16#include "yuzu/hotkeys.h"
@@ -22,6 +22,7 @@
22#endif 22#endif
23 23
24class Config; 24class Config;
25class ClickableLabel;
25class EmuThread; 26class EmuThread;
26class GameList; 27class GameList;
27class GImageInfo; 28class GImageInfo;
@@ -31,6 +32,7 @@ class MicroProfileDialog;
31class ProfilerWidget; 32class ProfilerWidget;
32class ControllerDialog; 33class ControllerDialog;
33class QLabel; 34class QLabel;
35class MultiplayerState;
34class QPushButton; 36class QPushButton;
35class QProgressDialog; 37class QProgressDialog;
36class WaitTreeWidget; 38class WaitTreeWidget;
@@ -118,7 +120,7 @@ class GMainWindow : public QMainWindow {
118public: 120public:
119 void filterBarSetChecked(bool state); 121 void filterBarSetChecked(bool state);
120 void UpdateUITheme(); 122 void UpdateUITheme();
121 explicit GMainWindow(); 123 explicit GMainWindow(bool has_broken_vulkan);
122 ~GMainWindow() override; 124 ~GMainWindow() override;
123 125
124 bool DropAction(QDropEvent* event); 126 bool DropAction(QDropEvent* event);
@@ -161,6 +163,8 @@ signals:
161 void WebBrowserExtractOfflineRomFS(); 163 void WebBrowserExtractOfflineRomFS();
162 void WebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, std::string last_url); 164 void WebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, std::string last_url);
163 165
166 void SigInterrupt();
167
164public slots: 168public slots:
165 void OnLoadComplete(); 169 void OnLoadComplete();
166 void OnExecuteProgram(std::size_t program_index); 170 void OnExecuteProgram(std::size_t program_index);
@@ -200,6 +204,8 @@ private:
200 void ConnectMenuEvents(); 204 void ConnectMenuEvents();
201 void UpdateMenuState(); 205 void UpdateMenuState();
202 206
207 MultiplayerState* multiplayer_state = nullptr;
208
203 void PreventOSSleep(); 209 void PreventOSSleep();
204 void AllowOSSleep(); 210 void AllowOSSleep();
205 211
@@ -247,6 +253,12 @@ private:
247 void RequestGameResume(); 253 void RequestGameResume();
248 void closeEvent(QCloseEvent* event) override; 254 void closeEvent(QCloseEvent* event) override;
249 255
256#ifdef __linux__
257 void SetupSigInterrupts();
258 static void HandleSigInterrupt(int);
259 void OnSigInterruptNotifierActivated();
260#endif
261
250private slots: 262private slots:
251 void OnStartGame(); 263 void OnStartGame();
252 void OnRestartGame(); 264 void OnRestartGame();
@@ -415,6 +427,9 @@ private:
415 bool is_tas_recording_dialog_active{}; 427 bool is_tas_recording_dialog_active{};
416 428
417#ifdef __linux__ 429#ifdef __linux__
430 QSocketNotifier* sig_interrupt_notifier;
431 static std::array<int, 3> sig_interrupt_fds;
432
418 QDBusObjectPath wake_lock{}; 433 QDBusObjectPath wake_lock{};
419#endif 434#endif
420 435
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 6ab95b9a5..cdf31b417 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -154,6 +154,7 @@
154 <addaction name="menu_Emulation"/> 154 <addaction name="menu_Emulation"/>
155 <addaction name="menu_View"/> 155 <addaction name="menu_View"/>
156 <addaction name="menu_Tools"/> 156 <addaction name="menu_Tools"/>
157 <addaction name="menu_Multiplayer"/>
157 <addaction name="menu_Help"/> 158 <addaction name="menu_Help"/>
158 </widget> 159 </widget>
159 <action name="action_Install_File_NAND"> 160 <action name="action_Install_File_NAND">
@@ -245,6 +246,43 @@
245 <string>Show Status Bar</string> 246 <string>Show Status Bar</string>
246 </property> 247 </property>
247 </action> 248 </action>
249 <action name="action_View_Lobby">
250 <property name="enabled">
251 <bool>true</bool>
252 </property>
253 <property name="text">
254 <string>Browse Public Game Lobby</string>
255 </property>
256 </action>
257 <action name="action_Start_Room">
258 <property name="enabled">
259 <bool>true</bool>
260 </property>
261 <property name="text">
262 <string>Create Room</string>
263 </property>
264 </action>
265 <action name="action_Leave_Room">
266 <property name="enabled">
267 <bool>false</bool>
268 </property>
269 <property name="text">
270 <string>Leave Room</string>
271 </property>
272 </action>
273 <action name="action_Connect_To_Room">
274 <property name="text">
275 <string>Direct Connect to Room</string>
276 </property>
277 </action>
278 <action name="action_Show_Room">
279 <property name="enabled">
280 <bool>false</bool>
281 </property>
282 <property name="text">
283 <string>Show Current Room</string>
284 </property>
285 </action>
248 <action name="action_Fullscreen"> 286 <action name="action_Fullscreen">
249 <property name="checkable"> 287 <property name="checkable">
250 <bool>true</bool> 288 <bool>true</bool>
diff --git a/src/yuzu/multiplayer/chat_room.cpp b/src/yuzu/multiplayer/chat_room.cpp
new file mode 100644
index 000000000..5837b36ab
--- /dev/null
+++ b/src/yuzu/multiplayer/chat_room.cpp
@@ -0,0 +1,491 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <array>
5#include <future>
6#include <QColor>
7#include <QDesktopServices>
8#include <QFutureWatcher>
9#include <QImage>
10#include <QList>
11#include <QLocale>
12#include <QMenu>
13#include <QMessageBox>
14#include <QMetaType>
15#include <QTime>
16#include <QUrl>
17#include <QtConcurrent/QtConcurrentRun>
18#include "common/logging/log.h"
19#include "core/announce_multiplayer_session.h"
20#include "ui_chat_room.h"
21#include "yuzu/game_list_p.h"
22#include "yuzu/multiplayer/chat_room.h"
23#include "yuzu/multiplayer/message.h"
24#ifdef ENABLE_WEB_SERVICE
25#include "web_service/web_backend.h"
26#endif
27
28class ChatMessage {
29public:
30 explicit ChatMessage(const Network::ChatEntry& chat, Network::RoomNetwork& room_network,
31 QTime ts = {}) {
32 /// Convert the time to their default locale defined format
33 QLocale locale;
34 timestamp = locale.toString(ts.isValid() ? ts : QTime::currentTime(), QLocale::ShortFormat);
35 nickname = QString::fromStdString(chat.nickname);
36 username = QString::fromStdString(chat.username);
37 message = QString::fromStdString(chat.message);
38
39 // Check for user pings
40 QString cur_nickname, cur_username;
41 if (auto room = room_network.GetRoomMember().lock()) {
42 cur_nickname = QString::fromStdString(room->GetNickname());
43 cur_username = QString::fromStdString(room->GetUsername());
44 }
45
46 // Handle pings at the beginning and end of message
47 QString fixed_message = QStringLiteral(" %1 ").arg(message);
48 if (fixed_message.contains(QStringLiteral(" @%1 ").arg(cur_nickname)) ||
49 (!cur_username.isEmpty() &&
50 fixed_message.contains(QStringLiteral(" @%1 ").arg(cur_username)))) {
51
52 contains_ping = true;
53 } else {
54 contains_ping = false;
55 }
56 }
57
58 bool ContainsPing() const {
59 return contains_ping;
60 }
61
62 /// Format the message using the players color
63 QString GetPlayerChatMessage(u16 player) const {
64 auto color = player_color[player % 16];
65 QString name;
66 if (username.isEmpty() || username == nickname) {
67 name = nickname;
68 } else {
69 name = QStringLiteral("%1 (%2)").arg(nickname, username);
70 }
71
72 QString style, text_color;
73 if (ContainsPing()) {
74 // Add a background color to these messages
75 style = QStringLiteral("background-color: %1").arg(QString::fromStdString(ping_color));
76 // Add a font color
77 text_color = QStringLiteral("color='#000000'");
78 }
79
80 return QStringLiteral("[%1] <font color='%2'>&lt;%3&gt;</font> <font style='%4' "
81 "%5>%6</font>")
82 .arg(timestamp, QString::fromStdString(color), name.toHtmlEscaped(), style, text_color,
83 message.toHtmlEscaped());
84 }
85
86private:
87 static constexpr std::array<const char*, 16> player_color = {
88 {"#0000FF", "#FF0000", "#8A2BE2", "#FF69B4", "#1E90FF", "#008000", "#00FF7F", "#B22222",
89 "#DAA520", "#FF4500", "#2E8B57", "#5F9EA0", "#D2691E", "#9ACD32", "#FF7F50", "FFFF00"}};
90 static constexpr char ping_color[] = "#FFFF00";
91
92 QString timestamp;
93 QString nickname;
94 QString username;
95 QString message;
96 bool contains_ping;
97};
98
99class StatusMessage {
100public:
101 explicit StatusMessage(const QString& msg, QTime ts = {}) {
102 /// Convert the time to their default locale defined format
103 QLocale locale;
104 timestamp = locale.toString(ts.isValid() ? ts : QTime::currentTime(), QLocale::ShortFormat);
105 message = msg;
106 }
107
108 QString GetSystemChatMessage() const {
109 return QStringLiteral("[%1] <font color='%2'>* %3</font>")
110 .arg(timestamp, QString::fromStdString(system_color), message);
111 }
112
113private:
114 static constexpr const char system_color[] = "#FF8C00";
115 QString timestamp;
116 QString message;
117};
118
119class PlayerListItem : public QStandardItem {
120public:
121 static const int NicknameRole = Qt::UserRole + 1;
122 static const int UsernameRole = Qt::UserRole + 2;
123 static const int AvatarUrlRole = Qt::UserRole + 3;
124 static const int GameNameRole = Qt::UserRole + 4;
125
126 PlayerListItem() = default;
127 explicit PlayerListItem(const std::string& nickname, const std::string& username,
128 const std::string& avatar_url, const std::string& game_name) {
129 setEditable(false);
130 setData(QString::fromStdString(nickname), NicknameRole);
131 setData(QString::fromStdString(username), UsernameRole);
132 setData(QString::fromStdString(avatar_url), AvatarUrlRole);
133 if (game_name.empty()) {
134 setData(QObject::tr("Not playing a game"), GameNameRole);
135 } else {
136 setData(QString::fromStdString(game_name), GameNameRole);
137 }
138 }
139
140 QVariant data(int role) const override {
141 if (role != Qt::DisplayRole) {
142 return QStandardItem::data(role);
143 }
144 QString name;
145 const QString nickname = data(NicknameRole).toString();
146 const QString username = data(UsernameRole).toString();
147 if (username.isEmpty() || username == nickname) {
148 name = nickname;
149 } else {
150 name = QStringLiteral("%1 (%2)").arg(nickname, username);
151 }
152 return QStringLiteral("%1\n %2").arg(name, data(GameNameRole).toString());
153 }
154};
155
156ChatRoom::ChatRoom(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::ChatRoom>()) {
157 ui->setupUi(this);
158
159 // set the item_model for player_view
160
161 player_list = new QStandardItemModel(ui->player_view);
162 ui->player_view->setModel(player_list);
163 ui->player_view->setContextMenuPolicy(Qt::CustomContextMenu);
164 // set a header to make it look better though there is only one column
165 player_list->insertColumns(0, 1);
166 player_list->setHeaderData(0, Qt::Horizontal, tr("Members"));
167
168 ui->chat_history->document()->setMaximumBlockCount(max_chat_lines);
169
170 // register the network structs to use in slots and signals
171 qRegisterMetaType<Network::ChatEntry>();
172 qRegisterMetaType<Network::StatusMessageEntry>();
173 qRegisterMetaType<Network::RoomInformation>();
174 qRegisterMetaType<Network::RoomMember::State>();
175
176 // Connect all the widgets to the appropriate events
177 connect(ui->player_view, &QTreeView::customContextMenuRequested, this,
178 &ChatRoom::PopupContextMenu);
179 connect(ui->chat_message, &QLineEdit::returnPressed, this, &ChatRoom::OnSendChat);
180 connect(ui->chat_message, &QLineEdit::textChanged, this, &ChatRoom::OnChatTextChanged);
181 connect(ui->send_message, &QPushButton::clicked, this, &ChatRoom::OnSendChat);
182}
183
184ChatRoom::~ChatRoom() = default;
185
186void ChatRoom::Initialize(Network::RoomNetwork* room_network_) {
187 room_network = room_network_;
188 // setup the callbacks for network updates
189 if (auto member = room_network->GetRoomMember().lock()) {
190 member->BindOnChatMessageRecieved(
191 [this](const Network::ChatEntry& chat) { emit ChatReceived(chat); });
192 member->BindOnStatusMessageReceived(
193 [this](const Network::StatusMessageEntry& status_message) {
194 emit StatusMessageReceived(status_message);
195 });
196 connect(this, &ChatRoom::ChatReceived, this, &ChatRoom::OnChatReceive);
197 connect(this, &ChatRoom::StatusMessageReceived, this, &ChatRoom::OnStatusMessageReceive);
198 }
199}
200
201void ChatRoom::SetModPerms(bool is_mod) {
202 has_mod_perms = is_mod;
203}
204
205void ChatRoom::RetranslateUi() {
206 ui->retranslateUi(this);
207}
208
209void ChatRoom::Clear() {
210 ui->chat_history->clear();
211 block_list.clear();
212}
213
214void ChatRoom::AppendStatusMessage(const QString& msg) {
215 ui->chat_history->append(StatusMessage(msg).GetSystemChatMessage());
216}
217
218void ChatRoom::AppendChatMessage(const QString& msg) {
219 ui->chat_history->append(msg);
220}
221
222void ChatRoom::SendModerationRequest(Network::RoomMessageTypes type, const std::string& nickname) {
223 if (auto room = room_network->GetRoomMember().lock()) {
224 auto members = room->GetMemberInformation();
225 auto it = std::find_if(members.begin(), members.end(),
226 [&nickname](const Network::RoomMember::MemberInformation& member) {
227 return member.nickname == nickname;
228 });
229 if (it == members.end()) {
230 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::NO_SUCH_USER);
231 return;
232 }
233 room->SendModerationRequest(type, nickname);
234 }
235}
236
237bool ChatRoom::ValidateMessage(const std::string& msg) {
238 return !msg.empty();
239}
240
241void ChatRoom::OnRoomUpdate(const Network::RoomInformation& info) {
242 // TODO(B3N30): change title
243 if (auto room_member = room_network->GetRoomMember().lock()) {
244 SetPlayerList(room_member->GetMemberInformation());
245 }
246}
247
248void ChatRoom::Disable() {
249 ui->send_message->setDisabled(true);
250 ui->chat_message->setDisabled(true);
251}
252
253void ChatRoom::Enable() {
254 ui->send_message->setEnabled(true);
255 ui->chat_message->setEnabled(true);
256}
257
258void ChatRoom::OnChatReceive(const Network::ChatEntry& chat) {
259 if (!ValidateMessage(chat.message)) {
260 return;
261 }
262 if (auto room = room_network->GetRoomMember().lock()) {
263 // get the id of the player
264 auto members = room->GetMemberInformation();
265 auto it = std::find_if(members.begin(), members.end(),
266 [&chat](const Network::RoomMember::MemberInformation& member) {
267 return member.nickname == chat.nickname &&
268 member.username == chat.username;
269 });
270 if (it == members.end()) {
271 LOG_INFO(Network, "Chat message received from unknown player. Ignoring it.");
272 return;
273 }
274 if (block_list.count(chat.nickname)) {
275 LOG_INFO(Network, "Chat message received from blocked player {}. Ignoring it.",
276 chat.nickname);
277 return;
278 }
279 auto player = std::distance(members.begin(), it);
280 ChatMessage m(chat, *room_network);
281 if (m.ContainsPing()) {
282 emit UserPinged();
283 }
284 AppendChatMessage(m.GetPlayerChatMessage(player));
285 }
286}
287
288void ChatRoom::OnStatusMessageReceive(const Network::StatusMessageEntry& status_message) {
289 QString name;
290 if (status_message.username.empty() || status_message.username == status_message.nickname) {
291 name = QString::fromStdString(status_message.nickname);
292 } else {
293 name = QStringLiteral("%1 (%2)").arg(QString::fromStdString(status_message.nickname),
294 QString::fromStdString(status_message.username));
295 }
296 QString message;
297 switch (status_message.type) {
298 case Network::IdMemberJoin:
299 message = tr("%1 has joined").arg(name);
300 break;
301 case Network::IdMemberLeave:
302 message = tr("%1 has left").arg(name);
303 break;
304 case Network::IdMemberKicked:
305 message = tr("%1 has been kicked").arg(name);
306 break;
307 case Network::IdMemberBanned:
308 message = tr("%1 has been banned").arg(name);
309 break;
310 case Network::IdAddressUnbanned:
311 message = tr("%1 has been unbanned").arg(name);
312 break;
313 }
314 if (!message.isEmpty())
315 AppendStatusMessage(message);
316}
317
318void ChatRoom::OnSendChat() {
319 if (auto room = room_network->GetRoomMember().lock()) {
320 if (room->GetState() != Network::RoomMember::State::Joined &&
321 room->GetState() != Network::RoomMember::State::Moderator) {
322
323 return;
324 }
325 auto message = ui->chat_message->text().toStdString();
326 if (!ValidateMessage(message)) {
327 return;
328 }
329 auto nick = room->GetNickname();
330 auto username = room->GetUsername();
331 Network::ChatEntry chat{nick, username, message};
332
333 auto members = room->GetMemberInformation();
334 auto it = std::find_if(members.begin(), members.end(),
335 [&chat](const Network::RoomMember::MemberInformation& member) {
336 return member.nickname == chat.nickname &&
337 member.username == chat.username;
338 });
339 if (it == members.end()) {
340 LOG_INFO(Network, "Cannot find self in the player list when sending a message.");
341 }
342 auto player = std::distance(members.begin(), it);
343 ChatMessage m(chat, *room_network);
344 room->SendChatMessage(message);
345 AppendChatMessage(m.GetPlayerChatMessage(player));
346 ui->chat_message->clear();
347 }
348}
349
350void ChatRoom::UpdateIconDisplay() {
351 for (int row = 0; row < player_list->invisibleRootItem()->rowCount(); ++row) {
352 QStandardItem* item = player_list->invisibleRootItem()->child(row);
353 const std::string avatar_url =
354 item->data(PlayerListItem::AvatarUrlRole).toString().toStdString();
355 if (icon_cache.count(avatar_url)) {
356 item->setData(icon_cache.at(avatar_url), Qt::DecorationRole);
357 } else {
358 item->setData(QIcon::fromTheme(QStringLiteral("no_avatar")).pixmap(48),
359 Qt::DecorationRole);
360 }
361 }
362}
363
364void ChatRoom::SetPlayerList(const Network::RoomMember::MemberList& member_list) {
365 // TODO(B3N30): Remember which row is selected
366 player_list->removeRows(0, player_list->rowCount());
367 for (const auto& member : member_list) {
368 if (member.nickname.empty())
369 continue;
370 QStandardItem* name_item = new PlayerListItem(member.nickname, member.username,
371 member.avatar_url, member.game_info.name);
372
373#ifdef ENABLE_WEB_SERVICE
374 if (!icon_cache.count(member.avatar_url) && !member.avatar_url.empty()) {
375 // Start a request to get the member's avatar
376 const QUrl url(QString::fromStdString(member.avatar_url));
377 QFuture<std::string> future = QtConcurrent::run([url] {
378 WebService::Client client(
379 QStringLiteral("%1://%2").arg(url.scheme(), url.host()).toStdString(), "", "");
380 auto result = client.GetImage(url.path().toStdString(), true);
381 if (result.returned_data.empty()) {
382 LOG_ERROR(WebService, "Failed to get avatar");
383 }
384 return result.returned_data;
385 });
386 auto* future_watcher = new QFutureWatcher<std::string>(this);
387 connect(future_watcher, &QFutureWatcher<std::string>::finished, this,
388 [this, future_watcher, avatar_url = member.avatar_url] {
389 const std::string result = future_watcher->result();
390 if (result.empty())
391 return;
392 QPixmap pixmap;
393 if (!pixmap.loadFromData(reinterpret_cast<const u8*>(result.data()),
394 static_cast<uint>(result.size())))
395 return;
396 icon_cache[avatar_url] =
397 pixmap.scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
398 // Update all the displayed icons with the new icon_cache
399 UpdateIconDisplay();
400 });
401 future_watcher->setFuture(future);
402 }
403#endif
404
405 player_list->invisibleRootItem()->appendRow(name_item);
406 }
407 UpdateIconDisplay();
408 // TODO(B3N30): Restore row selection
409}
410
411void ChatRoom::OnChatTextChanged() {
412 if (ui->chat_message->text().length() > static_cast<int>(Network::MaxMessageSize))
413 ui->chat_message->setText(
414 ui->chat_message->text().left(static_cast<int>(Network::MaxMessageSize)));
415}
416
417void ChatRoom::PopupContextMenu(const QPoint& menu_location) {
418 QModelIndex item = ui->player_view->indexAt(menu_location);
419 if (!item.isValid())
420 return;
421
422 std::string nickname =
423 player_list->item(item.row())->data(PlayerListItem::NicknameRole).toString().toStdString();
424
425 QMenu context_menu;
426
427 QString username = player_list->item(item.row())->data(PlayerListItem::UsernameRole).toString();
428 if (!username.isEmpty()) {
429 QAction* view_profile_action = context_menu.addAction(tr("View Profile"));
430 connect(view_profile_action, &QAction::triggered, [username] {
431 QDesktopServices::openUrl(
432 QUrl(QStringLiteral("https://community.citra-emu.org/u/%1").arg(username)));
433 });
434 }
435
436 std::string cur_nickname;
437 if (auto room = room_network->GetRoomMember().lock()) {
438 cur_nickname = room->GetNickname();
439 }
440
441 if (nickname != cur_nickname) { // You can't block yourself
442 QAction* block_action = context_menu.addAction(tr("Block Player"));
443
444 block_action->setCheckable(true);
445 block_action->setChecked(block_list.count(nickname) > 0);
446
447 connect(block_action, &QAction::triggered, [this, nickname] {
448 if (block_list.count(nickname)) {
449 block_list.erase(nickname);
450 } else {
451 QMessageBox::StandardButton result = QMessageBox::question(
452 this, tr("Block Player"),
453 tr("When you block a player, you will no longer receive chat messages from "
454 "them.<br><br>Are you sure you would like to block %1?")
455 .arg(QString::fromStdString(nickname)),
456 QMessageBox::Yes | QMessageBox::No);
457 if (result == QMessageBox::Yes)
458 block_list.emplace(nickname);
459 }
460 });
461 }
462
463 if (has_mod_perms && nickname != cur_nickname) { // You can't kick or ban yourself
464 context_menu.addSeparator();
465
466 QAction* kick_action = context_menu.addAction(tr("Kick"));
467 QAction* ban_action = context_menu.addAction(tr("Ban"));
468
469 connect(kick_action, &QAction::triggered, [this, nickname] {
470 QMessageBox::StandardButton result =
471 QMessageBox::question(this, tr("Kick Player"),
472 tr("Are you sure you would like to <b>kick</b> %1?")
473 .arg(QString::fromStdString(nickname)),
474 QMessageBox::Yes | QMessageBox::No);
475 if (result == QMessageBox::Yes)
476 SendModerationRequest(Network::IdModKick, nickname);
477 });
478 connect(ban_action, &QAction::triggered, [this, nickname] {
479 QMessageBox::StandardButton result = QMessageBox::question(
480 this, tr("Ban Player"),
481 tr("Are you sure you would like to <b>kick and ban</b> %1?\n\nThis would "
482 "ban both their forum username and their IP address.")
483 .arg(QString::fromStdString(nickname)),
484 QMessageBox::Yes | QMessageBox::No);
485 if (result == QMessageBox::Yes)
486 SendModerationRequest(Network::IdModBan, nickname);
487 });
488 }
489
490 context_menu.exec(ui->player_view->viewport()->mapToGlobal(menu_location));
491}
diff --git a/src/yuzu/multiplayer/chat_room.h b/src/yuzu/multiplayer/chat_room.h
new file mode 100644
index 000000000..01c70fad0
--- /dev/null
+++ b/src/yuzu/multiplayer/chat_room.h
@@ -0,0 +1,75 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <unordered_set>
8#include <QDialog>
9#include <QSortFilterProxyModel>
10#include <QStandardItemModel>
11#include <QVariant>
12#include "network/network.h"
13
14namespace Ui {
15class ChatRoom;
16}
17
18namespace Core {
19class AnnounceMultiplayerSession;
20}
21
22class ConnectionError;
23class ComboBoxProxyModel;
24
25class ChatMessage;
26
27class ChatRoom : public QWidget {
28 Q_OBJECT
29
30public:
31 explicit ChatRoom(QWidget* parent);
32 void Initialize(Network::RoomNetwork* room_network);
33 void RetranslateUi();
34 void SetPlayerList(const Network::RoomMember::MemberList& member_list);
35 void Clear();
36 void AppendStatusMessage(const QString& msg);
37 ~ChatRoom();
38
39 void SetModPerms(bool is_mod);
40 void UpdateIconDisplay();
41
42public slots:
43 void OnRoomUpdate(const Network::RoomInformation& info);
44 void OnChatReceive(const Network::ChatEntry&);
45 void OnStatusMessageReceive(const Network::StatusMessageEntry&);
46 void OnSendChat();
47 void OnChatTextChanged();
48 void PopupContextMenu(const QPoint& menu_location);
49 void Disable();
50 void Enable();
51
52signals:
53 void ChatReceived(const Network::ChatEntry&);
54 void StatusMessageReceived(const Network::StatusMessageEntry&);
55 void UserPinged();
56
57private:
58 static constexpr u32 max_chat_lines = 1000;
59 void AppendChatMessage(const QString&);
60 bool ValidateMessage(const std::string&);
61 void SendModerationRequest(Network::RoomMessageTypes type, const std::string& nickname);
62
63 bool has_mod_perms = false;
64 QStandardItemModel* player_list;
65 std::unique_ptr<Ui::ChatRoom> ui;
66 std::unordered_set<std::string> block_list;
67 std::unordered_map<std::string, QPixmap> icon_cache;
68 Network::RoomNetwork* room_network;
69};
70
71Q_DECLARE_METATYPE(Network::ChatEntry);
72Q_DECLARE_METATYPE(Network::StatusMessageEntry);
73Q_DECLARE_METATYPE(Network::RoomInformation);
74Q_DECLARE_METATYPE(Network::RoomMember::State);
75Q_DECLARE_METATYPE(Network::RoomMember::Error);
diff --git a/src/yuzu/multiplayer/chat_room.ui b/src/yuzu/multiplayer/chat_room.ui
new file mode 100644
index 000000000..f2b31b5da
--- /dev/null
+++ b/src/yuzu/multiplayer/chat_room.ui
@@ -0,0 +1,59 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ChatRoom</class>
4 <widget class="QWidget" name="ChatRoom">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>807</width>
10 <height>432</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Room Window</string>
15 </property>
16 <layout class="QHBoxLayout" name="horizontalLayout">
17 <item>
18 <widget class="QTreeView" name="player_view"/>
19 </item>
20 <item>
21 <layout class="QVBoxLayout" name="verticalLayout_4">
22 <item>
23 <widget class="QTextEdit" name="chat_history">
24 <property name="undoRedoEnabled">
25 <bool>false</bool>
26 </property>
27 <property name="readOnly">
28 <bool>true</bool>
29 </property>
30 <property name="textInteractionFlags">
31 <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
32 </property>
33 </widget>
34 </item>
35 <item>
36 <layout class="QHBoxLayout" name="horizontalLayout_3">
37 <item>
38 <widget class="QLineEdit" name="chat_message">
39 <property name="placeholderText">
40 <string>Send Chat Message</string>
41 </property>
42 </widget>
43 </item>
44 <item>
45 <widget class="QPushButton" name="send_message">
46 <property name="text">
47 <string>Send Message</string>
48 </property>
49 </widget>
50 </item>
51 </layout>
52 </item>
53 </layout>
54 </item>
55 </layout>
56 </widget>
57 <resources/>
58 <connections/>
59</ui>
diff --git a/src/yuzu/multiplayer/client_room.cpp b/src/yuzu/multiplayer/client_room.cpp
new file mode 100644
index 000000000..a9859ed70
--- /dev/null
+++ b/src/yuzu/multiplayer/client_room.cpp
@@ -0,0 +1,115 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <future>
5#include <QColor>
6#include <QImage>
7#include <QList>
8#include <QLocale>
9#include <QMetaType>
10#include <QTime>
11#include <QtConcurrent/QtConcurrentRun>
12#include "common/logging/log.h"
13#include "core/announce_multiplayer_session.h"
14#include "ui_client_room.h"
15#include "yuzu/game_list_p.h"
16#include "yuzu/multiplayer/client_room.h"
17#include "yuzu/multiplayer/message.h"
18#include "yuzu/multiplayer/moderation_dialog.h"
19#include "yuzu/multiplayer/state.h"
20
21ClientRoomWindow::ClientRoomWindow(QWidget* parent, Network::RoomNetwork& room_network_)
22 : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
23 ui(std::make_unique<Ui::ClientRoom>()), room_network{room_network_} {
24 ui->setupUi(this);
25 ui->chat->Initialize(&room_network);
26
27 // setup the callbacks for network updates
28 if (auto member = room_network.GetRoomMember().lock()) {
29 member->BindOnRoomInformationChanged(
30 [this](const Network::RoomInformation& info) { emit RoomInformationChanged(info); });
31 member->BindOnStateChanged(
32 [this](const Network::RoomMember::State& state) { emit StateChanged(state); });
33
34 connect(this, &ClientRoomWindow::RoomInformationChanged, this,
35 &ClientRoomWindow::OnRoomUpdate);
36 connect(this, &ClientRoomWindow::StateChanged, this, &::ClientRoomWindow::OnStateChange);
37 // Update the state
38 OnStateChange(member->GetState());
39 } else {
40 // TODO (jroweboy) network was not initialized?
41 }
42
43 connect(ui->disconnect, &QPushButton::clicked, this, &ClientRoomWindow::Disconnect);
44 ui->disconnect->setDefault(false);
45 ui->disconnect->setAutoDefault(false);
46 connect(ui->moderation, &QPushButton::clicked, [this] {
47 ModerationDialog dialog(room_network, this);
48 dialog.exec();
49 });
50 ui->moderation->setDefault(false);
51 ui->moderation->setAutoDefault(false);
52 connect(ui->chat, &ChatRoom::UserPinged, this, &ClientRoomWindow::ShowNotification);
53 UpdateView();
54}
55
56ClientRoomWindow::~ClientRoomWindow() = default;
57
58void ClientRoomWindow::SetModPerms(bool is_mod) {
59 ui->chat->SetModPerms(is_mod);
60 ui->moderation->setVisible(is_mod);
61 ui->moderation->setDefault(false);
62 ui->moderation->setAutoDefault(false);
63}
64
65void ClientRoomWindow::RetranslateUi() {
66 ui->retranslateUi(this);
67 ui->chat->RetranslateUi();
68}
69
70void ClientRoomWindow::OnRoomUpdate(const Network::RoomInformation& info) {
71 UpdateView();
72}
73
74void ClientRoomWindow::OnStateChange(const Network::RoomMember::State& state) {
75 if (state == Network::RoomMember::State::Joined ||
76 state == Network::RoomMember::State::Moderator) {
77
78 ui->chat->Clear();
79 ui->chat->AppendStatusMessage(tr("Connected"));
80 SetModPerms(state == Network::RoomMember::State::Moderator);
81 }
82 UpdateView();
83}
84
85void ClientRoomWindow::Disconnect() {
86 auto parent = static_cast<MultiplayerState*>(parentWidget());
87 if (parent->OnCloseRoom()) {
88 ui->chat->AppendStatusMessage(tr("Disconnected"));
89 close();
90 }
91}
92
93void ClientRoomWindow::UpdateView() {
94 if (auto member = room_network.GetRoomMember().lock()) {
95 if (member->IsConnected()) {
96 ui->chat->Enable();
97 ui->disconnect->setEnabled(true);
98 auto memberlist = member->GetMemberInformation();
99 ui->chat->SetPlayerList(memberlist);
100 const auto information = member->GetRoomInformation();
101 setWindowTitle(QString(tr("%1 (%2/%3 members) - connected"))
102 .arg(QString::fromStdString(information.name))
103 .arg(memberlist.size())
104 .arg(information.member_slots));
105 ui->description->setText(QString::fromStdString(information.description));
106 return;
107 }
108 }
109 // TODO(B3N30): can't get RoomMember*, show error and close window
110 close();
111}
112
113void ClientRoomWindow::UpdateIconDisplay() {
114 ui->chat->UpdateIconDisplay();
115}
diff --git a/src/yuzu/multiplayer/client_room.h b/src/yuzu/multiplayer/client_room.h
new file mode 100644
index 000000000..f338e3c59
--- /dev/null
+++ b/src/yuzu/multiplayer/client_room.h
@@ -0,0 +1,39 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "yuzu/multiplayer/chat_room.h"
7
8namespace Ui {
9class ClientRoom;
10}
11
12class ClientRoomWindow : public QDialog {
13 Q_OBJECT
14
15public:
16 explicit ClientRoomWindow(QWidget* parent, Network::RoomNetwork& room_network_);
17 ~ClientRoomWindow();
18
19 void RetranslateUi();
20 void UpdateIconDisplay();
21
22public slots:
23 void OnRoomUpdate(const Network::RoomInformation&);
24 void OnStateChange(const Network::RoomMember::State&);
25
26signals:
27 void RoomInformationChanged(const Network::RoomInformation&);
28 void StateChanged(const Network::RoomMember::State&);
29 void ShowNotification();
30
31private:
32 void Disconnect();
33 void UpdateView();
34 void SetModPerms(bool is_mod);
35
36 QStandardItemModel* player_list;
37 std::unique_ptr<Ui::ClientRoom> ui;
38 Network::RoomNetwork& room_network;
39};
diff --git a/src/yuzu/multiplayer/client_room.ui b/src/yuzu/multiplayer/client_room.ui
new file mode 100644
index 000000000..97e88b502
--- /dev/null
+++ b/src/yuzu/multiplayer/client_room.ui
@@ -0,0 +1,80 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ClientRoom</class>
4 <widget class="QWidget" name="ClientRoom">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>807</width>
10 <height>432</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Room Window</string>
15 </property>
16 <layout class="QVBoxLayout" name="verticalLayout">
17 <item>
18 <layout class="QVBoxLayout" name="verticalLayout_3">
19 <item>
20 <layout class="QHBoxLayout" name="horizontalLayout">
21 <property name="rightMargin">
22 <number>0</number>
23 </property>
24 <item>
25 <widget class="QLabel" name="description">
26 <property name="text">
27 <string>Room Description</string>
28 </property>
29 </widget>
30 </item>
31 <item>
32 <spacer name="horizontalSpacer">
33 <property name="orientation">
34 <enum>Qt::Horizontal</enum>
35 </property>
36 <property name="sizeHint" stdset="0">
37 <size>
38 <width>40</width>
39 <height>20</height>
40 </size>
41 </property>
42 </spacer>
43 </item>
44 <item>
45 <widget class="QPushButton" name="moderation">
46 <property name="text">
47 <string>Moderation...</string>
48 </property>
49 <property name="visible">
50 <bool>false</bool>
51 </property>
52 </widget>
53 </item>
54 <item>
55 <widget class="QPushButton" name="disconnect">
56 <property name="text">
57 <string>Leave Room</string>
58 </property>
59 </widget>
60 </item>
61 </layout>
62 </item>
63 <item>
64 <widget class="ChatRoom" name="chat" native="true"/>
65 </item>
66 </layout>
67 </item>
68 </layout>
69 </widget>
70 <customwidgets>
71 <customwidget>
72 <class>ChatRoom</class>
73 <extends>QWidget</extends>
74 <header>multiplayer/chat_room.h</header>
75 <container>1</container>
76 </customwidget>
77 </customwidgets>
78 <resources/>
79 <connections/>
80</ui>
diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp
new file mode 100644
index 000000000..9000c4531
--- /dev/null
+++ b/src/yuzu/multiplayer/direct_connect.cpp
@@ -0,0 +1,130 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <QComboBox>
5#include <QFuture>
6#include <QIntValidator>
7#include <QRegExpValidator>
8#include <QString>
9#include <QtConcurrent/QtConcurrentRun>
10#include "common/settings.h"
11#include "network/network.h"
12#include "ui_direct_connect.h"
13#include "yuzu/main.h"
14#include "yuzu/multiplayer/client_room.h"
15#include "yuzu/multiplayer/direct_connect.h"
16#include "yuzu/multiplayer/message.h"
17#include "yuzu/multiplayer/state.h"
18#include "yuzu/multiplayer/validation.h"
19#include "yuzu/uisettings.h"
20
21enum class ConnectionType : u8 { TraversalServer, IP };
22
23DirectConnectWindow::DirectConnectWindow(Network::RoomNetwork& room_network_, QWidget* parent)
24 : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
25 ui(std::make_unique<Ui::DirectConnect>()), room_network{room_network_} {
26
27 ui->setupUi(this);
28
29 // setup the watcher for background connections
30 watcher = new QFutureWatcher<void>;
31 connect(watcher, &QFutureWatcher<void>::finished, this, &DirectConnectWindow::OnConnection);
32
33 ui->nickname->setValidator(validation.GetNickname());
34 ui->nickname->setText(UISettings::values.multiplayer_nickname.GetValue());
35 if (ui->nickname->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) {
36 // Use yuzu Web Service user name as nickname by default
37 ui->nickname->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
38 }
39 ui->ip->setValidator(validation.GetIP());
40 ui->ip->setText(UISettings::values.multiplayer_ip.GetValue());
41 ui->port->setValidator(validation.GetPort());
42 ui->port->setText(QString::number(UISettings::values.multiplayer_port.GetValue()));
43
44 // TODO(jroweboy): Show or hide the connection options based on the current value of the combo
45 // box. Add this back in when the traversal server support is added.
46 connect(ui->connect, &QPushButton::clicked, this, &DirectConnectWindow::Connect);
47}
48
49DirectConnectWindow::~DirectConnectWindow() = default;
50
51void DirectConnectWindow::RetranslateUi() {
52 ui->retranslateUi(this);
53}
54
55void DirectConnectWindow::Connect() {
56 if (!ui->nickname->hasAcceptableInput()) {
57 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID);
58 return;
59 }
60 if (const auto member = room_network.GetRoomMember().lock()) {
61 // Prevent the user from trying to join a room while they are already joining.
62 if (member->GetState() == Network::RoomMember::State::Joining) {
63 return;
64 } else if (member->IsConnected()) {
65 // And ask if they want to leave the room if they are already in one.
66 if (!NetworkMessage::WarnDisconnect()) {
67 return;
68 }
69 }
70 }
71 switch (static_cast<ConnectionType>(ui->connection_type->currentIndex())) {
72 case ConnectionType::TraversalServer:
73 break;
74 case ConnectionType::IP:
75 if (!ui->ip->hasAcceptableInput()) {
76 NetworkMessage::ErrorManager::ShowError(
77 NetworkMessage::ErrorManager::IP_ADDRESS_NOT_VALID);
78 return;
79 }
80 if (!ui->port->hasAcceptableInput()) {
81 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::PORT_NOT_VALID);
82 return;
83 }
84 break;
85 }
86
87 // Store settings
88 UISettings::values.multiplayer_nickname = ui->nickname->text();
89 UISettings::values.multiplayer_ip = ui->ip->text();
90 if (ui->port->isModified() && !ui->port->text().isEmpty()) {
91 UISettings::values.multiplayer_port = ui->port->text().toInt();
92 } else {
93 UISettings::values.multiplayer_port = UISettings::values.multiplayer_port.GetDefault();
94 }
95
96 // attempt to connect in a different thread
97 QFuture<void> f = QtConcurrent::run([&] {
98 if (auto room_member = room_network.GetRoomMember().lock()) {
99 auto port = UISettings::values.multiplayer_port.GetValue();
100 room_member->Join(ui->nickname->text().toStdString(), "",
101 ui->ip->text().toStdString().c_str(), port, 0,
102 Network::NoPreferredMac, ui->password->text().toStdString().c_str());
103 }
104 });
105 watcher->setFuture(f);
106 // and disable widgets and display a connecting while we wait
107 BeginConnecting();
108}
109
110void DirectConnectWindow::BeginConnecting() {
111 ui->connect->setEnabled(false);
112 ui->connect->setText(tr("Connecting"));
113}
114
115void DirectConnectWindow::EndConnecting() {
116 ui->connect->setEnabled(true);
117 ui->connect->setText(tr("Connect"));
118}
119
120void DirectConnectWindow::OnConnection() {
121 EndConnecting();
122
123 if (auto room_member = room_network.GetRoomMember().lock()) {
124 if (room_member->GetState() == Network::RoomMember::State::Joined ||
125 room_member->GetState() == Network::RoomMember::State::Moderator) {
126
127 close();
128 }
129 }
130}
diff --git a/src/yuzu/multiplayer/direct_connect.h b/src/yuzu/multiplayer/direct_connect.h
new file mode 100644
index 000000000..4e1043053
--- /dev/null
+++ b/src/yuzu/multiplayer/direct_connect.h
@@ -0,0 +1,43 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <QDialog>
8#include <QFutureWatcher>
9#include "yuzu/multiplayer/validation.h"
10
11namespace Ui {
12class DirectConnect;
13}
14
15class DirectConnectWindow : public QDialog {
16 Q_OBJECT
17
18public:
19 explicit DirectConnectWindow(Network::RoomNetwork& room_network_, QWidget* parent = nullptr);
20 ~DirectConnectWindow();
21
22 void RetranslateUi();
23
24signals:
25 /**
26 * Signalled by this widget when it is closing itself and destroying any state such as
27 * connections that it might have.
28 */
29 void Closed();
30
31private slots:
32 void OnConnection();
33
34private:
35 void Connect();
36 void BeginConnecting();
37 void EndConnecting();
38
39 QFutureWatcher<void>* watcher;
40 std::unique_ptr<Ui::DirectConnect> ui;
41 Validation validation;
42 Network::RoomNetwork& room_network;
43};
diff --git a/src/yuzu/multiplayer/direct_connect.ui b/src/yuzu/multiplayer/direct_connect.ui
new file mode 100644
index 000000000..681b6bf69
--- /dev/null
+++ b/src/yuzu/multiplayer/direct_connect.ui
@@ -0,0 +1,168 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>DirectConnect</class>
4 <widget class="QWidget" name="DirectConnect">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>455</width>
10 <height>161</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Direct Connect</string>
15 </property>
16 <layout class="QVBoxLayout" name="verticalLayout">
17 <item>
18 <layout class="QVBoxLayout" name="verticalLayout_3">
19 <item>
20 <layout class="QVBoxLayout" name="verticalLayout_2">
21 <item>
22 <layout class="QHBoxLayout" name="horizontalLayout">
23 <property name="spacing">
24 <number>0</number>
25 </property>
26 <property name="leftMargin">
27 <number>0</number>
28 </property>
29 <item>
30 <widget class="QComboBox" name="connection_type">
31 <item>
32 <property name="text">
33 <string>IP Address</string>
34 </property>
35 </item>
36 </widget>
37 </item>
38 <item>
39 <widget class="QWidget" name="ip_container" native="true">
40 <layout class="QHBoxLayout" name="ip_layout">
41 <property name="leftMargin">
42 <number>5</number>
43 </property>
44 <property name="topMargin">
45 <number>0</number>
46 </property>
47 <property name="rightMargin">
48 <number>0</number>
49 </property>
50 <property name="bottomMargin">
51 <number>0</number>
52 </property>
53 <item>
54 <widget class="QLabel" name="label_2">
55 <property name="text">
56 <string>IP</string>
57 </property>
58 </widget>
59 </item>
60 <item>
61 <widget class="QLineEdit" name="ip">
62 <property name="toolTip">
63 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;IPv4 address of the host&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
64 </property>
65 <property name="maxLength">
66 <number>16</number>
67 </property>
68 </widget>
69 </item>
70 <item>
71 <widget class="QLabel" name="label_3">
72 <property name="text">
73 <string>Port</string>
74 </property>
75 </widget>
76 </item>
77 <item>
78 <widget class="QLineEdit" name="port">
79 <property name="toolTip">
80 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Port number the host is listening on&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
81 </property>
82 <property name="maxLength">
83 <number>5</number>
84 </property>
85 <property name="placeholderText">
86 <string>24872</string>
87 </property>
88 </widget>
89 </item>
90 </layout>
91 </widget>
92 </item>
93 </layout>
94 </item>
95 <item>
96 <layout class="QHBoxLayout" name="horizontalLayout_2">
97 <item>
98 <widget class="QLabel" name="label_5">
99 <property name="text">
100 <string>Nickname</string>
101 </property>
102 </widget>
103 </item>
104 <item>
105 <widget class="QLineEdit" name="nickname">
106 <property name="maxLength">
107 <number>20</number>
108 </property>
109 </widget>
110 </item>
111 <item>
112 <widget class="QLabel" name="label">
113 <property name="text">
114 <string>Password</string>
115 </property>
116 </widget>
117 </item>
118 <item>
119 <widget class="QLineEdit" name="password"/>
120 </item>
121 </layout>
122 </item>
123 </layout>
124 </item>
125 <item>
126 <spacer name="verticalSpacer">
127 <property name="orientation">
128 <enum>Qt::Vertical</enum>
129 </property>
130 <property name="sizeHint" stdset="0">
131 <size>
132 <width>20</width>
133 <height>20</height>
134 </size>
135 </property>
136 </spacer>
137 </item>
138 <item>
139 <layout class="QHBoxLayout" name="horizontalLayout_3">
140 <item>
141 <spacer name="horizontalSpacer">
142 <property name="orientation">
143 <enum>Qt::Horizontal</enum>
144 </property>
145 <property name="sizeHint" stdset="0">
146 <size>
147 <width>40</width>
148 <height>20</height>
149 </size>
150 </property>
151 </spacer>
152 </item>
153 <item>
154 <widget class="QPushButton" name="connect">
155 <property name="text">
156 <string>Connect</string>
157 </property>
158 </widget>
159 </item>
160 </layout>
161 </item>
162 </layout>
163 </item>
164 </layout>
165 </widget>
166 <resources/>
167 <connections/>
168</ui>
diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp
new file mode 100644
index 000000000..cb9464b2b
--- /dev/null
+++ b/src/yuzu/multiplayer/host_room.cpp
@@ -0,0 +1,246 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <future>
5#include <QColor>
6#include <QImage>
7#include <QList>
8#include <QLocale>
9#include <QMessageBox>
10#include <QMetaType>
11#include <QTime>
12#include <QtConcurrent/QtConcurrentRun>
13#include "common/logging/log.h"
14#include "common/settings.h"
15#include "core/announce_multiplayer_session.h"
16#include "ui_host_room.h"
17#include "yuzu/game_list_p.h"
18#include "yuzu/main.h"
19#include "yuzu/multiplayer/host_room.h"
20#include "yuzu/multiplayer/message.h"
21#include "yuzu/multiplayer/state.h"
22#include "yuzu/multiplayer/validation.h"
23#include "yuzu/uisettings.h"
24#ifdef ENABLE_WEB_SERVICE
25#include "web_service/verify_user_jwt.h"
26#endif
27
28HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list,
29 std::shared_ptr<Core::AnnounceMultiplayerSession> session,
30 Network::RoomNetwork& room_network_)
31 : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
32 ui(std::make_unique<Ui::HostRoom>()),
33 announce_multiplayer_session(session), room_network{room_network_} {
34 ui->setupUi(this);
35
36 // set up validation for all of the fields
37 ui->room_name->setValidator(validation.GetRoomName());
38 ui->username->setValidator(validation.GetNickname());
39 ui->port->setValidator(validation.GetPort());
40 ui->port->setPlaceholderText(QString::number(Network::DefaultRoomPort));
41
42 // Create a proxy to the game list to display the list of preferred games
43 game_list = new QStandardItemModel;
44 UpdateGameList(list);
45
46 proxy = new ComboBoxProxyModel;
47 proxy->setSourceModel(game_list);
48 proxy->sort(0, Qt::AscendingOrder);
49 ui->game_list->setModel(proxy);
50
51 // Connect all the widgets to the appropriate events
52 connect(ui->host, &QPushButton::clicked, this, &HostRoomWindow::Host);
53
54 // Restore the settings:
55 ui->username->setText(UISettings::values.multiplayer_room_nickname.GetValue());
56 if (ui->username->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) {
57 // Use yuzu Web Service user name as nickname by default
58 ui->username->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
59 }
60 ui->room_name->setText(UISettings::values.multiplayer_room_name.GetValue());
61 ui->port->setText(QString::number(UISettings::values.multiplayer_room_port.GetValue()));
62 ui->max_player->setValue(UISettings::values.multiplayer_max_player.GetValue());
63 int index = UISettings::values.multiplayer_host_type.GetValue();
64 if (index < ui->host_type->count()) {
65 ui->host_type->setCurrentIndex(index);
66 }
67 index = ui->game_list->findData(UISettings::values.multiplayer_game_id.GetValue(),
68 GameListItemPath::ProgramIdRole);
69 if (index != -1) {
70 ui->game_list->setCurrentIndex(index);
71 }
72 ui->room_description->setText(UISettings::values.multiplayer_room_description.GetValue());
73}
74
75HostRoomWindow::~HostRoomWindow() = default;
76
77void HostRoomWindow::UpdateGameList(QStandardItemModel* list) {
78 game_list->clear();
79 for (int i = 0; i < list->rowCount(); i++) {
80 auto parent = list->item(i, 0);
81 for (int j = 0; j < parent->rowCount(); j++) {
82 game_list->appendRow(parent->child(j)->clone());
83 }
84 }
85}
86
87void HostRoomWindow::RetranslateUi() {
88 ui->retranslateUi(this);
89}
90
91std::unique_ptr<Network::VerifyUser::Backend> HostRoomWindow::CreateVerifyBackend(
92 bool use_validation) const {
93 std::unique_ptr<Network::VerifyUser::Backend> verify_backend;
94 if (use_validation) {
95#ifdef ENABLE_WEB_SERVICE
96 verify_backend =
97 std::make_unique<WebService::VerifyUserJWT>(Settings::values.web_api_url.GetValue());
98#else
99 verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
100#endif
101 } else {
102 verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
103 }
104 return verify_backend;
105}
106
107void HostRoomWindow::Host() {
108 if (!ui->username->hasAcceptableInput()) {
109 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID);
110 return;
111 }
112 if (!ui->room_name->hasAcceptableInput()) {
113 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::ROOMNAME_NOT_VALID);
114 return;
115 }
116 if (!ui->port->hasAcceptableInput()) {
117 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::PORT_NOT_VALID);
118 return;
119 }
120 if (ui->game_list->currentIndex() == -1) {
121 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::GAME_NOT_SELECTED);
122 return;
123 }
124 if (auto member = room_network.GetRoomMember().lock()) {
125 if (member->GetState() == Network::RoomMember::State::Joining) {
126 return;
127 } else if (member->IsConnected()) {
128 auto parent = static_cast<MultiplayerState*>(parentWidget());
129 if (!parent->OnCloseRoom()) {
130 close();
131 return;
132 }
133 }
134 ui->host->setDisabled(true);
135
136 const AnnounceMultiplayerRoom::GameInfo game{
137 .name = ui->game_list->currentData(Qt::DisplayRole).toString().toStdString(),
138 .id = ui->game_list->currentData(GameListItemPath::ProgramIdRole).toULongLong(),
139 };
140 const auto port =
141 ui->port->isModified() ? ui->port->text().toInt() : Network::DefaultRoomPort;
142 const auto password = ui->password->text().toStdString();
143 const bool is_public = ui->host_type->currentIndex() == 0;
144 Network::Room::BanList ban_list{};
145 if (ui->load_ban_list->isChecked()) {
146 ban_list = UISettings::values.multiplayer_ban_list;
147 }
148 if (auto room = room_network.GetRoom().lock()) {
149 const bool created =
150 room->Create(ui->room_name->text().toStdString(),
151 ui->room_description->toPlainText().toStdString(), "", port, password,
152 ui->max_player->value(), Settings::values.yuzu_username.GetValue(),
153 game, CreateVerifyBackend(is_public), ban_list);
154 if (!created) {
155 NetworkMessage::ErrorManager::ShowError(
156 NetworkMessage::ErrorManager::COULD_NOT_CREATE_ROOM);
157 LOG_ERROR(Network, "Could not create room!");
158 ui->host->setEnabled(true);
159 return;
160 }
161 }
162 // Start the announce session if they chose Public
163 if (is_public) {
164 if (auto session = announce_multiplayer_session.lock()) {
165 // Register the room first to ensure verify_uid is present when we connect
166 WebService::WebResult result = session->Register();
167 if (result.result_code != WebService::WebResult::Code::Success) {
168 QMessageBox::warning(
169 this, tr("Error"),
170 tr("Failed to announce the room to the public lobby. In order to host a "
171 "room publicly, you must have a valid yuzu account configured in "
172 "Emulation -> Configure -> Web. If you do not want to publish a room in "
173 "the public lobby, then select Unlisted instead.\nDebug Message: ") +
174 QString::fromStdString(result.result_string),
175 QMessageBox::Ok);
176 ui->host->setEnabled(true);
177 if (auto room = room_network.GetRoom().lock()) {
178 room->Destroy();
179 }
180 return;
181 }
182 session->Start();
183 } else {
184 LOG_ERROR(Network, "Starting announce session failed");
185 }
186 }
187 std::string token;
188#ifdef ENABLE_WEB_SERVICE
189 if (is_public) {
190 WebService::Client client(Settings::values.web_api_url.GetValue(),
191 Settings::values.yuzu_username.GetValue(),
192 Settings::values.yuzu_token.GetValue());
193 if (auto room = room_network.GetRoom().lock()) {
194 token = client.GetExternalJWT(room->GetVerifyUID()).returned_data;
195 }
196 if (token.empty()) {
197 LOG_ERROR(WebService, "Could not get external JWT, verification may fail");
198 } else {
199 LOG_INFO(WebService, "Successfully requested external JWT: size={}", token.size());
200 }
201 }
202#endif
203 // TODO: Check what to do with this
204 member->Join(ui->username->text().toStdString(), "", "127.0.0.1", port, 0,
205 Network::NoPreferredMac, password, token);
206
207 // Store settings
208 UISettings::values.multiplayer_room_nickname = ui->username->text();
209 UISettings::values.multiplayer_room_name = ui->room_name->text();
210 UISettings::values.multiplayer_game_id =
211 ui->game_list->currentData(GameListItemPath::ProgramIdRole).toLongLong();
212 UISettings::values.multiplayer_max_player = ui->max_player->value();
213
214 UISettings::values.multiplayer_host_type = ui->host_type->currentIndex();
215 if (ui->port->isModified() && !ui->port->text().isEmpty()) {
216 UISettings::values.multiplayer_room_port = ui->port->text().toInt();
217 } else {
218 UISettings::values.multiplayer_room_port = Network::DefaultRoomPort;
219 }
220 UISettings::values.multiplayer_room_description = ui->room_description->toPlainText();
221 ui->host->setEnabled(true);
222 close();
223 }
224}
225
226QVariant ComboBoxProxyModel::data(const QModelIndex& idx, int role) const {
227 if (role != Qt::DisplayRole) {
228 auto val = QSortFilterProxyModel::data(idx, role);
229 // If its the icon, shrink it to 16x16
230 if (role == Qt::DecorationRole)
231 val = val.value<QImage>().scaled(16, 16, Qt::KeepAspectRatio);
232 return val;
233 }
234 std::string filename;
235 Common::SplitPath(
236 QSortFilterProxyModel::data(idx, GameListItemPath::FullPathRole).toString().toStdString(),
237 nullptr, &filename, nullptr);
238 QString title = QSortFilterProxyModel::data(idx, GameListItemPath::TitleRole).toString();
239 return title.isEmpty() ? QString::fromStdString(filename) : title;
240}
241
242bool ComboBoxProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const {
243 auto leftData = left.data(GameListItemPath::TitleRole).toString();
244 auto rightData = right.data(GameListItemPath::TitleRole).toString();
245 return leftData.compare(rightData) < 0;
246}
diff --git a/src/yuzu/multiplayer/host_room.h b/src/yuzu/multiplayer/host_room.h
new file mode 100644
index 000000000..a968042d0
--- /dev/null
+++ b/src/yuzu/multiplayer/host_room.h
@@ -0,0 +1,75 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <QDialog>
8#include <QSortFilterProxyModel>
9#include <QStandardItemModel>
10#include <QVariant>
11#include "network/network.h"
12#include "yuzu/multiplayer/chat_room.h"
13#include "yuzu/multiplayer/validation.h"
14
15namespace Ui {
16class HostRoom;
17}
18
19namespace Core {
20class AnnounceMultiplayerSession;
21}
22
23class ConnectionError;
24class ComboBoxProxyModel;
25
26class ChatMessage;
27
28namespace Network::VerifyUser {
29class Backend;
30};
31
32class HostRoomWindow : public QDialog {
33 Q_OBJECT
34
35public:
36 explicit HostRoomWindow(QWidget* parent, QStandardItemModel* list,
37 std::shared_ptr<Core::AnnounceMultiplayerSession> session,
38 Network::RoomNetwork& room_network_);
39 ~HostRoomWindow();
40
41 /**
42 * Updates the dialog with a new game list model.
43 * This model should be the original model of the game list.
44 */
45 void UpdateGameList(QStandardItemModel* list);
46 void RetranslateUi();
47
48private:
49 void Host();
50 std::unique_ptr<Network::VerifyUser::Backend> CreateVerifyBackend(bool use_validation) const;
51
52 std::unique_ptr<Ui::HostRoom> ui;
53 std::weak_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
54 QStandardItemModel* game_list;
55 ComboBoxProxyModel* proxy;
56 Validation validation;
57 Network::RoomNetwork& room_network;
58};
59
60/**
61 * Proxy Model for the game list combo box so we can reuse the game list model while still
62 * displaying the fields slightly differently
63 */
64class ComboBoxProxyModel : public QSortFilterProxyModel {
65 Q_OBJECT
66
67public:
68 int columnCount(const QModelIndex& idx) const override {
69 return 1;
70 }
71
72 QVariant data(const QModelIndex& idx, int role) const override;
73
74 bool lessThan(const QModelIndex& left, const QModelIndex& right) const override;
75};
diff --git a/src/yuzu/multiplayer/host_room.ui b/src/yuzu/multiplayer/host_room.ui
new file mode 100644
index 000000000..d54cf49c6
--- /dev/null
+++ b/src/yuzu/multiplayer/host_room.ui
@@ -0,0 +1,207 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>HostRoom</class>
4 <widget class="QWidget" name="HostRoom">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>607</width>
10 <height>211</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Create Room</string>
15 </property>
16 <layout class="QVBoxLayout" name="verticalLayout_3">
17 <item>
18 <widget class="QWidget" name="settings" native="true">
19 <layout class="QHBoxLayout">
20 <property name="leftMargin">
21 <number>0</number>
22 </property>
23 <property name="topMargin">
24 <number>0</number>
25 </property>
26 <property name="rightMargin">
27 <number>0</number>
28 </property>
29 <item>
30 <layout class="QFormLayout" name="formLayout_2">
31 <property name="labelAlignment">
32 <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
33 </property>
34 <item row="0" column="0">
35 <widget class="QLabel" name="label">
36 <property name="text">
37 <string>Room Name</string>
38 </property>
39 </widget>
40 </item>
41 <item row="0" column="1">
42 <widget class="QLineEdit" name="room_name">
43 <property name="maxLength">
44 <number>50</number>
45 </property>
46 </widget>
47 </item>
48 <item row="1" column="0">
49 <widget class="QLabel" name="label_3">
50 <property name="text">
51 <string>Preferred Game</string>
52 </property>
53 </widget>
54 </item>
55 <item row="1" column="1">
56 <widget class="QComboBox" name="game_list"/>
57 </item>
58 <item row="2" column="0">
59 <widget class="QLabel" name="label_2">
60 <property name="text">
61 <string>Max Players</string>
62 </property>
63 </widget>
64 </item>
65 <item row="2" column="1">
66 <widget class="QSpinBox" name="max_player">
67 <property name="minimum">
68 <number>2</number>
69 </property>
70 <property name="maximum">
71 <number>16</number>
72 </property>
73 <property name="value">
74 <number>8</number>
75 </property>
76 </widget>
77 </item>
78 </layout>
79 </item>
80 <item>
81 <layout class="QFormLayout" name="formLayout">
82 <property name="labelAlignment">
83 <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
84 </property>
85 <item row="0" column="1">
86 <widget class="QLineEdit" name="username"/>
87 </item>
88 <item row="0" column="0">
89 <widget class="QLabel" name="label_6">
90 <property name="text">
91 <string>Username</string>
92 </property>
93 </widget>
94 </item>
95 <item row="1" column="1">
96 <widget class="QLineEdit" name="password">
97 <property name="echoMode">
98 <enum>QLineEdit::PasswordEchoOnEdit</enum>
99 </property>
100 <property name="placeholderText">
101 <string>(Leave blank for open game)</string>
102 </property>
103 </widget>
104 </item>
105 <item row="2" column="1">
106 <widget class="QLineEdit" name="port">
107 <property name="inputMethodHints">
108 <set>Qt::ImhDigitsOnly</set>
109 </property>
110 <property name="maxLength">
111 <number>5</number>
112 </property>
113 </widget>
114 </item>
115 <item row="1" column="0">
116 <widget class="QLabel" name="label_5">
117 <property name="text">
118 <string>Password</string>
119 </property>
120 </widget>
121 </item>
122 <item row="2" column="0">
123 <widget class="QLabel" name="label_4">
124 <property name="text">
125 <string>Port</string>
126 </property>
127 </widget>
128 </item>
129 </layout>
130 </item>
131 </layout>
132 </widget>
133 </item>
134 <item>
135 <layout class="QHBoxLayout" name="horizontalLayout_3">
136 <item>
137 <widget class="QLabel" name="label_7">
138 <property name="text">
139 <string>Room Description</string>
140 </property>
141 </widget>
142 </item>
143 <item>
144 <widget class="QTextEdit" name="room_description"/>
145 </item>
146 </layout>
147 </item>
148 <item>
149 <layout class="QHBoxLayout">
150 <item>
151 <widget class="QCheckBox" name="load_ban_list">
152 <property name="text">
153 <string>Load Previous Ban List</string>
154 </property>
155 <property name="checked">
156 <bool>true</bool>
157 </property>
158 </widget>
159 </item>
160 </layout>
161 </item>
162 <item>
163 <layout class="QHBoxLayout" name="horizontalLayout">
164 <property name="rightMargin">
165 <number>0</number>
166 </property>
167 <item>
168 <spacer name="horizontalSpacer">
169 <property name="orientation">
170 <enum>Qt::Horizontal</enum>
171 </property>
172 <property name="sizeHint" stdset="0">
173 <size>
174 <width>40</width>
175 <height>20</height>
176 </size>
177 </property>
178 </spacer>
179 </item>
180 <item>
181 <widget class="QComboBox" name="host_type">
182 <item>
183 <property name="text">
184 <string>Public</string>
185 </property>
186 </item>
187 <item>
188 <property name="text">
189 <string>Unlisted</string>
190 </property>
191 </item>
192 </widget>
193 </item>
194 <item>
195 <widget class="QPushButton" name="host">
196 <property name="text">
197 <string>Host Room</string>
198 </property>
199 </widget>
200 </item>
201 </layout>
202 </item>
203 </layout>
204 </widget>
205 <resources/>
206 <connections/>
207</ui>
diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp
new file mode 100644
index 000000000..23c2f21ab
--- /dev/null
+++ b/src/yuzu/multiplayer/lobby.cpp
@@ -0,0 +1,367 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <QInputDialog>
5#include <QList>
6#include <QtConcurrent/QtConcurrentRun>
7#include "common/logging/log.h"
8#include "common/settings.h"
9#include "network/network.h"
10#include "ui_lobby.h"
11#include "yuzu/game_list_p.h"
12#include "yuzu/main.h"
13#include "yuzu/multiplayer/client_room.h"
14#include "yuzu/multiplayer/lobby.h"
15#include "yuzu/multiplayer/lobby_p.h"
16#include "yuzu/multiplayer/message.h"
17#include "yuzu/multiplayer/state.h"
18#include "yuzu/multiplayer/validation.h"
19#include "yuzu/uisettings.h"
20#ifdef ENABLE_WEB_SERVICE
21#include "web_service/web_backend.h"
22#endif
23
24Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
25 std::shared_ptr<Core::AnnounceMultiplayerSession> session,
26 Network::RoomNetwork& room_network_)
27 : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
28 ui(std::make_unique<Ui::Lobby>()),
29 announce_multiplayer_session(session), room_network{room_network_} {
30 ui->setupUi(this);
31
32 // setup the watcher for background connections
33 watcher = new QFutureWatcher<void>;
34
35 model = new QStandardItemModel(ui->room_list);
36
37 // Create a proxy to the game list to get the list of games owned
38 game_list = new QStandardItemModel;
39 UpdateGameList(list);
40
41 proxy = new LobbyFilterProxyModel(this, game_list);
42 proxy->setSourceModel(model);
43 proxy->setDynamicSortFilter(true);
44 proxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
45 proxy->setSortLocaleAware(true);
46 ui->room_list->setModel(proxy);
47 ui->room_list->header()->setSectionResizeMode(QHeaderView::Interactive);
48 ui->room_list->header()->stretchLastSection();
49 ui->room_list->setAlternatingRowColors(true);
50 ui->room_list->setSelectionMode(QHeaderView::SingleSelection);
51 ui->room_list->setSelectionBehavior(QHeaderView::SelectRows);
52 ui->room_list->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
53 ui->room_list->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
54 ui->room_list->setSortingEnabled(true);
55 ui->room_list->setEditTriggers(QHeaderView::NoEditTriggers);
56 ui->room_list->setExpandsOnDoubleClick(false);
57 ui->room_list->setContextMenuPolicy(Qt::CustomContextMenu);
58
59 ui->nickname->setValidator(validation.GetNickname());
60 ui->nickname->setText(UISettings::values.multiplayer_nickname.GetValue());
61 if (ui->nickname->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) {
62 // Use yuzu Web Service user name as nickname by default
63 ui->nickname->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
64 }
65
66 // UI Buttons
67 connect(ui->refresh_list, &QPushButton::clicked, this, &Lobby::RefreshLobby);
68 connect(ui->games_owned, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterOwned);
69 connect(ui->hide_full, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterFull);
70 connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch);
71 connect(ui->room_list, &QTreeView::doubleClicked, this, &Lobby::OnJoinRoom);
72 connect(ui->room_list, &QTreeView::clicked, this, &Lobby::OnExpandRoom);
73
74 // Actions
75 connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this,
76 &Lobby::OnRefreshLobby);
77
78 // manually start a refresh when the window is opening
79 // TODO(jroweboy): if this refresh is slow for people with bad internet, then don't do it as
80 // part of the constructor, but offload the refresh until after the window shown. perhaps emit a
81 // refreshroomlist signal from places that open the lobby
82 RefreshLobby();
83}
84
85Lobby::~Lobby() = default;
86
87void Lobby::UpdateGameList(QStandardItemModel* list) {
88 game_list->clear();
89 for (int i = 0; i < list->rowCount(); i++) {
90 auto parent = list->item(i, 0);
91 for (int j = 0; j < parent->rowCount(); j++) {
92 game_list->appendRow(parent->child(j)->clone());
93 }
94 }
95 if (proxy)
96 proxy->UpdateGameList(game_list);
97}
98
99void Lobby::RetranslateUi() {
100 ui->retranslateUi(this);
101}
102
103QString Lobby::PasswordPrompt() {
104 bool ok;
105 const QString text =
106 QInputDialog::getText(this, tr("Password Required to Join"), tr("Password:"),
107 QLineEdit::Password, QString(), &ok);
108 return ok ? text : QString();
109}
110
111void Lobby::OnExpandRoom(const QModelIndex& index) {
112 QModelIndex member_index = proxy->index(index.row(), Column::MEMBER);
113 auto member_list = proxy->data(member_index, LobbyItemMemberList::MemberListRole).toList();
114}
115
116void Lobby::OnJoinRoom(const QModelIndex& source) {
117 if (const auto member = room_network.GetRoomMember().lock()) {
118 // Prevent the user from trying to join a room while they are already joining.
119 if (member->GetState() == Network::RoomMember::State::Joining) {
120 return;
121 } else if (member->IsConnected()) {
122 // And ask if they want to leave the room if they are already in one.
123 if (!NetworkMessage::WarnDisconnect()) {
124 return;
125 }
126 }
127 }
128 QModelIndex index = source;
129 // If the user double clicks on a child row (aka the player list) then use the parent instead
130 if (source.parent() != QModelIndex()) {
131 index = source.parent();
132 }
133 if (!ui->nickname->hasAcceptableInput()) {
134 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID);
135 return;
136 }
137
138 // Get a password to pass if the room is password protected
139 QModelIndex password_index = proxy->index(index.row(), Column::ROOM_NAME);
140 bool has_password = proxy->data(password_index, LobbyItemName::PasswordRole).toBool();
141 const std::string password = has_password ? PasswordPrompt().toStdString() : "";
142 if (has_password && password.empty()) {
143 return;
144 }
145
146 QModelIndex connection_index = proxy->index(index.row(), Column::HOST);
147 const std::string nickname = ui->nickname->text().toStdString();
148 const std::string ip =
149 proxy->data(connection_index, LobbyItemHost::HostIPRole).toString().toStdString();
150 int port = proxy->data(connection_index, LobbyItemHost::HostPortRole).toInt();
151 const std::string verify_uid =
152 proxy->data(connection_index, LobbyItemHost::HostVerifyUIDRole).toString().toStdString();
153
154 // attempt to connect in a different thread
155 QFuture<void> f = QtConcurrent::run([nickname, ip, port, password, verify_uid, this] {
156 std::string token;
157#ifdef ENABLE_WEB_SERVICE
158 if (!Settings::values.yuzu_username.GetValue().empty() &&
159 !Settings::values.yuzu_token.GetValue().empty()) {
160 WebService::Client client(Settings::values.web_api_url.GetValue(),
161 Settings::values.yuzu_username.GetValue(),
162 Settings::values.yuzu_token.GetValue());
163 token = client.GetExternalJWT(verify_uid).returned_data;
164 if (token.empty()) {
165 LOG_ERROR(WebService, "Could not get external JWT, verification may fail");
166 } else {
167 LOG_INFO(WebService, "Successfully requested external JWT: size={}", token.size());
168 }
169 }
170#endif
171 if (auto room_member = room_network.GetRoomMember().lock()) {
172 room_member->Join(nickname, "", ip.c_str(), port, 0, Network::NoPreferredMac, password,
173 token);
174 }
175 });
176 watcher->setFuture(f);
177
178 // TODO(jroweboy): disable widgets and display a connecting while we wait
179
180 // Save settings
181 UISettings::values.multiplayer_nickname = ui->nickname->text();
182 UISettings::values.multiplayer_ip =
183 proxy->data(connection_index, LobbyItemHost::HostIPRole).toString();
184 UISettings::values.multiplayer_port =
185 proxy->data(connection_index, LobbyItemHost::HostPortRole).toInt();
186}
187
188void Lobby::ResetModel() {
189 model->clear();
190 model->insertColumns(0, Column::TOTAL);
191 model->setHeaderData(Column::EXPAND, Qt::Horizontal, QString(), Qt::DisplayRole);
192 model->setHeaderData(Column::ROOM_NAME, Qt::Horizontal, tr("Room Name"), Qt::DisplayRole);
193 model->setHeaderData(Column::GAME_NAME, Qt::Horizontal, tr("Preferred Game"), Qt::DisplayRole);
194 model->setHeaderData(Column::HOST, Qt::Horizontal, tr("Host"), Qt::DisplayRole);
195 model->setHeaderData(Column::MEMBER, Qt::Horizontal, tr("Players"), Qt::DisplayRole);
196}
197
198void Lobby::RefreshLobby() {
199 if (auto session = announce_multiplayer_session.lock()) {
200 ResetModel();
201 ui->refresh_list->setEnabled(false);
202 ui->refresh_list->setText(tr("Refreshing"));
203 room_list_watcher.setFuture(
204 QtConcurrent::run([session]() { return session->GetRoomList(); }));
205 } else {
206 // TODO(jroweboy): Display an error box about announce couldn't be started
207 }
208}
209
210void Lobby::OnRefreshLobby() {
211 AnnounceMultiplayerRoom::RoomList new_room_list = room_list_watcher.result();
212 for (auto room : new_room_list) {
213 // find the icon for the game if this person owns that game.
214 QPixmap smdh_icon;
215 for (int r = 0; r < game_list->rowCount(); ++r) {
216 auto index = game_list->index(r, 0);
217 auto game_id = game_list->data(index, GameListItemPath::ProgramIdRole).toULongLong();
218 if (game_id != 0 && room.information.preferred_game.id == game_id) {
219 smdh_icon = game_list->data(index, Qt::DecorationRole).value<QPixmap>();
220 }
221 }
222
223 QList<QVariant> members;
224 for (auto member : room.members) {
225 QVariant var;
226 var.setValue(LobbyMember{QString::fromStdString(member.username),
227 QString::fromStdString(member.nickname), member.game.id,
228 QString::fromStdString(member.game.name)});
229 members.append(var);
230 }
231
232 auto first_item = new LobbyItem();
233 auto row = QList<QStandardItem*>({
234 first_item,
235 new LobbyItemName(room.has_password, QString::fromStdString(room.information.name)),
236 new LobbyItemGame(room.information.preferred_game.id,
237 QString::fromStdString(room.information.preferred_game.name),
238 smdh_icon),
239 new LobbyItemHost(QString::fromStdString(room.information.host_username),
240 QString::fromStdString(room.ip), room.information.port,
241 QString::fromStdString(room.verify_uid)),
242 new LobbyItemMemberList(members, room.information.member_slots),
243 });
244 model->appendRow(row);
245 // To make the rows expandable, add the member data as a child of the first column of the
246 // rows with people in them and have qt set them to colspan after the model is finished
247 // resetting
248 if (!room.information.description.empty()) {
249 first_item->appendRow(
250 new LobbyItemDescription(QString::fromStdString(room.information.description)));
251 }
252 if (!room.members.empty()) {
253 first_item->appendRow(new LobbyItemExpandedMemberList(members));
254 }
255 }
256
257 // Reenable the refresh button and resize the columns
258 ui->refresh_list->setEnabled(true);
259 ui->refresh_list->setText(tr("Refresh List"));
260 ui->room_list->header()->stretchLastSection();
261 for (int i = 0; i < Column::TOTAL - 1; ++i) {
262 ui->room_list->resizeColumnToContents(i);
263 }
264
265 // Set the member list child items to span all columns
266 for (int i = 0; i < proxy->rowCount(); i++) {
267 auto parent = model->item(i, 0);
268 for (int j = 0; j < parent->rowCount(); j++) {
269 ui->room_list->setFirstColumnSpanned(j, proxy->index(i, 0), true);
270 }
271 }
272}
273
274LobbyFilterProxyModel::LobbyFilterProxyModel(QWidget* parent, QStandardItemModel* list)
275 : QSortFilterProxyModel(parent), game_list(list) {}
276
277void LobbyFilterProxyModel::UpdateGameList(QStandardItemModel* list) {
278 game_list = list;
279}
280
281bool LobbyFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const {
282 // Prioritize filters by fastest to compute
283
284 // pass over any child rows (aka row that shows the players in the room)
285 if (sourceParent != QModelIndex()) {
286 return true;
287 }
288
289 // filter by filled rooms
290 if (filter_full) {
291 QModelIndex member_list = sourceModel()->index(sourceRow, Column::MEMBER, sourceParent);
292 int player_count =
293 sourceModel()->data(member_list, LobbyItemMemberList::MemberListRole).toList().size();
294 int max_players =
295 sourceModel()->data(member_list, LobbyItemMemberList::MaxPlayerRole).toInt();
296 if (player_count >= max_players) {
297 return false;
298 }
299 }
300
301 // filter by search parameters
302 if (!filter_search.isEmpty()) {
303 QModelIndex game_name = sourceModel()->index(sourceRow, Column::GAME_NAME, sourceParent);
304 QModelIndex room_name = sourceModel()->index(sourceRow, Column::ROOM_NAME, sourceParent);
305 QModelIndex host_name = sourceModel()->index(sourceRow, Column::HOST, sourceParent);
306 bool preferred_game_match = sourceModel()
307 ->data(game_name, LobbyItemGame::GameNameRole)
308 .toString()
309 .contains(filter_search, filterCaseSensitivity());
310 bool room_name_match = sourceModel()
311 ->data(room_name, LobbyItemName::NameRole)
312 .toString()
313 .contains(filter_search, filterCaseSensitivity());
314 bool username_match = sourceModel()
315 ->data(host_name, LobbyItemHost::HostUsernameRole)
316 .toString()
317 .contains(filter_search, filterCaseSensitivity());
318 if (!preferred_game_match && !room_name_match && !username_match) {
319 return false;
320 }
321 }
322
323 // filter by game owned
324 if (filter_owned) {
325 QModelIndex game_name = sourceModel()->index(sourceRow, Column::GAME_NAME, sourceParent);
326 QList<QModelIndex> owned_games;
327 for (int r = 0; r < game_list->rowCount(); ++r) {
328 owned_games.append(QModelIndex(game_list->index(r, 0)));
329 }
330 auto current_id = sourceModel()->data(game_name, LobbyItemGame::TitleIDRole).toLongLong();
331 if (current_id == 0) {
332 // TODO(jroweboy): homebrew often doesn't have a game id and this hides them
333 return false;
334 }
335 bool owned = false;
336 for (const auto& game : owned_games) {
337 auto game_id = game_list->data(game, GameListItemPath::ProgramIdRole).toLongLong();
338 if (current_id == game_id) {
339 owned = true;
340 }
341 }
342 if (!owned) {
343 return false;
344 }
345 }
346
347 return true;
348}
349
350void LobbyFilterProxyModel::sort(int column, Qt::SortOrder order) {
351 sourceModel()->sort(column, order);
352}
353
354void LobbyFilterProxyModel::SetFilterOwned(bool filter) {
355 filter_owned = filter;
356 invalidate();
357}
358
359void LobbyFilterProxyModel::SetFilterFull(bool filter) {
360 filter_full = filter;
361 invalidate();
362}
363
364void LobbyFilterProxyModel::SetFilterSearch(const QString& filter) {
365 filter_search = filter;
366 invalidate();
367}
diff --git a/src/yuzu/multiplayer/lobby.h b/src/yuzu/multiplayer/lobby.h
new file mode 100644
index 000000000..82744ca94
--- /dev/null
+++ b/src/yuzu/multiplayer/lobby.h
@@ -0,0 +1,128 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <QDialog>
8#include <QFutureWatcher>
9#include <QSortFilterProxyModel>
10#include <QStandardItemModel>
11#include "common/announce_multiplayer_room.h"
12#include "core/announce_multiplayer_session.h"
13#include "network/network.h"
14#include "yuzu/multiplayer/validation.h"
15
16namespace Ui {
17class Lobby;
18}
19
20class LobbyModel;
21class LobbyFilterProxyModel;
22
23/**
24 * 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.
26 */
27class Lobby : public QDialog {
28 Q_OBJECT
29
30public:
31 explicit Lobby(QWidget* parent, QStandardItemModel* list,
32 std::shared_ptr<Core::AnnounceMultiplayerSession> session,
33 Network::RoomNetwork& room_network_);
34 ~Lobby() override;
35
36 /**
37 * Updates the lobby with a new game list model.
38 * This model should be the original model of the game list.
39 */
40 void UpdateGameList(QStandardItemModel* list);
41 void RetranslateUi();
42
43public slots:
44 /**
45 * Begin the process to pull the latest room list from web services. After the listing is
46 * returned from web services, `LobbyRefreshed` will be signalled
47 */
48 void RefreshLobby();
49
50private slots:
51 /**
52 * Pulls the list of rooms from network and fills out the lobby model with the results
53 */
54 void OnRefreshLobby();
55
56 /**
57 * Handler for single clicking on a room in the list. Expands the treeitem to show player
58 * information for the people in the room
59 *
60 * index - The row of the proxy model that the user wants to join.
61 */
62 void OnExpandRoom(const QModelIndex&);
63
64 /**
65 * Handler for double clicking on a room in the list. Gathers the host ip and port and attempts
66 * to connect. Will also prompt for a password in case one is required.
67 *
68 * index - The row of the proxy model that the user wants to join.
69 */
70 void OnJoinRoom(const QModelIndex&);
71
72signals:
73 void StateChanged(const Network::RoomMember::State&);
74
75private:
76 /**
77 * Removes all entries in the Lobby before refreshing.
78 */
79 void ResetModel();
80
81 /**
82 * Prompts for a password. Returns an empty QString if the user either did not provide a
83 * password or if the user closed the window.
84 */
85 QString PasswordPrompt();
86
87 std::unique_ptr<Ui::Lobby> ui;
88
89 QStandardItemModel* model{};
90 QStandardItemModel* game_list{};
91 LobbyFilterProxyModel* proxy{};
92
93 QFutureWatcher<AnnounceMultiplayerRoom::RoomList> room_list_watcher;
94 std::weak_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
95 QFutureWatcher<void>* watcher;
96 Validation validation;
97 Network::RoomNetwork& room_network;
98};
99
100/**
101 * Proxy Model for filtering the lobby
102 */
103class LobbyFilterProxyModel : public QSortFilterProxyModel {
104 Q_OBJECT;
105
106public:
107 explicit LobbyFilterProxyModel(QWidget* parent, QStandardItemModel* list);
108
109 /**
110 * Updates the filter with a new game list model.
111 * This model should be the processed one created by the Lobby.
112 */
113 void UpdateGameList(QStandardItemModel* list);
114
115 bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
116 void sort(int column, Qt::SortOrder order) override;
117
118public slots:
119 void SetFilterOwned(bool);
120 void SetFilterFull(bool);
121 void SetFilterSearch(const QString&);
122
123private:
124 QStandardItemModel* game_list;
125 bool filter_owned = false;
126 bool filter_full = false;
127 QString filter_search;
128};
diff --git a/src/yuzu/multiplayer/lobby.ui b/src/yuzu/multiplayer/lobby.ui
new file mode 100644
index 000000000..4c9901c9a
--- /dev/null
+++ b/src/yuzu/multiplayer/lobby.ui
@@ -0,0 +1,123 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>Lobby</class>
4 <widget class="QWidget" name="Lobby">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>903</width>
10 <height>487</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Public Room Browser</string>
15 </property>
16 <layout class="QVBoxLayout" name="verticalLayout">
17 <item>
18 <layout class="QVBoxLayout" name="verticalLayout_2">
19 <property name="spacing">
20 <number>3</number>
21 </property>
22 <item>
23 <layout class="QHBoxLayout" name="horizontalLayout_3">
24 <property name="spacing">
25 <number>6</number>
26 </property>
27 <item>
28 <layout class="QHBoxLayout" name="horizontalLayout_5">
29 <item>
30 <widget class="QLabel" name="label">
31 <property name="text">
32 <string>Nickname</string>
33 </property>
34 </widget>
35 </item>
36 <item>
37 <widget class="QLineEdit" name="nickname">
38 <property name="placeholderText">
39 <string>Nickname</string>
40 </property>
41 </widget>
42 </item>
43 <item>
44 <spacer name="horizontalSpacer_2">
45 <property name="orientation">
46 <enum>Qt::Horizontal</enum>
47 </property>
48 <property name="sizeHint" stdset="0">
49 <size>
50 <width>40</width>
51 <height>20</height>
52 </size>
53 </property>
54 </spacer>
55 </item>
56 <item>
57 <widget class="QLabel" name="label_2">
58 <property name="text">
59 <string>Filters</string>
60 </property>
61 </widget>
62 </item>
63 <item>
64 <widget class="QLineEdit" name="search">
65 <property name="placeholderText">
66 <string>Search</string>
67 </property>
68 <property name="clearButtonEnabled">
69 <bool>true</bool>
70 </property>
71 </widget>
72 </item>
73 <item>
74 <widget class="QCheckBox" name="games_owned">
75 <property name="text">
76 <string>Games I Own</string>
77 </property>
78 </widget>
79 </item>
80 <item>
81 <widget class="QCheckBox" name="hide_full">
82 <property name="text">
83 <string>Hide Full Rooms</string>
84 </property>
85 </widget>
86 </item>
87 <item>
88 <spacer name="horizontalSpacer">
89 <property name="orientation">
90 <enum>Qt::Horizontal</enum>
91 </property>
92 <property name="sizeHint" stdset="0">
93 <size>
94 <width>40</width>
95 <height>20</height>
96 </size>
97 </property>
98 </spacer>
99 </item>
100 <item>
101 <widget class="QPushButton" name="refresh_list">
102 <property name="text">
103 <string>Refresh Lobby</string>
104 </property>
105 </widget>
106 </item>
107 </layout>
108 </item>
109 </layout>
110 </item>
111 <item>
112 <widget class="QTreeView" name="room_list"/>
113 </item>
114 <item>
115 <widget class="QWidget" name="widget" native="true"/>
116 </item>
117 </layout>
118 </item>
119 </layout>
120 </widget>
121 <resources/>
122 <connections/>
123</ui>
diff --git a/src/yuzu/multiplayer/lobby_p.h b/src/yuzu/multiplayer/lobby_p.h
new file mode 100644
index 000000000..8071cede4
--- /dev/null
+++ b/src/yuzu/multiplayer/lobby_p.h
@@ -0,0 +1,238 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <utility>
7#include <QPixmap>
8#include <QStandardItem>
9#include <QStandardItemModel>
10#include "common/common_types.h"
11
12namespace Column {
13enum List {
14 EXPAND,
15 ROOM_NAME,
16 GAME_NAME,
17 HOST,
18 MEMBER,
19 TOTAL,
20};
21}
22
23class LobbyItem : public QStandardItem {
24public:
25 LobbyItem() = default;
26 explicit LobbyItem(const QString& string) : QStandardItem(string) {}
27 virtual ~LobbyItem() override = default;
28};
29
30class LobbyItemName : public LobbyItem {
31public:
32 static const int NameRole = Qt::UserRole + 1;
33 static const int PasswordRole = Qt::UserRole + 2;
34
35 LobbyItemName() = default;
36 explicit LobbyItemName(bool has_password, QString name) : LobbyItem() {
37 setData(name, NameRole);
38 setData(has_password, PasswordRole);
39 }
40
41 QVariant data(int role) const override {
42 if (role == Qt::DecorationRole) {
43 bool has_password = data(PasswordRole).toBool();
44 return has_password ? QIcon::fromTheme(QStringLiteral("lock")).pixmap(16) : QIcon();
45 }
46 if (role != Qt::DisplayRole) {
47 return LobbyItem::data(role);
48 }
49 return data(NameRole).toString();
50 }
51
52 bool operator<(const QStandardItem& other) const override {
53 return data(NameRole).toString().localeAwareCompare(other.data(NameRole).toString()) < 0;
54 }
55};
56
57class LobbyItemDescription : public LobbyItem {
58public:
59 static const int DescriptionRole = Qt::UserRole + 1;
60
61 LobbyItemDescription() = default;
62 explicit LobbyItemDescription(QString description) {
63 setData(description, DescriptionRole);
64 }
65
66 QVariant data(int role) const override {
67 if (role != Qt::DisplayRole) {
68 return LobbyItem::data(role);
69 }
70 auto description = data(DescriptionRole).toString();
71 description.prepend(QStringLiteral("Description: "));
72 return description;
73 }
74
75 bool operator<(const QStandardItem& other) const override {
76 return data(DescriptionRole)
77 .toString()
78 .localeAwareCompare(other.data(DescriptionRole).toString()) < 0;
79 }
80};
81
82class LobbyItemGame : public LobbyItem {
83public:
84 static const int TitleIDRole = Qt::UserRole + 1;
85 static const int GameNameRole = Qt::UserRole + 2;
86 static const int GameIconRole = Qt::UserRole + 3;
87
88 LobbyItemGame() = default;
89 explicit LobbyItemGame(u64 title_id, QString game_name, QPixmap smdh_icon) {
90 setData(static_cast<unsigned long long>(title_id), TitleIDRole);
91 setData(game_name, GameNameRole);
92 if (!smdh_icon.isNull()) {
93 setData(smdh_icon, GameIconRole);
94 }
95 }
96
97 QVariant data(int role) const override {
98 if (role == Qt::DecorationRole) {
99 auto val = data(GameIconRole);
100 if (val.isValid()) {
101 val = val.value<QPixmap>().scaled(16, 16, Qt::KeepAspectRatio);
102 }
103 return val;
104 } else if (role != Qt::DisplayRole) {
105 return LobbyItem::data(role);
106 }
107 return data(GameNameRole).toString();
108 }
109
110 bool operator<(const QStandardItem& other) const override {
111 return data(GameNameRole)
112 .toString()
113 .localeAwareCompare(other.data(GameNameRole).toString()) < 0;
114 }
115};
116
117class LobbyItemHost : public LobbyItem {
118public:
119 static const int HostUsernameRole = Qt::UserRole + 1;
120 static const int HostIPRole = Qt::UserRole + 2;
121 static const int HostPortRole = Qt::UserRole + 3;
122 static const int HostVerifyUIDRole = Qt::UserRole + 4;
123
124 LobbyItemHost() = default;
125 explicit LobbyItemHost(QString username, QString ip, u16 port, QString verify_uid) {
126 setData(username, HostUsernameRole);
127 setData(ip, HostIPRole);
128 setData(port, HostPortRole);
129 setData(verify_uid, HostVerifyUIDRole);
130 }
131
132 QVariant data(int role) const override {
133 if (role != Qt::DisplayRole) {
134 return LobbyItem::data(role);
135 }
136 return data(HostUsernameRole).toString();
137 }
138
139 bool operator<(const QStandardItem& other) const override {
140 return data(HostUsernameRole)
141 .toString()
142 .localeAwareCompare(other.data(HostUsernameRole).toString()) < 0;
143 }
144};
145
146class LobbyMember {
147public:
148 LobbyMember() = default;
149 LobbyMember(const LobbyMember& other) = default;
150 explicit LobbyMember(QString username_, QString nickname_, u64 title_id_, QString game_name_)
151 : username(std::move(username_)), nickname(std::move(nickname_)), title_id(title_id_),
152 game_name(std::move(game_name_)) {}
153 ~LobbyMember() = default;
154
155 QString GetName() const {
156 if (username.isEmpty() || username == nickname) {
157 return nickname;
158 } else {
159 return QStringLiteral("%1 (%2)").arg(nickname, username);
160 }
161 }
162 u64 GetTitleId() const {
163 return title_id;
164 }
165 QString GetGameName() const {
166 return game_name;
167 }
168
169private:
170 QString username;
171 QString nickname;
172 u64 title_id;
173 QString game_name;
174};
175
176Q_DECLARE_METATYPE(LobbyMember);
177
178class LobbyItemMemberList : public LobbyItem {
179public:
180 static const int MemberListRole = Qt::UserRole + 1;
181 static const int MaxPlayerRole = Qt::UserRole + 2;
182
183 LobbyItemMemberList() = default;
184 explicit LobbyItemMemberList(QList<QVariant> members, u32 max_players) {
185 setData(members, MemberListRole);
186 setData(max_players, MaxPlayerRole);
187 }
188
189 QVariant data(int role) const override {
190 if (role != Qt::DisplayRole) {
191 return LobbyItem::data(role);
192 }
193 auto members = data(MemberListRole).toList();
194 return QStringLiteral("%1 / %2").arg(QString::number(members.size()),
195 data(MaxPlayerRole).toString());
196 }
197
198 bool operator<(const QStandardItem& other) const override {
199 // sort by rooms that have the most players
200 int left_members = data(MemberListRole).toList().size();
201 int right_members = other.data(MemberListRole).toList().size();
202 return left_members < right_members;
203 }
204};
205
206/**
207 * Member information for when a lobby is expanded in the UI
208 */
209class LobbyItemExpandedMemberList : public LobbyItem {
210public:
211 static const int MemberListRole = Qt::UserRole + 1;
212
213 LobbyItemExpandedMemberList() = default;
214 explicit LobbyItemExpandedMemberList(QList<QVariant> members) {
215 setData(members, MemberListRole);
216 }
217
218 QVariant data(int role) const override {
219 if (role != Qt::DisplayRole) {
220 return LobbyItem::data(role);
221 }
222 auto members = data(MemberListRole).toList();
223 QString out;
224 bool first = true;
225 for (const auto& member : members) {
226 if (!first)
227 out.append(QStringLiteral("\n"));
228 const auto& m = member.value<LobbyMember>();
229 if (m.GetGameName().isEmpty()) {
230 out += QString(QObject::tr("%1 is not playing a game")).arg(m.GetName());
231 } else {
232 out += QString(QObject::tr("%1 is playing %2")).arg(m.GetName(), m.GetGameName());
233 }
234 first = false;
235 }
236 return out;
237 }
238};
diff --git a/src/yuzu/multiplayer/message.cpp b/src/yuzu/multiplayer/message.cpp
new file mode 100644
index 000000000..76ec276ad
--- /dev/null
+++ b/src/yuzu/multiplayer/message.cpp
@@ -0,0 +1,78 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <QMessageBox>
5#include <QString>
6
7#include "yuzu/multiplayer/message.h"
8
9namespace NetworkMessage {
10const ConnectionError ErrorManager::USERNAME_NOT_VALID(
11 QT_TR_NOOP("Username is not valid. Must be 4 to 20 alphanumeric characters."));
12const ConnectionError ErrorManager::ROOMNAME_NOT_VALID(
13 QT_TR_NOOP("Room name is not valid. Must be 4 to 20 alphanumeric characters."));
14const ConnectionError ErrorManager::USERNAME_NOT_VALID_SERVER(
15 QT_TR_NOOP("Username is already in use or not valid. Please choose another."));
16const ConnectionError ErrorManager::IP_ADDRESS_NOT_VALID(
17 QT_TR_NOOP("IP is not a valid IPv4 address."));
18const ConnectionError ErrorManager::PORT_NOT_VALID(
19 QT_TR_NOOP("Port must be a number between 0 to 65535."));
20const ConnectionError ErrorManager::GAME_NOT_SELECTED(QT_TR_NOOP(
21 "You must choose a Preferred Game to host a room. If you do not have any games in your game "
22 "list yet, add a game folder by clicking on the plus icon in the game list."));
23const ConnectionError ErrorManager::NO_INTERNET(
24 QT_TR_NOOP("Unable to find an internet connection. Check your internet settings."));
25const ConnectionError ErrorManager::UNABLE_TO_CONNECT(
26 QT_TR_NOOP("Unable to connect to the host. Verify that the connection settings are correct. If "
27 "you still cannot connect, contact the room host and verify that the host is "
28 "properly configured with the external port forwarded."));
29const ConnectionError ErrorManager::ROOM_IS_FULL(
30 QT_TR_NOOP("Unable to connect to the room because it is already full."));
31const ConnectionError ErrorManager::COULD_NOT_CREATE_ROOM(
32 QT_TR_NOOP("Creating a room failed. Please retry. Restarting yuzu might be necessary."));
33const ConnectionError ErrorManager::HOST_BANNED(
34 QT_TR_NOOP("The host of the room has banned you. Speak with the host to unban you "
35 "or try a different room."));
36const ConnectionError ErrorManager::WRONG_VERSION(
37 QT_TR_NOOP("Version mismatch! Please update to the latest version of yuzu. If the problem "
38 "persists, contact the room host and ask them to update the server."));
39const ConnectionError ErrorManager::WRONG_PASSWORD(QT_TR_NOOP("Incorrect password."));
40const ConnectionError ErrorManager::GENERIC_ERROR(QT_TR_NOOP(
41 "An unknown error occurred. If this error continues to occur, please open an issue"));
42const ConnectionError ErrorManager::LOST_CONNECTION(
43 QT_TR_NOOP("Connection to room lost. Try to reconnect."));
44const ConnectionError ErrorManager::HOST_KICKED(
45 QT_TR_NOOP("You have been kicked by the room host."));
46const ConnectionError ErrorManager::MAC_COLLISION(
47 QT_TR_NOOP("MAC 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(
52 QT_TR_NOOP("You do not have enough permission to perform this action."));
53const 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."));
55
56static bool WarnMessage(const std::string& title, const std::string& text) {
57 return QMessageBox::Ok == QMessageBox::warning(nullptr, QObject::tr(title.c_str()),
58 QObject::tr(text.c_str()),
59 QMessageBox::Ok | QMessageBox::Cancel);
60}
61
62void ErrorManager::ShowError(const ConnectionError& e) {
63 QMessageBox::critical(nullptr, tr("Error"), tr(e.GetString().c_str()));
64}
65
66bool WarnCloseRoom() {
67 return WarnMessage(
68 QT_TR_NOOP("Leave Room"),
69 QT_TR_NOOP("You are about to close the room. Any network connections will be closed."));
70}
71
72bool WarnDisconnect() {
73 return WarnMessage(
74 QT_TR_NOOP("Disconnect"),
75 QT_TR_NOOP("You are about to leave the room. Any network connections will be closed."));
76}
77
78} // namespace NetworkMessage
diff --git a/src/yuzu/multiplayer/message.h b/src/yuzu/multiplayer/message.h
new file mode 100644
index 000000000..eb5c8d1be
--- /dev/null
+++ b/src/yuzu/multiplayer/message.h
@@ -0,0 +1,64 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <utility>
7
8namespace NetworkMessage {
9
10class ConnectionError {
11
12public:
13 explicit ConnectionError(std::string str) : err(std::move(str)) {}
14 const std::string& GetString() const {
15 return err;
16 }
17
18private:
19 std::string err;
20};
21
22class ErrorManager : QObject {
23 Q_OBJECT
24public:
25 /// When the nickname is considered invalid by the client
26 static const ConnectionError USERNAME_NOT_VALID;
27 static const ConnectionError ROOMNAME_NOT_VALID;
28 /// When the nickname is considered invalid by the room server
29 static const ConnectionError USERNAME_NOT_VALID_SERVER;
30 static const ConnectionError IP_ADDRESS_NOT_VALID;
31 static const ConnectionError PORT_NOT_VALID;
32 static const ConnectionError GAME_NOT_SELECTED;
33 static const ConnectionError NO_INTERNET;
34 static const ConnectionError UNABLE_TO_CONNECT;
35 static const ConnectionError ROOM_IS_FULL;
36 static const ConnectionError COULD_NOT_CREATE_ROOM;
37 static const ConnectionError HOST_BANNED;
38 static const ConnectionError WRONG_VERSION;
39 static const ConnectionError WRONG_PASSWORD;
40 static const ConnectionError GENERIC_ERROR;
41 static const ConnectionError LOST_CONNECTION;
42 static const ConnectionError HOST_KICKED;
43 static const ConnectionError MAC_COLLISION;
44 static const ConnectionError CONSOLE_ID_COLLISION;
45 static const ConnectionError PERMISSION_DENIED;
46 static const ConnectionError NO_SUCH_USER;
47 /**
48 * Shows a standard QMessageBox with a error message
49 */
50 static void ShowError(const ConnectionError& e);
51};
52/**
53 * Show a standard QMessageBox with a warning message about leaving the room
54 * return true if the user wants to close the network connection
55 */
56bool WarnCloseRoom();
57
58/**
59 * Show a standard QMessageBox with a warning message about disconnecting from the room
60 * return true if the user wants to disconnect
61 */
62bool WarnDisconnect();
63
64} // namespace NetworkMessage
diff --git a/src/yuzu/multiplayer/moderation_dialog.cpp b/src/yuzu/multiplayer/moderation_dialog.cpp
new file mode 100644
index 000000000..c9b8ed397
--- /dev/null
+++ b/src/yuzu/multiplayer/moderation_dialog.cpp
@@ -0,0 +1,112 @@
1// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <QStandardItem>
5#include <QStandardItemModel>
6#include "network/network.h"
7#include "network/room_member.h"
8#include "ui_moderation_dialog.h"
9#include "yuzu/multiplayer/moderation_dialog.h"
10
11namespace Column {
12enum {
13 SUBJECT,
14 TYPE,
15 COUNT,
16};
17}
18
19ModerationDialog::ModerationDialog(Network::RoomNetwork& room_network_, QWidget* parent)
20 : QDialog(parent), ui(std::make_unique<Ui::ModerationDialog>()), room_network{room_network_} {
21 ui->setupUi(this);
22
23 qRegisterMetaType<Network::Room::BanList>();
24
25 if (auto member = room_network.GetRoomMember().lock()) {
26 callback_handle_status_message = member->BindOnStatusMessageReceived(
27 [this](const Network::StatusMessageEntry& status_message) {
28 emit StatusMessageReceived(status_message);
29 });
30 connect(this, &ModerationDialog::StatusMessageReceived, this,
31 &ModerationDialog::OnStatusMessageReceived);
32 callback_handle_ban_list = member->BindOnBanListReceived(
33 [this](const Network::Room::BanList& ban_list) { emit BanListReceived(ban_list); });
34 connect(this, &ModerationDialog::BanListReceived, this, &ModerationDialog::PopulateBanList);
35 }
36
37 // Initialize the UI
38 model = new QStandardItemModel(ui->ban_list_view);
39 model->insertColumns(0, Column::COUNT);
40 model->setHeaderData(Column::SUBJECT, Qt::Horizontal, tr("Subject"));
41 model->setHeaderData(Column::TYPE, Qt::Horizontal, tr("Type"));
42
43 ui->ban_list_view->setModel(model);
44
45 // Load the ban list in background
46 LoadBanList();
47
48 connect(ui->refresh, &QPushButton::clicked, this, [this] { LoadBanList(); });
49 connect(ui->unban, &QPushButton::clicked, this, [this] {
50 auto index = ui->ban_list_view->currentIndex();
51 SendUnbanRequest(model->item(index.row(), 0)->text());
52 });
53 connect(ui->ban_list_view, &QTreeView::clicked, [this] { ui->unban->setEnabled(true); });
54}
55
56ModerationDialog::~ModerationDialog() {
57 if (callback_handle_status_message) {
58 if (auto room = room_network.GetRoomMember().lock()) {
59 room->Unbind(callback_handle_status_message);
60 }
61 }
62
63 if (callback_handle_ban_list) {
64 if (auto room = room_network.GetRoomMember().lock()) {
65 room->Unbind(callback_handle_ban_list);
66 }
67 }
68}
69
70void ModerationDialog::LoadBanList() {
71 if (auto room = room_network.GetRoomMember().lock()) {
72 ui->refresh->setEnabled(false);
73 ui->refresh->setText(tr("Refreshing"));
74 ui->unban->setEnabled(false);
75 room->RequestBanList();
76 }
77}
78
79void ModerationDialog::PopulateBanList(const Network::Room::BanList& ban_list) {
80 model->removeRows(0, model->rowCount());
81 for (const auto& username : ban_list.first) {
82 QStandardItem* subject_item = new QStandardItem(QString::fromStdString(username));
83 QStandardItem* type_item = new QStandardItem(tr("Forum Username"));
84 model->invisibleRootItem()->appendRow({subject_item, type_item});
85 }
86 for (const auto& ip : ban_list.second) {
87 QStandardItem* subject_item = new QStandardItem(QString::fromStdString(ip));
88 QStandardItem* type_item = new QStandardItem(tr("IP Address"));
89 model->invisibleRootItem()->appendRow({subject_item, type_item});
90 }
91 for (int i = 0; i < Column::COUNT - 1; ++i) {
92 ui->ban_list_view->resizeColumnToContents(i);
93 }
94 ui->refresh->setEnabled(true);
95 ui->refresh->setText(tr("Refresh"));
96 ui->unban->setEnabled(false);
97}
98
99void ModerationDialog::SendUnbanRequest(const QString& subject) {
100 if (auto room = room_network.GetRoomMember().lock()) {
101 room->SendModerationRequest(Network::IdModUnban, subject.toStdString());
102 }
103}
104
105void ModerationDialog::OnStatusMessageReceived(const Network::StatusMessageEntry& status_message) {
106 if (status_message.type != Network::IdMemberBanned &&
107 status_message.type != Network::IdAddressUnbanned)
108 return;
109
110 // Update the ban list for ban/unban
111 LoadBanList();
112}
diff --git a/src/yuzu/multiplayer/moderation_dialog.h b/src/yuzu/multiplayer/moderation_dialog.h
new file mode 100644
index 000000000..e9e5daff7
--- /dev/null
+++ b/src/yuzu/multiplayer/moderation_dialog.h
@@ -0,0 +1,43 @@
1// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <optional>
8#include <QDialog>
9#include "network/room.h"
10#include "network/room_member.h"
11
12namespace Ui {
13class ModerationDialog;
14}
15
16class QStandardItemModel;
17
18class ModerationDialog : public QDialog {
19 Q_OBJECT
20
21public:
22 explicit ModerationDialog(Network::RoomNetwork& room_network_, QWidget* parent = nullptr);
23 ~ModerationDialog();
24
25signals:
26 void StatusMessageReceived(const Network::StatusMessageEntry&);
27 void BanListReceived(const Network::Room::BanList&);
28
29private:
30 void LoadBanList();
31 void PopulateBanList(const Network::Room::BanList& ban_list);
32 void SendUnbanRequest(const QString& subject);
33 void OnStatusMessageReceived(const Network::StatusMessageEntry& status_message);
34
35 std::unique_ptr<Ui::ModerationDialog> ui;
36 QStandardItemModel* model;
37 Network::RoomMember::CallbackHandle<Network::StatusMessageEntry> callback_handle_status_message;
38 Network::RoomMember::CallbackHandle<Network::Room::BanList> callback_handle_ban_list;
39
40 Network::RoomNetwork& room_network;
41};
42
43Q_DECLARE_METATYPE(Network::Room::BanList);
diff --git a/src/yuzu/multiplayer/moderation_dialog.ui b/src/yuzu/multiplayer/moderation_dialog.ui
new file mode 100644
index 000000000..808d99414
--- /dev/null
+++ b/src/yuzu/multiplayer/moderation_dialog.ui
@@ -0,0 +1,84 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ModerationDialog</class>
4 <widget class="QDialog" name="ModerationDialog">
5 <property name="windowTitle">
6 <string>Moderation</string>
7 </property>
8 <property name="geometry">
9 <rect>
10 <x>0</x>
11 <y>0</y>
12 <width>500</width>
13 <height>300</height>
14 </rect>
15 </property>
16 <layout class="QVBoxLayout">
17 <item>
18 <widget class="QGroupBox" name="ban_list_group_box">
19 <property name="title">
20 <string>Ban List</string>
21 </property>
22 <layout class="QVBoxLayout">
23 <item>
24 <layout class="QHBoxLayout">
25 <item>
26 <spacer name="horizontalSpacer">
27 <property name="orientation">
28 <enum>Qt::Horizontal</enum>
29 </property>
30 <property name="sizeHint" stdset="0">
31 <size>
32 <width>40</width>
33 <height>20</height>
34 </size>
35 </property>
36 </spacer>
37 </item>
38 <item>
39 <widget class="QPushButton" name="refresh">
40 <property name="text">
41 <string>Refreshing</string>
42 </property>
43 <property name="enabled">
44 <bool>false</bool>
45 </property>
46 </widget>
47 </item>
48 <item>
49 <widget class="QPushButton" name="unban">
50 <property name="text">
51 <string>Unban</string>
52 </property>
53 <property name="enabled">
54 <bool>false</bool>
55 </property>
56 </widget>
57 </item>
58 </layout>
59 </item>
60 <item>
61 <widget class="QTreeView" name="ban_list_view"/>
62 </item>
63 </layout>
64 </widget>
65 </item>
66 <item>
67 <widget class="QDialogButtonBox" name="buttonBox">
68 <property name="standardButtons">
69 <set>QDialogButtonBox::Ok</set>
70 </property>
71 </widget>
72 </item>
73 </layout>
74 </widget>
75 <connections>
76 <connection>
77 <sender>buttonBox</sender>
78 <signal>accepted()</signal>
79 <receiver>ModerationDialog</receiver>
80 <slot>accept()</slot>
81 </connection>
82 </connections>
83 <resources/>
84</ui>
diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp
new file mode 100644
index 000000000..4149b5232
--- /dev/null
+++ b/src/yuzu/multiplayer/state.cpp
@@ -0,0 +1,308 @@
1// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <QAction>
5#include <QApplication>
6#include <QIcon>
7#include <QMessageBox>
8#include <QStandardItemModel>
9#include "common/announce_multiplayer_room.h"
10#include "common/logging/log.h"
11#include "yuzu/game_list.h"
12#include "yuzu/multiplayer/client_room.h"
13#include "yuzu/multiplayer/direct_connect.h"
14#include "yuzu/multiplayer/host_room.h"
15#include "yuzu/multiplayer/lobby.h"
16#include "yuzu/multiplayer/message.h"
17#include "yuzu/multiplayer/state.h"
18#include "yuzu/uisettings.h"
19#include "yuzu/util/clickable_label.h"
20
21MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_list_model_,
22 QAction* leave_room_, QAction* show_room_,
23 Network::RoomNetwork& room_network_)
24 : QWidget(parent), game_list_model(game_list_model_), leave_room(leave_room_),
25 show_room(show_room_), room_network{room_network_} {
26 if (auto member = room_network.GetRoomMember().lock()) {
27 // register the network structs to use in slots and signals
28 state_callback_handle = member->BindOnStateChanged(
29 [this](const Network::RoomMember::State& state) { emit NetworkStateChanged(state); });
30 connect(this, &MultiplayerState::NetworkStateChanged, this,
31 &MultiplayerState::OnNetworkStateChanged);
32 error_callback_handle = member->BindOnError(
33 [this](const Network::RoomMember::Error& error) { emit NetworkError(error); });
34 connect(this, &MultiplayerState::NetworkError, this, &MultiplayerState::OnNetworkError);
35 }
36
37 qRegisterMetaType<Network::RoomMember::State>();
38 qRegisterMetaType<Network::RoomMember::Error>();
39 qRegisterMetaType<WebService::WebResult>();
40 announce_multiplayer_session = std::make_shared<Core::AnnounceMultiplayerSession>(room_network);
41 announce_multiplayer_session->BindErrorCallback(
42 [this](const WebService::WebResult& result) { emit AnnounceFailed(result); });
43 connect(this, &MultiplayerState::AnnounceFailed, this, &MultiplayerState::OnAnnounceFailed);
44
45 status_text = new ClickableLabel(this);
46 status_icon = new ClickableLabel(this);
47 status_text->setToolTip(tr("Current connection status"));
48 status_text->setText(tr("Not Connected. Click here to find a room!"));
49 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16));
50
51 connect(status_text, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom);
52 connect(status_icon, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom);
53
54 connect(static_cast<QApplication*>(QApplication::instance()), &QApplication::focusChanged, this,
55 [this](QWidget* /*old*/, QWidget* now) {
56 if (client_room && client_room->isAncestorOf(now)) {
57 HideNotification();
58 }
59 });
60}
61
62MultiplayerState::~MultiplayerState() {
63 if (state_callback_handle) {
64 if (auto member = room_network.GetRoomMember().lock()) {
65 member->Unbind(state_callback_handle);
66 }
67 }
68
69 if (error_callback_handle) {
70 if (auto member = room_network.GetRoomMember().lock()) {
71 member->Unbind(error_callback_handle);
72 }
73 }
74}
75
76void MultiplayerState::Close() {
77 if (host_room) {
78 host_room->close();
79 }
80 if (direct_connect) {
81 direct_connect->close();
82 }
83 if (client_room) {
84 client_room->close();
85 }
86 if (lobby) {
87 lobby->close();
88 }
89}
90
91void MultiplayerState::retranslateUi() {
92 status_text->setToolTip(tr("Current connection status"));
93
94 if (current_state == Network::RoomMember::State::Uninitialized) {
95 status_text->setText(tr("Not Connected. Click here to find a room!"));
96 } else if (current_state == Network::RoomMember::State::Joined ||
97 current_state == Network::RoomMember::State::Moderator) {
98
99 status_text->setText(tr("Connected"));
100 } else {
101 status_text->setText(tr("Not Connected"));
102 }
103
104 if (lobby) {
105 lobby->RetranslateUi();
106 }
107 if (host_room) {
108 host_room->RetranslateUi();
109 }
110 if (client_room) {
111 client_room->RetranslateUi();
112 }
113 if (direct_connect) {
114 direct_connect->RetranslateUi();
115 }
116}
117
118void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& state) {
119 LOG_DEBUG(Frontend, "Network State: {}", Network::GetStateStr(state));
120 if (state == Network::RoomMember::State::Joined ||
121 state == Network::RoomMember::State::Moderator) {
122
123 OnOpenNetworkRoom();
124 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16));
125 status_text->setText(tr("Connected"));
126 leave_room->setEnabled(true);
127 show_room->setEnabled(true);
128 } else {
129 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16));
130 status_text->setText(tr("Not Connected"));
131 leave_room->setEnabled(false);
132 show_room->setEnabled(false);
133 }
134
135 current_state = state;
136}
137
138void MultiplayerState::OnNetworkError(const Network::RoomMember::Error& error) {
139 LOG_DEBUG(Frontend, "Network Error: {}", Network::GetErrorStr(error));
140 switch (error) {
141 case Network::RoomMember::Error::LostConnection:
142 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::LOST_CONNECTION);
143 break;
144 case Network::RoomMember::Error::HostKicked:
145 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::HOST_KICKED);
146 break;
147 case Network::RoomMember::Error::CouldNotConnect:
148 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::UNABLE_TO_CONNECT);
149 break;
150 case Network::RoomMember::Error::NameCollision:
151 NetworkMessage::ErrorManager::ShowError(
152 NetworkMessage::ErrorManager::USERNAME_NOT_VALID_SERVER);
153 break;
154 case Network::RoomMember::Error::MacCollision:
155 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::MAC_COLLISION);
156 break;
157 case Network::RoomMember::Error::ConsoleIdCollision:
158 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::CONSOLE_ID_COLLISION);
159 break;
160 case Network::RoomMember::Error::RoomIsFull:
161 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::ROOM_IS_FULL);
162 break;
163 case Network::RoomMember::Error::WrongPassword:
164 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::WRONG_PASSWORD);
165 break;
166 case Network::RoomMember::Error::WrongVersion:
167 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::WRONG_VERSION);
168 break;
169 case Network::RoomMember::Error::HostBanned:
170 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::HOST_BANNED);
171 break;
172 case Network::RoomMember::Error::UnknownError:
173 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::UNABLE_TO_CONNECT);
174 break;
175 case Network::RoomMember::Error::PermissionDenied:
176 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::PERMISSION_DENIED);
177 break;
178 case Network::RoomMember::Error::NoSuchUser:
179 NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::NO_SUCH_USER);
180 break;
181 }
182}
183
184void MultiplayerState::OnAnnounceFailed(const WebService::WebResult& result) {
185 announce_multiplayer_session->Stop();
186 QMessageBox::warning(this, tr("Error"),
187 tr("Failed to update the room information. Please check your Internet "
188 "connection and try hosting the room again.\nDebug Message: ") +
189 QString::fromStdString(result.result_string),
190 QMessageBox::Ok);
191}
192
193void MultiplayerState::UpdateThemedIcons() {
194 if (show_notification) {
195 status_icon->setPixmap(
196 QIcon::fromTheme(QStringLiteral("connected_notification")).pixmap(16));
197 } else if (current_state == Network::RoomMember::State::Joined ||
198 current_state == Network::RoomMember::State::Moderator) {
199
200 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16));
201 } else {
202 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16));
203 }
204 if (client_room)
205 client_room->UpdateIconDisplay();
206}
207
208static void BringWidgetToFront(QWidget* widget) {
209 widget->show();
210 widget->activateWindow();
211 widget->raise();
212}
213
214void MultiplayerState::OnViewLobby() {
215 if (lobby == nullptr) {
216 lobby = new Lobby(this, game_list_model, announce_multiplayer_session, room_network);
217 }
218 BringWidgetToFront(lobby);
219}
220
221void MultiplayerState::OnCreateRoom() {
222 if (host_room == nullptr) {
223 host_room =
224 new HostRoomWindow(this, game_list_model, announce_multiplayer_session, room_network);
225 }
226 BringWidgetToFront(host_room);
227}
228
229bool MultiplayerState::OnCloseRoom() {
230 if (!NetworkMessage::WarnCloseRoom())
231 return false;
232 if (auto room = room_network.GetRoom().lock()) {
233 // if you are in a room, leave it
234 if (auto member = room_network.GetRoomMember().lock()) {
235 member->Leave();
236 LOG_DEBUG(Frontend, "Left the room (as a client)");
237 }
238
239 // if you are hosting a room, also stop hosting
240 if (room->GetState() != Network::Room::State::Open) {
241 return true;
242 }
243 // Save ban list
244 UISettings::values.multiplayer_ban_list = std::move(room->GetBanList());
245
246 room->Destroy();
247 announce_multiplayer_session->Stop();
248 LOG_DEBUG(Frontend, "Closed the room (as a server)");
249 }
250 return true;
251}
252
253void MultiplayerState::ShowNotification() {
254 if (client_room && client_room->isAncestorOf(QApplication::focusWidget()))
255 return; // Do not show notification if the chat window currently has focus
256 show_notification = true;
257 QApplication::alert(nullptr);
258 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected_notification")).pixmap(16));
259 status_text->setText(tr("New Messages Received"));
260}
261
262void MultiplayerState::HideNotification() {
263 show_notification = false;
264 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16));
265 status_text->setText(tr("Connected"));
266}
267
268void MultiplayerState::OnOpenNetworkRoom() {
269 if (auto member = room_network.GetRoomMember().lock()) {
270 if (member->IsConnected()) {
271 if (client_room == nullptr) {
272 client_room = new ClientRoomWindow(this, room_network);
273 connect(client_room, &ClientRoomWindow::ShowNotification, this,
274 &MultiplayerState::ShowNotification);
275 }
276 BringWidgetToFront(client_room);
277 return;
278 }
279 }
280 // If the user is not a member of a room, show the lobby instead.
281 // This is currently only used on the clickable label in the status bar
282 OnViewLobby();
283}
284
285void MultiplayerState::OnDirectConnectToRoom() {
286 if (direct_connect == nullptr) {
287 direct_connect = new DirectConnectWindow(room_network, this);
288 }
289 BringWidgetToFront(direct_connect);
290}
291
292bool MultiplayerState::IsHostingPublicRoom() const {
293 return announce_multiplayer_session->IsRunning();
294}
295
296void MultiplayerState::UpdateCredentials() {
297 announce_multiplayer_session->UpdateCredentials();
298}
299
300void MultiplayerState::UpdateGameList(QStandardItemModel* game_list) {
301 game_list_model = game_list;
302 if (lobby) {
303 lobby->UpdateGameList(game_list);
304 }
305 if (host_room) {
306 host_room->UpdateGameList(game_list);
307 }
308}
diff --git a/src/yuzu/multiplayer/state.h b/src/yuzu/multiplayer/state.h
new file mode 100644
index 000000000..9c60712d5
--- /dev/null
+++ b/src/yuzu/multiplayer/state.h
@@ -0,0 +1,92 @@
1// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <QWidget>
7#include "core/announce_multiplayer_session.h"
8#include "network/network.h"
9
10class QStandardItemModel;
11class Lobby;
12class HostRoomWindow;
13class ClientRoomWindow;
14class DirectConnectWindow;
15class ClickableLabel;
16
17class MultiplayerState : public QWidget {
18 Q_OBJECT;
19
20public:
21 explicit MultiplayerState(QWidget* parent, QStandardItemModel* game_list, QAction* leave_room,
22 QAction* show_room, Network::RoomNetwork& room_network_);
23 ~MultiplayerState();
24
25 /**
26 * Close all open multiplayer related dialogs
27 */
28 void Close();
29
30 ClickableLabel* GetStatusText() const {
31 return status_text;
32 }
33
34 ClickableLabel* GetStatusIcon() const {
35 return status_icon;
36 }
37
38 void retranslateUi();
39
40 /**
41 * Whether a public room is being hosted or not.
42 * When this is true, Web Services configuration should be disabled.
43 */
44 bool IsHostingPublicRoom() const;
45
46 void UpdateCredentials();
47
48 /**
49 * Updates the multiplayer dialogs with a new game list model.
50 * This model should be the original model of the game list.
51 */
52 void UpdateGameList(QStandardItemModel* game_list);
53
54public slots:
55 void OnNetworkStateChanged(const Network::RoomMember::State& state);
56 void OnNetworkError(const Network::RoomMember::Error& error);
57 void OnViewLobby();
58 void OnCreateRoom();
59 bool OnCloseRoom();
60 void OnOpenNetworkRoom();
61 void OnDirectConnectToRoom();
62 void OnAnnounceFailed(const WebService::WebResult&);
63 void UpdateThemedIcons();
64 void ShowNotification();
65 void HideNotification();
66
67signals:
68 void NetworkStateChanged(const Network::RoomMember::State&);
69 void NetworkError(const Network::RoomMember::Error&);
70 void AnnounceFailed(const WebService::WebResult&);
71
72private:
73 Lobby* lobby = nullptr;
74 HostRoomWindow* host_room = nullptr;
75 ClientRoomWindow* client_room = nullptr;
76 DirectConnectWindow* direct_connect = nullptr;
77 ClickableLabel* status_icon = nullptr;
78 ClickableLabel* status_text = nullptr;
79 QStandardItemModel* game_list_model = nullptr;
80 QAction* leave_room;
81 QAction* show_room;
82 std::shared_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
83 Network::RoomMember::State current_state = Network::RoomMember::State::Uninitialized;
84 bool has_mod_perms = false;
85 Network::RoomMember::CallbackHandle<Network::RoomMember::State> state_callback_handle;
86 Network::RoomMember::CallbackHandle<Network::RoomMember::Error> error_callback_handle;
87
88 bool show_notification = false;
89 Network::RoomNetwork& room_network;
90};
91
92Q_DECLARE_METATYPE(WebService::WebResult);
diff --git a/src/yuzu/multiplayer/validation.h b/src/yuzu/multiplayer/validation.h
new file mode 100644
index 000000000..7d48e589d
--- /dev/null
+++ b/src/yuzu/multiplayer/validation.h
@@ -0,0 +1,48 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <QRegExp>
7#include <QString>
8#include <QValidator>
9
10class Validation {
11public:
12 Validation()
13 : room_name(room_name_regex), nickname(nickname_regex), ip(ip_regex), port(0, 65535) {}
14
15 ~Validation() = default;
16
17 const QValidator* GetRoomName() const {
18 return &room_name;
19 }
20 const QValidator* GetNickname() const {
21 return &nickname;
22 }
23 const QValidator* GetIP() const {
24 return &ip;
25 }
26 const QValidator* GetPort() const {
27 return &port;
28 }
29
30private:
31 /// room name can be alphanumeric and " " "_" "." and "-" and must have a size of 4-20
32 QRegExp room_name_regex = QRegExp(QStringLiteral("^[a-zA-Z0-9._- ]{4,20}$"));
33 QRegExpValidator room_name;
34
35 /// nickname can be alphanumeric and " " "_" "." and "-" and must have a size of 4-20
36 QRegExp nickname_regex = QRegExp(QStringLiteral("^[a-zA-Z0-9._- ]{4,20}$"));
37 QRegExpValidator nickname;
38
39 /// ipv4 address only
40 // TODO remove this when we support hostnames in direct connect
41 QRegExp ip_regex = QRegExp(QStringLiteral(
42 "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|"
43 "2[0-4][0-9]|25[0-5])"));
44 QRegExpValidator ip;
45
46 /// port must be between 0 and 65535
47 QIntValidator port;
48};
diff --git a/src/yuzu/startup_checks.cpp b/src/yuzu/startup_checks.cpp
new file mode 100644
index 000000000..8421280bf
--- /dev/null
+++ b/src/yuzu/startup_checks.cpp
@@ -0,0 +1,136 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "video_core/vulkan_common/vulkan_wrapper.h"
5
6#ifdef _WIN32
7#include <cstring> // for memset, strncpy
8#include <processthreadsapi.h>
9#include <windows.h>
10#elif defined(YUZU_UNIX)
11#include <errno.h>
12#include <sys/wait.h>
13#include <unistd.h>
14#endif
15
16#include <cstdio>
17#include "video_core/vulkan_common/vulkan_instance.h"
18#include "video_core/vulkan_common/vulkan_library.h"
19#include "yuzu/startup_checks.h"
20
21void CheckVulkan() {
22 // Just start the Vulkan loader, this will crash if something is wrong
23 try {
24 Vulkan::vk::InstanceDispatch dld;
25 const Common::DynamicLibrary library = Vulkan::OpenLibrary();
26 const Vulkan::vk::Instance instance =
27 Vulkan::CreateInstance(library, dld, VK_API_VERSION_1_0);
28
29 } catch (const Vulkan::vk::Exception& exception) {
30 std::fprintf(stderr, "Failed to initialize Vulkan: %s\n", exception.what());
31 }
32}
33
34bool StartupChecks(const char* arg0, bool* has_broken_vulkan) {
35#ifdef _WIN32
36 // Check environment variable to see if we are the child
37 char variable_contents[8];
38 const DWORD startup_check_var =
39 GetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, variable_contents, 8);
40 if (startup_check_var > 0 && std::strncmp(variable_contents, "ON", 8) == 0) {
41 CheckVulkan();
42 return true;
43 }
44
45 // Set the startup variable for child processes
46 const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, "ON");
47 if (!env_var_set) {
48 std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n",
49 STARTUP_CHECK_ENV_VAR, GetLastError());
50 return false;
51 }
52
53 PROCESS_INFORMATION process_info;
54 std::memset(&process_info, '\0', sizeof(process_info));
55
56 if (!SpawnChild(arg0, &process_info)) {
57 return false;
58 }
59
60 // Wait until the processs exits and get exit code from it
61 WaitForSingleObject(process_info.hProcess, INFINITE);
62 DWORD exit_code = STILL_ACTIVE;
63 const int err = GetExitCodeProcess(process_info.hProcess, &exit_code);
64 if (err == 0) {
65 std::fprintf(stderr, "GetExitCodeProcess failed with error %d\n", GetLastError());
66 }
67
68 // Vulkan is broken if the child crashed (return value is not zero)
69 *has_broken_vulkan = (exit_code != 0);
70
71 if (CloseHandle(process_info.hProcess) == 0) {
72 std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError());
73 }
74 if (CloseHandle(process_info.hThread) == 0) {
75 std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError());
76 }
77
78 if (!SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, nullptr)) {
79 std::fprintf(stderr, "SetEnvironmentVariableA failed to clear %s with error %d\n",
80 STARTUP_CHECK_ENV_VAR, GetLastError());
81 }
82
83#elif defined(YUZU_UNIX)
84 const pid_t pid = fork();
85 if (pid == 0) {
86 CheckVulkan();
87 return true;
88 } else if (pid == -1) {
89 const int err = errno;
90 std::fprintf(stderr, "fork failed with error %d\n", err);
91 return false;
92 }
93
94 // Get exit code from child process
95 int status;
96 const int r_val = wait(&status);
97 if (r_val == -1) {
98 const int err = errno;
99 std::fprintf(stderr, "wait failed with error %d\n", err);
100 return false;
101 }
102 // Vulkan is broken if the child crashed (return value is not zero)
103 *has_broken_vulkan = (status != 0);
104#endif
105 return false;
106}
107
108#ifdef _WIN32
109bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi) {
110 STARTUPINFOA startup_info;
111
112 std::memset(&startup_info, '\0', sizeof(startup_info));
113 startup_info.cb = sizeof(startup_info);
114
115 char p_name[255];
116 std::strncpy(p_name, arg0, 255);
117
118 const bool process_created = CreateProcessA(nullptr, // lpApplicationName
119 p_name, // lpCommandLine
120 nullptr, // lpProcessAttributes
121 nullptr, // lpThreadAttributes
122 false, // bInheritHandles
123 0, // dwCreationFlags
124 nullptr, // lpEnvironment
125 nullptr, // lpCurrentDirectory
126 &startup_info, // lpStartupInfo
127 pi // lpProcessInformation
128 );
129 if (!process_created) {
130 std::fprintf(stderr, "CreateProcessA failed with error %d\n", GetLastError());
131 return false;
132 }
133
134 return true;
135}
136#endif
diff --git a/src/yuzu/startup_checks.h b/src/yuzu/startup_checks.h
new file mode 100644
index 000000000..096dd54a8
--- /dev/null
+++ b/src/yuzu/startup_checks.h
@@ -0,0 +1,17 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#ifdef _WIN32
7#include <windows.h>
8#endif
9
10constexpr char STARTUP_CHECK_ENV_VAR[] = "YUZU_DO_STARTUP_CHECKS";
11
12void CheckVulkan();
13bool StartupChecks(const char* arg0, bool* has_broken_vulkan);
14
15#ifdef _WIN32
16bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi);
17#endif
diff --git a/src/yuzu/uisettings.cpp b/src/yuzu/uisettings.cpp
index f683b80f7..2c1b547fb 100644
--- a/src/yuzu/uisettings.cpp
+++ b/src/yuzu/uisettings.cpp
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include "yuzu/uisettings.h" 4#include "yuzu/uisettings.h"
6 5
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 044d88ca6..25d1bf1e6 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
@@ -78,7 +77,7 @@ struct Values {
78 Settings::Setting<bool> mute_when_in_background{false, "muteWhenInBackground"}; 77 Settings::Setting<bool> mute_when_in_background{false, "muteWhenInBackground"};
79 Settings::Setting<bool> hide_mouse{true, "hideInactiveMouse"}; 78 Settings::Setting<bool> hide_mouse{true, "hideInactiveMouse"};
80 // Set when Vulkan is known to crash the application 79 // Set when Vulkan is known to crash the application
81 Settings::Setting<bool> has_broken_vulkan{false, "has_broken_vulkan"}; 80 bool has_broken_vulkan = false;
82 81
83 Settings::Setting<bool> select_user_on_boot{false, "select_user_on_boot"}; 82 Settings::Setting<bool> select_user_on_boot{false, "select_user_on_boot"};
84 83
@@ -102,6 +101,19 @@ struct Values {
102 101
103 Settings::Setting<uint32_t> callout_flags{0, "calloutFlags"}; 102 Settings::Setting<uint32_t> callout_flags{0, "calloutFlags"};
104 103
104 // multiplayer settings
105 Settings::Setting<QString> multiplayer_nickname{QStringLiteral("yuzu"), "nickname"};
106 Settings::Setting<QString> multiplayer_ip{{}, "ip"};
107 Settings::SwitchableSetting<uint, true> multiplayer_port{24872, 0, 65535, "port"};
108 Settings::Setting<QString> multiplayer_room_nickname{{}, "room_nickname"};
109 Settings::Setting<QString> multiplayer_room_name{{}, "room_name"};
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"};
112 Settings::SwitchableSetting<uint, true> multiplayer_host_type{0, 0, 1, "host_type"};
113 Settings::Setting<qulonglong> multiplayer_game_id{{}, "game_id"};
114 Settings::Setting<QString> multiplayer_room_description{{}, "room_description"};
115 std::pair<std::vector<std::string>, std::vector<std::string>> multiplayer_ban_list;
116
105 // logging 117 // logging
106 Settings::Setting<bool> show_console{false, "showConsole"}; 118 Settings::Setting<bool> show_console{false, "showConsole"};
107 119
diff --git a/src/yuzu/util/clickable_label.cpp b/src/yuzu/util/clickable_label.cpp
new file mode 100644
index 000000000..89d14190a
--- /dev/null
+++ b/src/yuzu/util/clickable_label.cpp
@@ -0,0 +1,11 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "yuzu/util/clickable_label.h"
5
6ClickableLabel::ClickableLabel(QWidget* parent, [[maybe_unused]] Qt::WindowFlags f)
7 : QLabel(parent) {}
8
9void ClickableLabel::mouseReleaseEvent([[maybe_unused]] QMouseEvent* event) {
10 emit clicked();
11}
diff --git a/src/yuzu/util/clickable_label.h b/src/yuzu/util/clickable_label.h
new file mode 100644
index 000000000..4fe744150
--- /dev/null
+++ b/src/yuzu/util/clickable_label.h
@@ -0,0 +1,21 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <QLabel>
7#include <QWidget>
8
9class ClickableLabel : public QLabel {
10 Q_OBJECT
11
12public:
13 explicit ClickableLabel(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
14 ~ClickableLabel() = default;
15
16signals:
17 void clicked();
18
19protected:
20 void mouseReleaseEvent(QMouseEvent* event);
21};
diff --git a/src/yuzu/util/sequence_dialog/sequence_dialog.cpp b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp
index bb5f74ec4..4b10fa517 100644
--- a/src/yuzu/util/sequence_dialog/sequence_dialog.cpp
+++ b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp
@@ -1,6 +1,5 @@
1// Copyright 2018 Citra Emulator Project 1// SPDX-FileCopyrightText: 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <QDialogButtonBox> 4#include <QDialogButtonBox>
6#include <QKeySequenceEdit> 5#include <QKeySequenceEdit>
diff --git a/src/yuzu/util/sequence_dialog/sequence_dialog.h b/src/yuzu/util/sequence_dialog/sequence_dialog.h
index 969c77740..85e146d40 100644
--- a/src/yuzu/util/sequence_dialog/sequence_dialog.h
+++ b/src/yuzu/util/sequence_dialog/sequence_dialog.h
@@ -1,6 +1,5 @@
1// Copyright 2018 Citra Emulator Project 1// SPDX-FileCopyrightText: 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp
index ef31bc2d2..5c3e4589e 100644
--- a/src/yuzu/util/util.cpp
+++ b/src/yuzu/util/util.cpp
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <array> 4#include <array>
6#include <cmath> 5#include <cmath>
diff --git a/src/yuzu/util/util.h b/src/yuzu/util/util.h
index e6790f260..39dd2d895 100644
--- a/src/yuzu/util/util.h
+++ b/src/yuzu/util/util.h
@@ -1,6 +1,5 @@
1// Copyright 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu/yuzu.qrc b/src/yuzu/yuzu.qrc
index 5733cac98..855df05fd 100644
--- a/src/yuzu/yuzu.qrc
+++ b/src/yuzu/yuzu.qrc
@@ -1,3 +1,8 @@
1<!--
2SPDX-FileCopyrightText: 2021 yuzu Emulator Project
3SPDX-License-Identifier: GPL-2.0-or-later
4-->
5
1<RCC> 6<RCC>
2 <qresource prefix="/img"> 7 <qresource prefix="/img">
3 <file alias="yuzu.ico">../../dist/yuzu.ico</file> 8 <file alias="yuzu.ico">../../dist/yuzu.ico</file>
diff --git a/src/yuzu/yuzu.rc b/src/yuzu/yuzu.rc
index 4a3645a71..1fc74d065 100644
--- a/src/yuzu/yuzu.rc
+++ b/src/yuzu/yuzu.rc
@@ -1,3 +1,6 @@
1// SPDX-FileCopyrightText: 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
1#include "winresrc.h" 4#include "winresrc.h"
2///////////////////////////////////////////////////////////////////////////// 5/////////////////////////////////////////////////////////////////////////////
3// 6//
diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt
index c8901f2df..7d8ca3d8a 100644
--- a/src/yuzu_cmd/CMakeLists.txt
+++ b/src/yuzu_cmd/CMakeLists.txt
@@ -1,3 +1,6 @@
1# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-2.0-or-later
3
1set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) 4set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
2 5
3# Credits to Samantas5855 and others for this function. 6# Credits to Samantas5855 and others for this function.
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index ad7f9d239..bd0fb75f8 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <memory> 4#include <memory>
6#include <optional> 5#include <optional>
diff --git a/src/yuzu_cmd/config.h b/src/yuzu_cmd/config.h
index 32c03075f..021438b17 100644
--- a/src/yuzu_cmd/config.h
+++ b/src/yuzu_cmd/config.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index d9a2a460c..1168cf136 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -1,6 +1,5 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index 8e38724db..4ac72c2f6 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <SDL.h> 4#include <SDL.h>
6 5
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
index 58b885465..90bb0b415 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
@@ -1,6 +1,5 @@
1// Copyright 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#pragma once 4#pragma once
6 5
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index cb301e78b..003890c07 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -1,10 +1,10 @@
1// Copyright 2014 Citra Emulator Project 1// SPDX-FileCopyrightText: 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// SPDX-License-Identifier: GPL-2.0-or-later
3// Refer to the license.txt file included.
4 3
5#include <chrono> 4#include <chrono>
6#include <iostream> 5#include <iostream>
7#include <memory> 6#include <memory>
7#include <regex>
8#include <string> 8#include <string>
9#include <thread> 9#include <thread>
10 10
@@ -29,6 +29,7 @@
29#include "core/loader/loader.h" 29#include "core/loader/loader.h"
30#include "core/telemetry_session.h" 30#include "core/telemetry_session.h"
31#include "input_common/main.h" 31#include "input_common/main.h"
32#include "network/network.h"
32#include "video_core/renderer_base.h" 33#include "video_core/renderer_base.h"
33#include "yuzu_cmd/config.h" 34#include "yuzu_cmd/config.h"
34#include "yuzu_cmd/emu_window/emu_window_sdl2.h" 35#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
@@ -60,6 +61,8 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
60static void PrintHelp(const char* argv0) { 61static void PrintHelp(const char* argv0) {
61 std::cout << "Usage: " << argv0 62 std::cout << "Usage: " << argv0
62 << " [options] <filename>\n" 63 << " [options] <filename>\n"
64 "-m, --multiplayer=nick:password@address:port"
65 " Nickname, password, address and port for multiplayer\n"
63 "-f, --fullscreen Start in fullscreen mode\n" 66 "-f, --fullscreen Start in fullscreen mode\n"
64 "-h, --help Display this help and exit\n" 67 "-h, --help Display this help and exit\n"
65 "-v, --version Output version information and exit\n" 68 "-v, --version Output version information and exit\n"
@@ -71,6 +74,107 @@ static void PrintVersion() {
71 std::cout << "yuzu " << Common::g_scm_branch << " " << Common::g_scm_desc << std::endl; 74 std::cout << "yuzu " << Common::g_scm_branch << " " << Common::g_scm_desc << std::endl;
72} 75}
73 76
77static void OnStateChanged(const Network::RoomMember::State& state) {
78 switch (state) {
79 case Network::RoomMember::State::Idle:
80 LOG_DEBUG(Network, "Network is idle");
81 break;
82 case Network::RoomMember::State::Joining:
83 LOG_DEBUG(Network, "Connection sequence to room started");
84 break;
85 case Network::RoomMember::State::Joined:
86 LOG_DEBUG(Network, "Successfully joined to the room");
87 break;
88 case Network::RoomMember::State::Moderator:
89 LOG_DEBUG(Network, "Successfully joined the room as a moderator");
90 break;
91 default:
92 break;
93 }
94}
95
96static void OnNetworkError(const Network::RoomMember::Error& error) {
97 switch (error) {
98 case Network::RoomMember::Error::LostConnection:
99 LOG_DEBUG(Network, "Lost connection to the room");
100 break;
101 case Network::RoomMember::Error::CouldNotConnect:
102 LOG_ERROR(Network, "Error: Could not connect");
103 exit(1);
104 break;
105 case Network::RoomMember::Error::NameCollision:
106 LOG_ERROR(
107 Network,
108 "You tried to use the same nickname as another user that is connected to the Room");
109 exit(1);
110 break;
111 case Network::RoomMember::Error::MacCollision:
112 LOG_ERROR(Network, "You tried to use the same MAC-Address as another user that is "
113 "connected to the Room");
114 exit(1);
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:
121 LOG_ERROR(Network, "Room replied with: Wrong password");
122 exit(1);
123 break;
124 case Network::RoomMember::Error::WrongVersion:
125 LOG_ERROR(Network,
126 "You are using a different version than the room you are trying to connect to");
127 exit(1);
128 break;
129 case Network::RoomMember::Error::RoomIsFull:
130 LOG_ERROR(Network, "The room is full");
131 exit(1);
132 break;
133 case Network::RoomMember::Error::HostKicked:
134 LOG_ERROR(Network, "You have been kicked by the host");
135 break;
136 case Network::RoomMember::Error::HostBanned:
137 LOG_ERROR(Network, "You have been banned by the host");
138 break;
139 case Network::RoomMember::Error::UnknownError:
140 LOG_ERROR(Network, "UnknownError");
141 break;
142 case Network::RoomMember::Error::PermissionDenied:
143 LOG_ERROR(Network, "PermissionDenied");
144 break;
145 case Network::RoomMember::Error::NoSuchUser:
146 LOG_ERROR(Network, "NoSuchUser");
147 break;
148 }
149}
150
151static void OnMessageReceived(const Network::ChatEntry& msg) {
152 std::cout << std::endl << msg.nickname << ": " << msg.message << std::endl << std::endl;
153}
154
155static void OnStatusMessageReceived(const Network::StatusMessageEntry& msg) {
156 std::string message;
157 switch (msg.type) {
158 case Network::IdMemberJoin:
159 message = fmt::format("{} has joined", msg.nickname);
160 break;
161 case Network::IdMemberLeave:
162 message = fmt::format("{} has left", msg.nickname);
163 break;
164 case Network::IdMemberKicked:
165 message = fmt::format("{} has been kicked", msg.nickname);
166 break;
167 case Network::IdMemberBanned:
168 message = fmt::format("{} has been banned", msg.nickname);
169 break;
170 case Network::IdAddressUnbanned:
171 message = fmt::format("{} has been unbanned", msg.nickname);
172 break;
173 }
174 if (!message.empty())
175 std::cout << std::endl << "* " << message << std::endl << std::endl;
176}
177
74/// Application entry point 178/// Application entry point
75int main(int argc, char** argv) { 179int main(int argc, char** argv) {
76 Common::Log::Initialize(); 180 Common::Log::Initialize();
@@ -92,10 +196,16 @@ int main(int argc, char** argv) {
92 std::optional<std::string> config_path; 196 std::optional<std::string> config_path;
93 std::string program_args; 197 std::string program_args;
94 198
199 bool use_multiplayer = false;
95 bool fullscreen = false; 200 bool fullscreen = false;
201 std::string nickname{};
202 std::string password{};
203 std::string address{};
204 u16 port = Network::DefaultRoomPort;
96 205
97 static struct option long_options[] = { 206 static struct option long_options[] = {
98 // clang-format off 207 // clang-format off
208 {"multiplayer", required_argument, 0, 'm'},
99 {"fullscreen", no_argument, 0, 'f'}, 209 {"fullscreen", no_argument, 0, 'f'},
100 {"help", no_argument, 0, 'h'}, 210 {"help", no_argument, 0, 'h'},
101 {"version", no_argument, 0, 'v'}, 211 {"version", no_argument, 0, 'v'},
@@ -109,6 +219,38 @@ int main(int argc, char** argv) {
109 int arg = getopt_long(argc, argv, "g:fhvp::c:", long_options, &option_index); 219 int arg = getopt_long(argc, argv, "g:fhvp::c:", long_options, &option_index);
110 if (arg != -1) { 220 if (arg != -1) {
111 switch (static_cast<char>(arg)) { 221 switch (static_cast<char>(arg)) {
222 case 'm': {
223 use_multiplayer = true;
224 const std::string str_arg(optarg);
225 // regex to check if the format is nickname:password@ip:port
226 // with optional :password
227 const std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$");
228 if (!std::regex_match(str_arg, re)) {
229 std::cout << "Wrong format for option --multiplayer\n";
230 PrintHelp(argv[0]);
231 return 0;
232 }
233
234 std::smatch match;
235 std::regex_search(str_arg, match, re);
236 ASSERT(match.size() == 5);
237 nickname = match[1];
238 password = match[2];
239 address = match[3];
240 if (!match[4].str().empty())
241 port = std::stoi(match[4]);
242 std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
243 if (!std::regex_match(nickname, nickname_re)) {
244 std::cout
245 << "Nickname is not valid. Must be 4 to 20 alphanumeric characters.\n";
246 return 0;
247 }
248 if (address.empty()) {
249 std::cout << "Address to room must not be empty.\n";
250 return 0;
251 }
252 break;
253 }
112 case 'f': 254 case 'f':
113 fullscreen = true; 255 fullscreen = true;
114 LOG_INFO(Frontend, "Starting in fullscreen mode..."); 256 LOG_INFO(Frontend, "Starting in fullscreen mode...");
@@ -215,6 +357,21 @@ int main(int argc, char** argv) {
215 357
216 system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "SDL"); 358 system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "SDL");
217 359
360 if (use_multiplayer) {
361 if (auto member = system.GetRoomNetwork().GetRoomMember().lock()) {
362 member->BindOnChatMessageRecieved(OnMessageReceived);
363 member->BindOnStatusMessageReceived(OnStatusMessageReceived);
364 member->BindOnStateChanged(OnStateChanged);
365 member->BindOnError(OnNetworkError);
366 LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port,
367 nickname);
368 member->Join(nickname, "", address.c_str(), port, 0, Network::NoPreferredMac, password);
369 } else {
370 LOG_ERROR(Network, "Could not access RoomMember");
371 return 0;
372 }
373 }
374
218 // Core is loaded, start the GPU (makes the GPU contexts current to this thread) 375 // Core is loaded, start the GPU (makes the GPU contexts current to this thread)
219 system.GPU().Start(); 376 system.GPU().Start();
220 system.GetCpuManager().OnGpuReady(); 377 system.GetCpuManager().OnGpuReady();
diff --git a/src/yuzu_cmd/yuzu.rc b/src/yuzu_cmd/yuzu.rc
index 0cde75e2f..e230cf680 100644
--- a/src/yuzu_cmd/yuzu.rc
+++ b/src/yuzu_cmd/yuzu.rc
@@ -1,3 +1,6 @@
1// SPDX-FileCopyrightText: 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
1#include "winresrc.h" 4#include "winresrc.h"
2///////////////////////////////////////////////////////////////////////////// 5/////////////////////////////////////////////////////////////////////////////
3// 6//