summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/audio_core/audio_core.cpp2
-rw-r--r--src/audio_core/audio_manager.cpp17
-rw-r--r--src/audio_core/audio_manager.h19
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.cpp2
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.h2
-rw-r--r--src/audio_core/sink/sink_stream.cpp9
-rw-r--r--src/common/CMakeLists.txt29
-rw-r--r--src/common/settings.h1
-rw-r--r--src/core/CMakeLists.txt4
-rw-r--r--src/core/hid/emulated_controller.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp6
-rw-r--r--src/core/hle/service/hid/controllers/palma.cpp229
-rw-r--r--src/core/hle/service/hid/controllers/palma.h163
-rw-r--r--src/core/hle/service/hid/errors.h2
-rw-r--r--src/core/hle/service/hid/hid.cpp447
-rw-r--r--src/core/hle/service/hid/hid.h29
-rw-r--r--src/core/hle/service/hid/irs.cpp3
-rw-r--r--src/core/hle/service/ldn/lan_discovery.cpp633
-rw-r--r--src/core/hle/service/ldn/lan_discovery.h134
-rw-r--r--src/core/hle/service/ldn/ldn.cpp227
-rw-r--r--src/core/hle/service/ldn/ldn_types.h48
-rw-r--r--src/core/hle/service/nfp/nfp.cpp4
-rw-r--r--src/core/hle/service/sockets/bsd.cpp2
-rw-r--r--src/core/internal_network/network.cpp12
-rw-r--r--src/core/internal_network/network_interface.cpp12
-rw-r--r--src/core/internal_network/network_interface.h1
-rw-r--r--src/core/internal_network/socket_proxy.cpp8
-rw-r--r--src/core/internal_network/sockets.h11
-rw-r--r--src/core/loader/loader.cpp4
-rw-r--r--src/dedicated_room/yuzu_room.cpp13
-rw-r--r--src/network/network.cpp2
-rw-r--r--src/network/room.cpp63
-rw-r--r--src/network/room.h1
-rw-r--r--src/network/room_member.cpp57
-rw-r--r--src/network/room_member.h35
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm.cpp2
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl.cpp2
-rw-r--r--src/video_core/host_shaders/astc_decoder.comp2
-rw-r--r--src/video_core/macro/macro_hle.cpp63
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp2
-rw-r--r--src/video_core/textures/astc.cpp2
-rw-r--r--src/yuzu/applets/qt_controller.cpp2
-rw-r--r--src/yuzu/configuration/config.cpp2
-rw-r--r--src/yuzu/configuration/configure_debug.cpp2
-rw-r--r--src/yuzu/configuration/configure_debug.ui10
-rw-r--r--src/yuzu/configuration/configure_input.cpp7
-rw-r--r--src/yuzu/configuration/input_profiles.cpp2
-rw-r--r--src/yuzu/main.cpp19
-rw-r--r--src/yuzu/main.h1
-rw-r--r--src/yuzu/main.ui24
-rw-r--r--src/yuzu/multiplayer/chat_room.cpp12
-rw-r--r--src/yuzu/multiplayer/client_room.cpp3
-rw-r--r--src/yuzu/multiplayer/direct_connect.cpp2
-rw-r--r--src/yuzu/multiplayer/direct_connect.h1
-rw-r--r--src/yuzu/multiplayer/host_room.cpp1
-rw-r--r--src/yuzu/multiplayer/host_room.h3
-rw-r--r--src/yuzu/multiplayer/lobby.cpp67
-rw-r--r--src/yuzu/multiplayer/lobby.h8
-rw-r--r--src/yuzu/multiplayer/lobby_p.h18
-rw-r--r--src/yuzu/multiplayer/message.cpp6
-rw-r--r--src/yuzu/multiplayer/state.cpp80
-rw-r--r--src/yuzu/multiplayer/state.h14
-rw-r--r--src/yuzu/startup_checks.cpp91
-rw-r--r--src/yuzu/startup_checks.h2
-rw-r--r--src/yuzu/uisettings.h2
65 files changed, 2341 insertions, 344 deletions
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp
index c845330cd..07a679c32 100644
--- a/src/audio_core/audio_core.cpp
+++ b/src/audio_core/audio_core.cpp
@@ -8,7 +8,7 @@
namespace AudioCore {
-AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>(system)} {
+AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>()} {
CreateSinks();
// Must be created after the sinks
adsp = std::make_unique<AudioRenderer::ADSP::ADSP>(system, *output_sink);
diff --git a/src/audio_core/audio_manager.cpp b/src/audio_core/audio_manager.cpp
index 2f1bba9c3..2acde668e 100644
--- a/src/audio_core/audio_manager.cpp
+++ b/src/audio_core/audio_manager.cpp
@@ -1,14 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "audio_core/audio_in_manager.h"
#include "audio_core/audio_manager.h"
-#include "audio_core/audio_out_manager.h"
#include "core/core.h"
+#include "core/hle/service/audio/errors.h"
namespace AudioCore {
-AudioManager::AudioManager(Core::System& system_) : system{system_} {
+AudioManager::AudioManager() {
thread = std::jthread([this]() { ThreadFunc(); });
}
@@ -27,7 +26,7 @@ Result AudioManager::SetOutManager(BufferEventFunc buffer_func) {
const auto index{events.GetManagerIndex(Event::Type::AudioOutManager)};
if (buffer_events[index] == nullptr) {
- buffer_events[index] = buffer_func;
+ buffer_events[index] = std::move(buffer_func);
needs_update = true;
events.SetAudioEvent(Event::Type::AudioOutManager, true);
}
@@ -43,7 +42,7 @@ Result AudioManager::SetInManager(BufferEventFunc buffer_func) {
const auto index{events.GetManagerIndex(Event::Type::AudioInManager)};
if (buffer_events[index] == nullptr) {
- buffer_events[index] = buffer_func;
+ buffer_events[index] = std::move(buffer_func);
needs_update = true;
events.SetAudioEvent(Event::Type::AudioInManager, true);
}
@@ -60,19 +59,21 @@ void AudioManager::ThreadFunc() {
running = true;
while (running) {
- auto timed_out{events.Wait(l, std::chrono::seconds(2))};
+ const auto timed_out{events.Wait(l, std::chrono::seconds(2))};
if (events.CheckAudioEventSet(Event::Type::Max)) {
break;
}
for (size_t i = 0; i < buffer_events.size(); i++) {
- if (events.CheckAudioEventSet(Event::Type(i)) || timed_out) {
+ const auto event_type = static_cast<Event::Type>(i);
+
+ if (events.CheckAudioEventSet(event_type) || timed_out) {
if (buffer_events[i]) {
buffer_events[i]();
}
}
- events.SetAudioEvent(Event::Type(i), false);
+ events.SetAudioEvent(event_type, false);
}
}
}
diff --git a/src/audio_core/audio_manager.h b/src/audio_core/audio_manager.h
index 8cbd95e22..abf077de4 100644
--- a/src/audio_core/audio_manager.h
+++ b/src/audio_core/audio_manager.h
@@ -10,22 +10,11 @@
#include <thread>
#include "audio_core/audio_event.h"
-#include "core/hle/service/audio/errors.h"
-namespace Core {
-class System;
-}
+union Result;
namespace AudioCore {
-namespace AudioOut {
-class Manager;
-}
-
-namespace AudioIn {
-class Manager;
-}
-
/**
* The AudioManager's main purpose is to wait for buffer events for the audio in and out managers,
* and call an associated callback to release buffers.
@@ -43,7 +32,7 @@ class AudioManager {
using BufferEventFunc = std::function<void()>;
public:
- explicit AudioManager(Core::System& system);
+ explicit AudioManager();
/**
* Shutdown the audio manager.
@@ -80,10 +69,6 @@ private:
*/
void ThreadFunc();
- /// Core system
- Core::System& system;
- /// Have sessions started palying?
- bool sessions_started{};
/// Is the main thread running?
std::atomic<bool> running{};
/// Unused
diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp
index bafe4822a..ab2257bd8 100644
--- a/src/audio_core/renderer/adsp/audio_renderer.cpp
+++ b/src/audio_core/renderer/adsp/audio_renderer.cpp
@@ -47,7 +47,7 @@ RenderMessage AudioRenderer_Mailbox::ADSPWaitMessage() {
return msg;
}
-CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const s32 session_id) {
+CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const u32 session_id) {
return command_buffers[session_id];
}
diff --git a/src/audio_core/renderer/adsp/audio_renderer.h b/src/audio_core/renderer/adsp/audio_renderer.h
index 02e923c84..151f38c1b 100644
--- a/src/audio_core/renderer/adsp/audio_renderer.h
+++ b/src/audio_core/renderer/adsp/audio_renderer.h
@@ -83,7 +83,7 @@ public:
* @param session_id - The session id to get (0 or 1).
* @return The command buffer.
*/
- CommandBuffer& GetCommandBuffer(s32 session_id);
+ CommandBuffer& GetCommandBuffer(u32 session_id);
/**
* Set the command buffer with the given session id (0 or 1).
diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp
index 37fe725e4..849f862b0 100644
--- a/src/audio_core/sink/sink_stream.cpp
+++ b/src/audio_core/sink/sink_stream.cpp
@@ -214,8 +214,13 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
// video play out without attempting to stall.
// Can hopefully remove this later with a more complete NVDEC implementation.
const auto nvdec_active{system.AudioCore().IsNVDECActive()};
- if (!nvdec_active && queued_buffers > max_queue_size) {
+
+ // Core timing cannot be paused in single-core mode, so Stall ends up being called over and over
+ // and never recovers to a normal state, so just skip attempting to sync things on single-core.
+ if (system.IsMulticore() && !nvdec_active && queued_buffers > max_queue_size) {
Stall();
+ } else if (system.IsMulticore() && queued_buffers <= max_queue_size) {
+ Unstall();
}
while (frames_written < num_frames) {
@@ -255,7 +260,7 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
std::memcpy(&last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
frame_size_bytes);
- if (stalled && queued_buffers <= max_queue_size) {
+ if (system.IsMulticore() && queued_buffers <= max_queue_size) {
Unstall();
}
}
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 68436a4bc..3447fabd8 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -14,32 +14,7 @@ if (DEFINED ENV{DISPLAYVERSION})
set(DISPLAY_VERSION $ENV{DISPLAYVERSION})
endif ()
-# Pass the path to git to the GenerateSCMRev.cmake as well
-find_package(Git QUIET)
-
-add_custom_command(OUTPUT scm_rev.cpp
- COMMAND ${CMAKE_COMMAND}
- -DSRC_DIR=${PROJECT_SOURCE_DIR}
- -DBUILD_REPOSITORY=${BUILD_REPOSITORY}
- -DTITLE_BAR_FORMAT_IDLE=${TITLE_BAR_FORMAT_IDLE}
- -DTITLE_BAR_FORMAT_RUNNING=${TITLE_BAR_FORMAT_RUNNING}
- -DBUILD_TAG=${BUILD_TAG}
- -DBUILD_ID=${DISPLAY_VERSION}
- -DGIT_REF_SPEC=${GIT_REF_SPEC}
- -DGIT_REV=${GIT_REV}
- -DGIT_DESC=${GIT_DESC}
- -DGIT_BRANCH=${GIT_BRANCH}
- -DBUILD_FULLNAME=${BUILD_FULLNAME}
- -DGIT_EXECUTABLE=${GIT_EXECUTABLE}
- -P ${PROJECT_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake
- DEPENDS
- # Check that the scm_rev files haven't changed
- "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in"
- "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.h"
- # technically we should regenerate if the git version changed, but its not worth the effort imo
- "${PROJECT_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake"
- VERBATIM
-)
+include(GenerateSCMRev)
add_library(common STATIC
algorithm.h
@@ -117,7 +92,7 @@ add_library(common STATIC
quaternion.h
reader_writer_queue.h
ring_buffer.h
- scm_rev.cpp
+ ${CMAKE_CURRENT_BINARY_DIR}/scm_rev.cpp
scm_rev.h
scope_exit.h
settings.cpp
diff --git a/src/common/settings.h b/src/common/settings.h
index 851812f28..d2452c93b 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -531,6 +531,7 @@ struct Values {
Setting<bool> use_auto_stub{false, "use_auto_stub"};
Setting<bool> enable_all_controllers{false, "enable_all_controllers"};
Setting<bool> create_crash_dumps{false, "create_crash_dumps"};
+ Setting<bool> perform_vulkan_check{true, "perform_vulkan_check"};
// Miscellaneous
Setting<std::string> log_filter{"*:Info", "log_filter"};
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 33cf470d5..81391f513 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -460,6 +460,8 @@ add_library(core STATIC
hle/service/hid/controllers/mouse.h
hle/service/hid/controllers/npad.cpp
hle/service/hid/controllers/npad.h
+ hle/service/hid/controllers/palma.cpp
+ hle/service/hid/controllers/palma.h
hle/service/hid/controllers/stubbed.cpp
hle/service/hid/controllers/stubbed.h
hle/service/hid/controllers/touchscreen.cpp
@@ -494,6 +496,8 @@ add_library(core STATIC
hle/service/jit/jit.h
hle/service/lbl/lbl.cpp
hle/service/lbl/lbl.h
+ hle/service/ldn/lan_discovery.cpp
+ hle/service/ldn/lan_discovery.h
hle/service/ldn/ldn_results.h
hle/service/ldn/ldn.cpp
hle/service/ldn/ldn.h
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 01c43be93..2cff279b1 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -93,7 +93,7 @@ void EmulatedController::ReloadFromSettings() {
.body = GetNpadColor(player.body_color_left),
.button = GetNpadColor(player.button_color_left),
};
- controller.colors_state.left = {
+ controller.colors_state.right = {
.body = GetNpadColor(player.body_color_right),
.button = GetNpadColor(player.button_color_right),
};
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index cb29004e8..f8972ec7a 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -660,7 +660,6 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
ASSERT(false);
break;
case Core::HID::NpadStyleIndex::ProController:
- case Core::HID::NpadStyleIndex::Pokeball:
set_motion_state(sixaxis_fullkey_state, motion_state[0]);
break;
case Core::HID::NpadStyleIndex::Handheld:
@@ -676,6 +675,11 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
case Core::HID::NpadStyleIndex::JoyconRight:
set_motion_state(sixaxis_right_lifo_state, motion_state[1]);
break;
+ case Core::HID::NpadStyleIndex::Pokeball:
+ using namespace std::literals::chrono_literals;
+ set_motion_state(sixaxis_fullkey_state, motion_state[0]);
+ sixaxis_fullkey_state.delta_time = std::chrono::nanoseconds(15ms).count();
+ break;
default:
break;
}
diff --git a/src/core/hle/service/hid/controllers/palma.cpp b/src/core/hle/service/hid/controllers/palma.cpp
new file mode 100644
index 000000000..575d4e626
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/palma.cpp
@@ -0,0 +1,229 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/core_timing.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#include "core/hid/hid_types.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/service/hid/controllers/palma.h"
+#include "core/hle/service/kernel_helpers.h"
+
+namespace Service::HID {
+
+Controller_Palma::Controller_Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
+ KernelHelpers::ServiceContext& service_context_)
+ : ControllerBase{hid_core_}, service_context{service_context_} {
+ controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Other);
+ operation_complete_event = service_context.CreateEvent("hid:PalmaOperationCompleteEvent");
+}
+
+Controller_Palma::~Controller_Palma() = default;
+
+void Controller_Palma::OnInit() {}
+
+void Controller_Palma::OnRelease() {}
+
+void Controller_Palma::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
+ if (!IsControllerActivated()) {
+ return;
+ }
+}
+
+Result Controller_Palma::GetPalmaConnectionHandle(Core::HID::NpadIdType npad_id,
+ PalmaConnectionHandle& handle) {
+ active_handle.npad_id = npad_id;
+ handle = active_handle;
+ return ResultSuccess;
+}
+
+Result Controller_Palma::InitializePalma(const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ ActivateController();
+ return ResultSuccess;
+}
+
+Kernel::KReadableEvent& Controller_Palma::AcquirePalmaOperationCompleteEvent(
+ const PalmaConnectionHandle& handle) const {
+ if (handle.npad_id != active_handle.npad_id) {
+ LOG_ERROR(Service_HID, "Invalid npad id {}", handle.npad_id);
+ }
+ return operation_complete_event->GetReadableEvent();
+}
+
+Result Controller_Palma::GetPalmaOperationInfo(const PalmaConnectionHandle& handle,
+ PalmaOperationType& operation_type,
+ PalmaOperationData& data) const {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation_type = operation.operation;
+ data = operation.data;
+ return ResultSuccess;
+}
+
+Result Controller_Palma::PlayPalmaActivity(const PalmaConnectionHandle& handle,
+ u64 palma_activity) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::PlayActivity;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+Result Controller_Palma::SetPalmaFrModeType(const PalmaConnectionHandle& handle,
+ PalmaFrModeType fr_mode_) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ fr_mode = fr_mode_;
+ return ResultSuccess;
+}
+
+Result Controller_Palma::ReadPalmaStep(const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::ReadStep;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+Result Controller_Palma::EnablePalmaStep(const PalmaConnectionHandle& handle, bool is_enabled) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ return ResultSuccess;
+}
+
+Result Controller_Palma::ResetPalmaStep(const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ return ResultSuccess;
+}
+
+void Controller_Palma::ReadPalmaApplicationSection() {}
+
+void Controller_Palma::WritePalmaApplicationSection() {}
+
+Result Controller_Palma::ReadPalmaUniqueCode(const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::ReadUniqueCode;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+Result Controller_Palma::SetPalmaUniqueCodeInvalid(const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::SetUniqueCodeInvalid;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+void Controller_Palma::WritePalmaActivityEntry() {}
+
+Result Controller_Palma::WritePalmaRgbLedPatternEntry(const PalmaConnectionHandle& handle,
+ u64 unknown) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::WriteRgbLedPatternEntry;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+Result Controller_Palma::WritePalmaWaveEntry(const PalmaConnectionHandle& handle, PalmaWaveSet wave,
+ u8* t_mem, u64 size) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::WriteWaveEntry;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+Result Controller_Palma::SetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle,
+ s32 database_id_version_) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ database_id_version = database_id_version_;
+ operation.operation = PalmaOperationType::ReadDataBaseIdentificationVersion;
+ operation.result = PalmaResultSuccess;
+ operation.data[0] = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+Result Controller_Palma::GetPalmaDataBaseIdentificationVersion(
+ const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::ReadDataBaseIdentificationVersion;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation.data[0] = static_cast<u8>(database_id_version);
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+void Controller_Palma::SuspendPalmaFeature() {}
+
+Result Controller_Palma::GetPalmaOperationResult(const PalmaConnectionHandle& handle) const {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ return operation.result;
+}
+void Controller_Palma::ReadPalmaPlayLog() {}
+
+void Controller_Palma::ResetPalmaPlayLog() {}
+
+void Controller_Palma::SetIsPalmaAllConnectable(bool is_all_connectable) {
+ // If true controllers are able to be paired
+ is_connectable = is_all_connectable;
+}
+
+void Controller_Palma::SetIsPalmaPairedConnectable() {}
+
+Result Controller_Palma::PairPalma(const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ // TODO: Do something
+ return ResultSuccess;
+}
+
+void Controller_Palma::SetPalmaBoostMode(bool boost_mode) {}
+
+void Controller_Palma::CancelWritePalmaWaveEntry() {}
+
+void Controller_Palma::EnablePalmaBoostMode() {}
+
+void Controller_Palma::GetPalmaBluetoothAddress() {}
+
+void Controller_Palma::SetDisallowedPalmaConnection() {}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/palma.h b/src/core/hle/service/hid/controllers/palma.h
new file mode 100644
index 000000000..1d7fc94e1
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/palma.h
@@ -0,0 +1,163 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+#include "core/hle/service/hid/errors.h"
+
+namespace Kernel {
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
+
+namespace Service::KernelHelpers {
+class ServiceContext;
+}
+
+namespace Core::HID {
+class EmulatedController;
+} // namespace Core::HID
+
+namespace Service::HID {
+class Controller_Palma final : public ControllerBase {
+public:
+ using PalmaOperationData = std::array<u8, 0x140>;
+
+ // This is nn::hid::PalmaOperationType
+ enum class PalmaOperationType {
+ PlayActivity,
+ SetFrModeType,
+ ReadStep,
+ EnableStep,
+ ResetStep,
+ ReadApplicationSection,
+ WriteApplicationSection,
+ ReadUniqueCode,
+ SetUniqueCodeInvalid,
+ WriteActivityEntry,
+ WriteRgbLedPatternEntry,
+ WriteWaveEntry,
+ ReadDataBaseIdentificationVersion,
+ WriteDataBaseIdentificationVersion,
+ SuspendFeature,
+ ReadPlayLog,
+ ResetPlayLog,
+ };
+
+ // This is nn::hid::PalmaWaveSet
+ enum class PalmaWaveSet : u64 {
+ Small,
+ Medium,
+ Large,
+ };
+
+ // This is nn::hid::PalmaFrModeType
+ enum class PalmaFrModeType : u64 {
+ Off,
+ B01,
+ B02,
+ B03,
+ Downloaded,
+ };
+
+ // This is nn::hid::PalmaFeature
+ enum class PalmaFeature : u64 {
+ FrMode,
+ RumbleFeedback,
+ Step,
+ MuteSwitch,
+ };
+
+ // This is nn::hid::PalmaOperationInfo
+ struct PalmaOperationInfo {
+ PalmaOperationType operation{};
+ Result result{PalmaResultSuccess};
+ PalmaOperationData data{};
+ };
+ static_assert(sizeof(PalmaOperationInfo) == 0x148, "PalmaOperationInfo is an invalid size");
+
+ // This is nn::hid::PalmaActivityEntry
+ struct PalmaActivityEntry {
+ u32 rgb_led_pattern_index;
+ INSERT_PADDING_BYTES(2);
+ PalmaWaveSet wave_set;
+ u32 wave_index;
+ INSERT_PADDING_BYTES(12);
+ };
+ static_assert(sizeof(PalmaActivityEntry) == 0x20, "PalmaActivityEntry is an invalid size");
+
+ struct PalmaConnectionHandle {
+ Core::HID::NpadIdType npad_id;
+ INSERT_PADDING_BYTES(4); // Unknown
+ };
+ static_assert(sizeof(PalmaConnectionHandle) == 0x8,
+ "PalmaConnectionHandle has incorrect size.");
+
+ explicit Controller_Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
+ KernelHelpers::ServiceContext& service_context_);
+ ~Controller_Palma() override;
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
+
+ Result GetPalmaConnectionHandle(Core::HID::NpadIdType npad_id, PalmaConnectionHandle& handle);
+ Result InitializePalma(const PalmaConnectionHandle& handle);
+ Kernel::KReadableEvent& AcquirePalmaOperationCompleteEvent(
+ const PalmaConnectionHandle& handle) const;
+ Result GetPalmaOperationInfo(const PalmaConnectionHandle& handle,
+ PalmaOperationType& operation_type,
+ PalmaOperationData& data) const;
+ Result PlayPalmaActivity(const PalmaConnectionHandle& handle, u64 palma_activity);
+ Result SetPalmaFrModeType(const PalmaConnectionHandle& handle, PalmaFrModeType fr_mode_);
+ Result ReadPalmaStep(const PalmaConnectionHandle& handle);
+ Result EnablePalmaStep(const PalmaConnectionHandle& handle, bool is_enabled);
+ Result ResetPalmaStep(const PalmaConnectionHandle& handle);
+ Result ReadPalmaUniqueCode(const PalmaConnectionHandle& handle);
+ Result SetPalmaUniqueCodeInvalid(const PalmaConnectionHandle& handle);
+ Result WritePalmaRgbLedPatternEntry(const PalmaConnectionHandle& handle, u64 unknown);
+ Result WritePalmaWaveEntry(const PalmaConnectionHandle& handle, PalmaWaveSet wave, u8* t_mem,
+ u64 size);
+ Result SetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle,
+ s32 database_id_version_);
+ Result GetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle);
+ Result GetPalmaOperationResult(const PalmaConnectionHandle& handle) const;
+ void SetIsPalmaAllConnectable(bool is_all_connectable);
+ Result PairPalma(const PalmaConnectionHandle& handle);
+ void SetPalmaBoostMode(bool boost_mode);
+
+private:
+ void ReadPalmaApplicationSection();
+ void WritePalmaApplicationSection();
+ void WritePalmaActivityEntry();
+ void SuspendPalmaFeature();
+ void ReadPalmaPlayLog();
+ void ResetPalmaPlayLog();
+ void SetIsPalmaPairedConnectable();
+ void CancelWritePalmaWaveEntry();
+ void EnablePalmaBoostMode();
+ void GetPalmaBluetoothAddress();
+ void SetDisallowedPalmaConnection();
+
+ bool is_connectable{};
+ s32 database_id_version{};
+ PalmaOperationInfo operation{};
+ PalmaFrModeType fr_mode{};
+ PalmaConnectionHandle active_handle{};
+
+ Core::HID::EmulatedController* controller;
+
+ Kernel::KEvent* operation_complete_event;
+ KernelHelpers::ServiceContext& service_context;
+};
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/errors.h b/src/core/hle/service/hid/errors.h
index 4613a4e60..76208e9a4 100644
--- a/src/core/hle/service/hid/errors.h
+++ b/src/core/hle/service/hid/errors.h
@@ -7,6 +7,7 @@
namespace Service::HID {
+constexpr Result PalmaResultSuccess{ErrorModule::HID, 0};
constexpr Result NpadInvalidHandle{ErrorModule::HID, 100};
constexpr Result NpadDeviceIndexOutOfRange{ErrorModule::HID, 107};
constexpr Result VibrationInvalidStyleIndex{ErrorModule::HID, 122};
@@ -17,6 +18,7 @@ constexpr Result NpadIsDualJoycon{ErrorModule::HID, 601};
constexpr Result NpadIsSameType{ErrorModule::HID, 602};
constexpr Result InvalidNpadId{ErrorModule::HID, 709};
constexpr Result NpadNotConnected{ErrorModule::HID, 710};
+constexpr Result InvalidPalmaHandle{ErrorModule::HID, 3302};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 3d3457160..46bad7871 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -27,6 +27,7 @@
#include "core/hle/service/hid/controllers/keyboard.h"
#include "core/hle/service/hid/controllers/mouse.h"
#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/controllers/palma.h"
#include "core/hle/service/hid/controllers/stubbed.h"
#include "core/hle/service/hid/controllers/touchscreen.h"
#include "core/hle/service/hid/controllers/xpad.h"
@@ -35,7 +36,8 @@ namespace Service::HID {
// Updating period for each HID device.
// Period time is obtained by measuring the number of samples in a second on HW using a homebrew
-constexpr auto pad_update_ns = std::chrono::nanoseconds{4 * 1000 * 1000}; // (4ms, 250Hz)
+// Correct pad_update_ns is 4ms this is overclocked to lower input lag
+constexpr auto pad_update_ns = std::chrono::nanoseconds{1 * 1000 * 1000}; // (1ms, 1000Hz)
constexpr auto mouse_keyboard_update_ns = std::chrono::nanoseconds{8 * 1000 * 1000}; // (8ms, 125Hz)
constexpr auto motion_update_ns = std::chrono::nanoseconds{5 * 1000 * 1000}; // (5ms, 200Hz)
@@ -60,6 +62,7 @@ IAppletResource::IAppletResource(Core::System& system_,
MakeControllerWithServiceContext<Controller_NPad>(HidController::NPad, shared_memory);
MakeController<Controller_Gesture>(HidController::Gesture, shared_memory);
MakeController<Controller_ConsoleSixAxis>(HidController::ConsoleSixAxisSensor, shared_memory);
+ MakeControllerWithServiceContext<Controller_Palma>(HidController::Palma, shared_memory);
// Homebrew doesn't try to activate some controllers, so we activate them by default
GetController<Controller_NPad>(HidController::NPad).ActivateController();
@@ -310,36 +313,36 @@ Hid::Hid(Core::System& system_)
{406, nullptr, "GetNpadLeftRightInterfaceType"},
{407, nullptr, "GetNpadOfHighestBatteryLevel"},
{408, nullptr, "GetNpadOfHighestBatteryLevelForJoyRight"},
- {500, nullptr, "GetPalmaConnectionHandle"},
- {501, nullptr, "InitializePalma"},
- {502, nullptr, "AcquirePalmaOperationCompleteEvent"},
- {503, nullptr, "GetPalmaOperationInfo"},
- {504, nullptr, "PlayPalmaActivity"},
- {505, nullptr, "SetPalmaFrModeType"},
- {506, nullptr, "ReadPalmaStep"},
- {507, nullptr, "EnablePalmaStep"},
- {508, nullptr, "ResetPalmaStep"},
- {509, nullptr, "ReadPalmaApplicationSection"},
- {510, nullptr, "WritePalmaApplicationSection"},
- {511, nullptr, "ReadPalmaUniqueCode"},
- {512, nullptr, "SetPalmaUniqueCodeInvalid"},
- {513, nullptr, "WritePalmaActivityEntry"},
- {514, nullptr, "WritePalmaRgbLedPatternEntry"},
- {515, nullptr, "WritePalmaWaveEntry"},
- {516, nullptr, "SetPalmaDataBaseIdentificationVersion"},
- {517, nullptr, "GetPalmaDataBaseIdentificationVersion"},
- {518, nullptr, "SuspendPalmaFeature"},
- {519, nullptr, "GetPalmaOperationResult"},
- {520, nullptr, "ReadPalmaPlayLog"},
- {521, nullptr, "ResetPalmaPlayLog"},
+ {500, &Hid::GetPalmaConnectionHandle, "GetPalmaConnectionHandle"},
+ {501, &Hid::InitializePalma, "InitializePalma"},
+ {502, &Hid::AcquirePalmaOperationCompleteEvent, "AcquirePalmaOperationCompleteEvent"},
+ {503, &Hid::GetPalmaOperationInfo, "GetPalmaOperationInfo"},
+ {504, &Hid::PlayPalmaActivity, "PlayPalmaActivity"},
+ {505, &Hid::SetPalmaFrModeType, "SetPalmaFrModeType"},
+ {506, &Hid::ReadPalmaStep, "ReadPalmaStep"},
+ {507, &Hid::EnablePalmaStep, "EnablePalmaStep"},
+ {508, &Hid::ResetPalmaStep, "ResetPalmaStep"},
+ {509, &Hid::ReadPalmaApplicationSection, "ReadPalmaApplicationSection"},
+ {510, &Hid::WritePalmaApplicationSection, "WritePalmaApplicationSection"},
+ {511, &Hid::ReadPalmaUniqueCode, "ReadPalmaUniqueCode"},
+ {512, &Hid::SetPalmaUniqueCodeInvalid, "SetPalmaUniqueCodeInvalid"},
+ {513, &Hid::WritePalmaActivityEntry, "WritePalmaActivityEntry"},
+ {514, &Hid::WritePalmaRgbLedPatternEntry, "WritePalmaRgbLedPatternEntry"},
+ {515, &Hid::WritePalmaWaveEntry, "WritePalmaWaveEntry"},
+ {516, &Hid::SetPalmaDataBaseIdentificationVersion, "SetPalmaDataBaseIdentificationVersion"},
+ {517, &Hid::GetPalmaDataBaseIdentificationVersion, "GetPalmaDataBaseIdentificationVersion"},
+ {518, &Hid::SuspendPalmaFeature, "SuspendPalmaFeature"},
+ {519, &Hid::GetPalmaOperationResult, "GetPalmaOperationResult"},
+ {520, &Hid::ReadPalmaPlayLog, "ReadPalmaPlayLog"},
+ {521, &Hid::ResetPalmaPlayLog, "ResetPalmaPlayLog"},
{522, &Hid::SetIsPalmaAllConnectable, "SetIsPalmaAllConnectable"},
- {523, nullptr, "SetIsPalmaPairedConnectable"},
- {524, nullptr, "PairPalma"},
+ {523, &Hid::SetIsPalmaPairedConnectable, "SetIsPalmaPairedConnectable"},
+ {524, &Hid::PairPalma, "PairPalma"},
{525, &Hid::SetPalmaBoostMode, "SetPalmaBoostMode"},
- {526, nullptr, "CancelWritePalmaWaveEntry"},
- {527, nullptr, "EnablePalmaBoostMode"},
- {528, nullptr, "GetPalmaBluetoothAddress"},
- {529, nullptr, "SetDisallowedPalmaConnection"},
+ {526, &Hid::CancelWritePalmaWaveEntry, "CancelWritePalmaWaveEntry"},
+ {527, &Hid::EnablePalmaBoostMode, "EnablePalmaBoostMode"},
+ {528, &Hid::GetPalmaBluetoothAddress, "GetPalmaBluetoothAddress"},
+ {529, &Hid::SetDisallowedPalmaConnection, "SetDisallowedPalmaConnection"},
{1000, &Hid::SetNpadCommunicationMode, "SetNpadCommunicationMode"},
{1001, &Hid::GetNpadCommunicationMode, "GetNpadCommunicationMode"},
{1002, &Hid::SetTouchScreenConfiguration, "SetTouchScreenConfiguration"},
@@ -1878,14 +1881,361 @@ void Hid::IsUsbFullKeyControllerEnabled(Kernel::HLERequestContext& ctx) {
rb.Push(false);
}
+void Hid::GetPalmaConnectionHandle(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::NpadIdType npad_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
+ parameters.npad_id, parameters.applet_resource_user_id);
+
+ Controller_Palma::PalmaConnectionHandle handle;
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.GetPalmaConnectionHandle(parameters.npad_id, handle);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.PushRaw(handle);
+}
+
+void Hid::InitializePalma(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.InitializePalma(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::AcquirePalmaOperationCompleteEvent(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(controller.AcquirePalmaOperationCompleteEvent(connection_handle));
+}
+
+void Hid::GetPalmaOperationInfo(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ Controller_Palma::PalmaOperationType operation_type;
+ Controller_Palma::PalmaOperationData data;
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.GetPalmaOperationInfo(connection_handle, operation_type, data);
+
+ if (result.IsError()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
+ ctx.WriteBuffer(data);
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(static_cast<u64>(operation_type));
+}
+
+void Hid::PlayPalmaActivity(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+ const auto palma_activity{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, palma_activity={}",
+ connection_handle.npad_id, palma_activity);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.PlayPalmaActivity(connection_handle, palma_activity);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::SetPalmaFrModeType(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+ const auto fr_mode{rp.PopEnum<Controller_Palma::PalmaFrModeType>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, fr_mode={}",
+ connection_handle.npad_id, fr_mode);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.SetPalmaFrModeType(connection_handle, fr_mode);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::ReadPalmaStep(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.ReadPalmaStep(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::EnablePalmaStep(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ bool is_enabled;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ Controller_Palma::PalmaConnectionHandle connection_handle;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, is_enabled={}",
+ parameters.connection_handle.npad_id, parameters.is_enabled);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result =
+ controller.EnablePalmaStep(parameters.connection_handle, parameters.is_enabled);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::ResetPalmaStep(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.ResetPalmaStep(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::ReadPalmaApplicationSection(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::WritePalmaApplicationSection(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::ReadPalmaUniqueCode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .ReadPalmaUniqueCode(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::SetPalmaUniqueCodeInvalid(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .SetPalmaUniqueCodeInvalid(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::WritePalmaActivityEntry(Kernel::HLERequestContext& ctx) {
+ LOG_CRITICAL(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::WritePalmaRgbLedPatternEntry(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+ const auto unknown{rp.Pop<u64>()};
+
+ const auto buffer = ctx.ReadBuffer();
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, unknown={}",
+ connection_handle.npad_id, unknown);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .WritePalmaRgbLedPatternEntry(connection_handle, unknown);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::WritePalmaWaveEntry(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+ const auto wave_set{rp.PopEnum<Controller_Palma::PalmaWaveSet>()};
+ const auto unknown{rp.Pop<u64>()};
+ const auto t_mem_size{rp.Pop<u64>()};
+ const auto t_mem_handle{ctx.GetCopyHandle(0)};
+ const auto size{rp.Pop<u64>()};
+
+ ASSERT_MSG(t_mem_size == 0x3000, "t_mem_size is not 0x3000 bytes");
+
+ auto t_mem =
+ system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle);
+
+ if (t_mem.IsNull()) {
+ LOG_ERROR(Service_HID, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ ASSERT_MSG(t_mem->GetSize() == 0x3000, "t_mem has incorrect size");
+
+ LOG_WARNING(Service_HID,
+ "(STUBBED) called, connection_handle={}, wave_set={}, unkown={}, "
+ "t_mem_handle=0x{:08X}, t_mem_size={}, size={}",
+ connection_handle.npad_id, wave_set, unknown, t_mem_handle, t_mem_size, size);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .WritePalmaWaveEntry(connection_handle, wave_set,
+ system.Memory().GetPointer(t_mem->GetSourceAddress()), t_mem_size);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::SetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ s32 database_id_version;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ Controller_Palma::PalmaConnectionHandle connection_handle;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, database_id_version={}",
+ parameters.connection_handle.npad_id, parameters.database_id_version);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .SetPalmaDataBaseIdentificationVersion(parameters.connection_handle,
+ parameters.database_id_version);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::GetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .GetPalmaDataBaseIdentificationVersion(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::SuspendPalmaFeature(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::GetPalmaOperationResult(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ const auto result = applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .GetPalmaOperationResult(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::ReadPalmaPlayLog(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::ResetPalmaPlayLog(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void Hid::SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto is_palma_all_connectable{rp.Pop<bool>()};
+ struct Parameters {
+ bool is_palma_all_connectable;
+ INSERT_PADDING_BYTES_NOINIT(7);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
LOG_WARNING(Service_HID,
- "(STUBBED) called, applet_resource_user_id={}, is_palma_all_connectable={}",
- applet_resource_user_id, is_palma_all_connectable);
+ "(STUBBED) called, is_palma_all_connectable={},applet_resource_user_id={}",
+ parameters.is_palma_all_connectable, parameters.applet_resource_user_id);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .SetIsPalmaAllConnectable(parameters.is_palma_all_connectable);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::SetIsPalmaPairedConnectable(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::PairPalma(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .PairPalma(connection_handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -1897,6 +2247,37 @@ void Hid::SetPalmaBoostMode(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_HID, "(STUBBED) called, palma_boost_mode={}", palma_boost_mode);
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .SetPalmaBoostMode(palma_boost_mode);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::CancelWritePalmaWaveEntry(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::EnablePalmaBoostMode(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::GetPalmaBluetoothAddress(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::SetDisallowedPalmaConnection(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index ac4333022..340d26fdc 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -33,6 +33,7 @@ enum class HidController : std::size_t {
NPad,
Gesture,
ConsoleSixAxisSensor,
+ Palma,
MaxControllers,
};
@@ -166,8 +167,36 @@ private:
void FinalizeSevenSixAxisSensor(Kernel::HLERequestContext& ctx);
void ResetSevenSixAxisSensorTimestamp(Kernel::HLERequestContext& ctx);
void IsUsbFullKeyControllerEnabled(Kernel::HLERequestContext& ctx);
+ void GetPalmaConnectionHandle(Kernel::HLERequestContext& ctx);
+ void InitializePalma(Kernel::HLERequestContext& ctx);
+ void AcquirePalmaOperationCompleteEvent(Kernel::HLERequestContext& ctx);
+ void GetPalmaOperationInfo(Kernel::HLERequestContext& ctx);
+ void PlayPalmaActivity(Kernel::HLERequestContext& ctx);
+ void SetPalmaFrModeType(Kernel::HLERequestContext& ctx);
+ void ReadPalmaStep(Kernel::HLERequestContext& ctx);
+ void EnablePalmaStep(Kernel::HLERequestContext& ctx);
+ void ResetPalmaStep(Kernel::HLERequestContext& ctx);
+ void ReadPalmaApplicationSection(Kernel::HLERequestContext& ctx);
+ void WritePalmaApplicationSection(Kernel::HLERequestContext& ctx);
+ void ReadPalmaUniqueCode(Kernel::HLERequestContext& ctx);
+ void SetPalmaUniqueCodeInvalid(Kernel::HLERequestContext& ctx);
+ void WritePalmaActivityEntry(Kernel::HLERequestContext& ctx);
+ void WritePalmaRgbLedPatternEntry(Kernel::HLERequestContext& ctx);
+ void WritePalmaWaveEntry(Kernel::HLERequestContext& ctx);
+ void SetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx);
+ void GetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx);
+ void SuspendPalmaFeature(Kernel::HLERequestContext& ctx);
+ void GetPalmaOperationResult(Kernel::HLERequestContext& ctx);
+ void ReadPalmaPlayLog(Kernel::HLERequestContext& ctx);
+ void ResetPalmaPlayLog(Kernel::HLERequestContext& ctx);
void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx);
+ void SetIsPalmaPairedConnectable(Kernel::HLERequestContext& ctx);
+ void PairPalma(Kernel::HLERequestContext& ctx);
void SetPalmaBoostMode(Kernel::HLERequestContext& ctx);
+ void CancelWritePalmaWaveEntry(Kernel::HLERequestContext& ctx);
+ void EnablePalmaBoostMode(Kernel::HLERequestContext& ctx);
+ void GetPalmaBluetoothAddress(Kernel::HLERequestContext& ctx);
+ void SetDisallowedPalmaConnection(Kernel::HLERequestContext& ctx);
void SetNpadCommunicationMode(Kernel::HLERequestContext& ctx);
void GetNpadCommunicationMode(Kernel::HLERequestContext& ctx);
void SetTouchScreenConfiguration(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index c4b44cbf9..6a3453457 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -542,7 +542,8 @@ Result IRS::IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_h
Core::IrSensor::DeviceFormat& IRS::GetIrCameraSharedMemoryDeviceEntry(
const Core::IrSensor::IrCameraHandle& camera_handle) {
- ASSERT_MSG(sizeof(StatusManager::device) > camera_handle.npad_id, "invalid npad_id");
+ const auto npad_id_max_index = static_cast<u8>(sizeof(StatusManager::device));
+ ASSERT_MSG(camera_handle.npad_id < npad_id_max_index, "invalid npad_id");
return shared_memory->device[camera_handle.npad_id];
}
diff --git a/src/core/hle/service/ldn/lan_discovery.cpp b/src/core/hle/service/ldn/lan_discovery.cpp
new file mode 100644
index 000000000..8f3c04550
--- /dev/null
+++ b/src/core/hle/service/ldn/lan_discovery.cpp
@@ -0,0 +1,633 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/ldn/lan_discovery.h"
+#include "core/internal_network/network.h"
+#include "core/internal_network/network_interface.h"
+
+namespace Service::LDN {
+
+LanStation::LanStation(s8 node_id_, LANDiscovery* discovery_)
+ : node_info(nullptr), status(NodeStatus::Disconnected), node_id(node_id_),
+ discovery(discovery_) {}
+
+LanStation::~LanStation() = default;
+
+NodeStatus LanStation::GetStatus() const {
+ return status;
+}
+
+void LanStation::OnClose() {
+ LOG_INFO(Service_LDN, "OnClose {}", node_id);
+ Reset();
+ discovery->UpdateNodes();
+}
+
+void LanStation::Reset() {
+ status = NodeStatus::Disconnected;
+};
+
+void LanStation::OverrideInfo() {
+ bool connected = GetStatus() == NodeStatus::Connected;
+ node_info->node_id = node_id;
+ node_info->is_connected = connected ? 1 : 0;
+}
+
+LANDiscovery::LANDiscovery(Network::RoomNetwork& room_network_)
+ : stations({{{1, this}, {2, this}, {3, this}, {4, this}, {5, this}, {6, this}, {7, this}}}),
+ room_network{room_network_} {}
+
+LANDiscovery::~LANDiscovery() {
+ if (inited) {
+ Result rc = Finalize();
+ LOG_INFO(Service_LDN, "Finalize: {}", rc.raw);
+ }
+}
+
+void LANDiscovery::InitNetworkInfo() {
+ network_info.common.bssid = GetFakeMac();
+ network_info.common.channel = WifiChannel::Wifi24_6;
+ network_info.common.link_level = LinkLevel::Good;
+ network_info.common.network_type = PackedNetworkType::Ldn;
+ network_info.common.ssid = fake_ssid;
+
+ auto& nodes = network_info.ldn.nodes;
+ for (std::size_t i = 0; i < NodeCountMax; i++) {
+ nodes[i].node_id = static_cast<s8>(i);
+ nodes[i].is_connected = 0;
+ }
+}
+
+void LANDiscovery::InitNodeStateChange() {
+ for (auto& node_update : node_changes) {
+ node_update.state_change = NodeStateChange::None;
+ }
+ for (auto& node_state : node_last_states) {
+ node_state = 0;
+ }
+}
+
+State LANDiscovery::GetState() const {
+ return state;
+}
+
+void LANDiscovery::SetState(State new_state) {
+ state = new_state;
+}
+
+Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network) const {
+ if (state == State::AccessPointCreated || state == State::StationConnected) {
+ std::memcpy(&out_network, &network_info, sizeof(network_info));
+ return ResultSuccess;
+ }
+
+ return ResultBadState;
+}
+
+Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network,
+ std::vector<NodeLatestUpdate>& out_updates,
+ std::size_t buffer_count) {
+ if (buffer_count > NodeCountMax) {
+ return ResultInvalidBufferCount;
+ }
+
+ if (state == State::AccessPointCreated || state == State::StationConnected) {
+ std::memcpy(&out_network, &network_info, sizeof(network_info));
+ for (std::size_t i = 0; i < buffer_count; i++) {
+ out_updates[i].state_change = node_changes[i].state_change;
+ node_changes[i].state_change = NodeStateChange::None;
+ }
+ return ResultSuccess;
+ }
+
+ return ResultBadState;
+}
+
+DisconnectReason LANDiscovery::GetDisconnectReason() const {
+ return disconnect_reason;
+}
+
+Result LANDiscovery::Scan(std::vector<NetworkInfo>& networks, u16& count,
+ const ScanFilter& filter) {
+ if (!IsFlagSet(filter.flag, ScanFilterFlag::NetworkType) ||
+ filter.network_type <= NetworkType::All) {
+ if (!IsFlagSet(filter.flag, ScanFilterFlag::Ssid) && filter.ssid.length >= SsidLengthMax) {
+ return ResultBadInput;
+ }
+ }
+
+ {
+ std::scoped_lock lock{packet_mutex};
+ scan_results.clear();
+
+ SendBroadcast(Network::LDNPacketType::Scan);
+ }
+
+ LOG_INFO(Service_LDN, "Waiting for scan replies");
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+
+ std::scoped_lock lock{packet_mutex};
+ for (const auto& [key, info] : scan_results) {
+ if (count >= networks.size()) {
+ break;
+ }
+
+ if (IsFlagSet(filter.flag, ScanFilterFlag::LocalCommunicationId)) {
+ if (filter.network_id.intent_id.local_communication_id !=
+ info.network_id.intent_id.local_communication_id) {
+ continue;
+ }
+ }
+ if (IsFlagSet(filter.flag, ScanFilterFlag::SessionId)) {
+ if (filter.network_id.session_id != info.network_id.session_id) {
+ continue;
+ }
+ }
+ if (IsFlagSet(filter.flag, ScanFilterFlag::NetworkType)) {
+ if (filter.network_type != static_cast<NetworkType>(info.common.network_type)) {
+ continue;
+ }
+ }
+ if (IsFlagSet(filter.flag, ScanFilterFlag::Ssid)) {
+ if (filter.ssid != info.common.ssid) {
+ continue;
+ }
+ }
+ if (IsFlagSet(filter.flag, ScanFilterFlag::SceneId)) {
+ if (filter.network_id.intent_id.scene_id != info.network_id.intent_id.scene_id) {
+ continue;
+ }
+ }
+
+ networks[count++] = info;
+ }
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::SetAdvertiseData(std::span<const u8> data) {
+ std::scoped_lock lock{packet_mutex};
+ const std::size_t size = data.size();
+ if (size > AdvertiseDataSizeMax) {
+ return ResultAdvertiseDataTooLarge;
+ }
+
+ std::memcpy(network_info.ldn.advertise_data.data(), data.data(), size);
+ network_info.ldn.advertise_data_size = static_cast<u16>(size);
+
+ UpdateNodes();
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::OpenAccessPoint() {
+ std::scoped_lock lock{packet_mutex};
+ disconnect_reason = DisconnectReason::None;
+ if (state == State::None) {
+ return ResultBadState;
+ }
+
+ ResetStations();
+ SetState(State::AccessPointOpened);
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::CloseAccessPoint() {
+ std::scoped_lock lock{packet_mutex};
+ if (state == State::None) {
+ return ResultBadState;
+ }
+
+ if (state == State::AccessPointCreated) {
+ DestroyNetwork();
+ }
+
+ ResetStations();
+ SetState(State::Initialized);
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::OpenStation() {
+ std::scoped_lock lock{packet_mutex};
+ disconnect_reason = DisconnectReason::None;
+ if (state == State::None) {
+ return ResultBadState;
+ }
+
+ ResetStations();
+ SetState(State::StationOpened);
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::CloseStation() {
+ std::scoped_lock lock{packet_mutex};
+ if (state == State::None) {
+ return ResultBadState;
+ }
+
+ if (state == State::StationConnected) {
+ Disconnect();
+ }
+
+ ResetStations();
+ SetState(State::Initialized);
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::CreateNetwork(const SecurityConfig& security_config,
+ const UserConfig& user_config,
+ const NetworkConfig& network_config) {
+ std::scoped_lock lock{packet_mutex};
+
+ if (state != State::AccessPointOpened) {
+ return ResultBadState;
+ }
+
+ InitNetworkInfo();
+ network_info.ldn.node_count_max = network_config.node_count_max;
+ network_info.ldn.security_mode = security_config.security_mode;
+
+ if (network_config.channel == WifiChannel::Default) {
+ network_info.common.channel = WifiChannel::Wifi24_6;
+ } else {
+ network_info.common.channel = network_config.channel;
+ }
+
+ std::independent_bits_engine<std::mt19937, 64, u64> bits_engine;
+ network_info.network_id.session_id.high = bits_engine();
+ network_info.network_id.session_id.low = bits_engine();
+ network_info.network_id.intent_id = network_config.intent_id;
+
+ NodeInfo& node0 = network_info.ldn.nodes[0];
+ const Result rc2 = GetNodeInfo(node0, user_config, network_config.local_communication_version);
+ if (rc2.IsError()) {
+ return ResultAccessPointConnectionFailed;
+ }
+
+ SetState(State::AccessPointCreated);
+
+ InitNodeStateChange();
+ node0.is_connected = 1;
+ UpdateNodes();
+
+ return rc2;
+}
+
+Result LANDiscovery::DestroyNetwork() {
+ for (auto local_ip : connected_clients) {
+ SendPacket(Network::LDNPacketType::DestroyNetwork, local_ip);
+ }
+
+ ResetStations();
+
+ SetState(State::AccessPointOpened);
+ lan_event();
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::Connect(const NetworkInfo& network_info_, const UserConfig& user_config,
+ u16 local_communication_version) {
+ std::scoped_lock lock{packet_mutex};
+ if (network_info_.ldn.node_count == 0) {
+ return ResultInvalidNodeCount;
+ }
+
+ Result rc = GetNodeInfo(node_info, user_config, local_communication_version);
+ if (rc.IsError()) {
+ return ResultConnectionFailed;
+ }
+
+ Ipv4Address node_host = network_info_.ldn.nodes[0].ipv4_address;
+ std::reverse(std::begin(node_host), std::end(node_host)); // htonl
+ host_ip = node_host;
+ SendPacket(Network::LDNPacketType::Connect, node_info, *host_ip);
+
+ InitNodeStateChange();
+
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::Disconnect() {
+ if (host_ip) {
+ SendPacket(Network::LDNPacketType::Disconnect, node_info, *host_ip);
+ }
+
+ SetState(State::StationOpened);
+ lan_event();
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::Initialize(LanEventFunc lan_event_, bool listening) {
+ std::scoped_lock lock{packet_mutex};
+ if (inited) {
+ return ResultSuccess;
+ }
+
+ for (auto& station : stations) {
+ station.discovery = this;
+ station.node_info = &network_info.ldn.nodes[station.node_id];
+ station.Reset();
+ }
+
+ connected_clients.clear();
+ lan_event = lan_event_;
+
+ SetState(State::Initialized);
+
+ inited = true;
+ return ResultSuccess;
+}
+
+Result LANDiscovery::Finalize() {
+ std::scoped_lock lock{packet_mutex};
+ Result rc = ResultSuccess;
+
+ if (inited) {
+ if (state == State::AccessPointCreated) {
+ DestroyNetwork();
+ }
+ if (state == State::StationConnected) {
+ Disconnect();
+ }
+
+ ResetStations();
+ inited = false;
+ }
+
+ SetState(State::None);
+
+ return rc;
+}
+
+void LANDiscovery::ResetStations() {
+ for (auto& station : stations) {
+ station.Reset();
+ }
+ connected_clients.clear();
+}
+
+void LANDiscovery::UpdateNodes() {
+ u8 count = 0;
+ for (auto& station : stations) {
+ bool connected = station.GetStatus() == NodeStatus::Connected;
+ if (connected) {
+ count++;
+ }
+ station.OverrideInfo();
+ }
+ network_info.ldn.node_count = count + 1;
+
+ for (auto local_ip : connected_clients) {
+ SendPacket(Network::LDNPacketType::SyncNetwork, network_info, local_ip);
+ }
+
+ OnNetworkInfoChanged();
+}
+
+void LANDiscovery::OnSyncNetwork(const NetworkInfo& info) {
+ network_info = info;
+ if (state == State::StationOpened) {
+ SetState(State::StationConnected);
+ }
+ OnNetworkInfoChanged();
+}
+
+void LANDiscovery::OnDisconnectFromHost() {
+ LOG_INFO(Service_LDN, "OnDisconnectFromHost state: {}", static_cast<int>(state));
+ host_ip = std::nullopt;
+ if (state == State::StationConnected) {
+ SetState(State::StationOpened);
+ lan_event();
+ }
+}
+
+void LANDiscovery::OnNetworkInfoChanged() {
+ if (IsNodeStateChanged()) {
+ lan_event();
+ }
+ return;
+}
+
+Network::IPv4Address LANDiscovery::GetLocalIp() const {
+ Network::IPv4Address local_ip{0xFF, 0xFF, 0xFF, 0xFF};
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ if (room_member->IsConnected()) {
+ local_ip = room_member->GetFakeIpAddress();
+ }
+ }
+ return local_ip;
+}
+
+template <typename Data>
+void LANDiscovery::SendPacket(Network::LDNPacketType type, const Data& data,
+ Ipv4Address remote_ip) {
+ Network::LDNPacket packet;
+ packet.type = type;
+
+ packet.broadcast = false;
+ packet.local_ip = GetLocalIp();
+ packet.remote_ip = remote_ip;
+
+ packet.data.resize(sizeof(data));
+ std::memcpy(packet.data.data(), &data, sizeof(data));
+ SendPacket(packet);
+}
+
+void LANDiscovery::SendPacket(Network::LDNPacketType type, Ipv4Address remote_ip) {
+ Network::LDNPacket packet;
+ packet.type = type;
+
+ packet.broadcast = false;
+ packet.local_ip = GetLocalIp();
+ packet.remote_ip = remote_ip;
+
+ SendPacket(packet);
+}
+
+template <typename Data>
+void LANDiscovery::SendBroadcast(Network::LDNPacketType type, const Data& data) {
+ Network::LDNPacket packet;
+ packet.type = type;
+
+ packet.broadcast = true;
+ packet.local_ip = GetLocalIp();
+
+ packet.data.resize(sizeof(data));
+ std::memcpy(packet.data.data(), &data, sizeof(data));
+ SendPacket(packet);
+}
+
+void LANDiscovery::SendBroadcast(Network::LDNPacketType type) {
+ Network::LDNPacket packet;
+ packet.type = type;
+
+ packet.broadcast = true;
+ packet.local_ip = GetLocalIp();
+
+ SendPacket(packet);
+}
+
+void LANDiscovery::SendPacket(const Network::LDNPacket& packet) {
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ if (room_member->IsConnected()) {
+ room_member->SendLdnPacket(packet);
+ }
+ }
+}
+
+void LANDiscovery::ReceivePacket(const Network::LDNPacket& packet) {
+ std::scoped_lock lock{packet_mutex};
+ switch (packet.type) {
+ case Network::LDNPacketType::Scan: {
+ LOG_INFO(Frontend, "Scan packet received!");
+ if (state == State::AccessPointCreated) {
+ // Reply to the sender
+ SendPacket(Network::LDNPacketType::ScanResp, network_info, packet.local_ip);
+ }
+ break;
+ }
+ case Network::LDNPacketType::ScanResp: {
+ LOG_INFO(Frontend, "ScanResp packet received!");
+
+ NetworkInfo info{};
+ std::memcpy(&info, packet.data.data(), sizeof(NetworkInfo));
+ scan_results.insert({info.common.bssid, info});
+
+ break;
+ }
+ case Network::LDNPacketType::Connect: {
+ LOG_INFO(Frontend, "Connect packet received!");
+
+ NodeInfo info{};
+ std::memcpy(&info, packet.data.data(), sizeof(NodeInfo));
+
+ connected_clients.push_back(packet.local_ip);
+
+ for (LanStation& station : stations) {
+ if (station.status != NodeStatus::Connected) {
+ *station.node_info = info;
+ station.status = NodeStatus::Connected;
+ break;
+ }
+ }
+
+ UpdateNodes();
+
+ break;
+ }
+ case Network::LDNPacketType::Disconnect: {
+ LOG_INFO(Frontend, "Disconnect packet received!");
+
+ connected_clients.erase(
+ std::remove(connected_clients.begin(), connected_clients.end(), packet.local_ip),
+ connected_clients.end());
+
+ NodeInfo info{};
+ std::memcpy(&info, packet.data.data(), sizeof(NodeInfo));
+
+ for (LanStation& station : stations) {
+ if (station.status == NodeStatus::Connected &&
+ station.node_info->mac_address == info.mac_address) {
+ station.OnClose();
+ break;
+ }
+ }
+
+ break;
+ }
+ case Network::LDNPacketType::DestroyNetwork: {
+ ResetStations();
+ OnDisconnectFromHost();
+ break;
+ }
+ case Network::LDNPacketType::SyncNetwork: {
+ if (state == State::StationOpened || state == State::StationConnected) {
+ LOG_INFO(Frontend, "SyncNetwork packet received!");
+ NetworkInfo info{};
+ std::memcpy(&info, packet.data.data(), sizeof(NetworkInfo));
+
+ OnSyncNetwork(info);
+ } else {
+ LOG_INFO(Frontend, "SyncNetwork packet received but in wrong State!");
+ }
+
+ break;
+ }
+ default: {
+ LOG_INFO(Frontend, "ReceivePacket unhandled type {}", static_cast<int>(packet.type));
+ break;
+ }
+ }
+}
+
+bool LANDiscovery::IsNodeStateChanged() {
+ bool changed = false;
+ const auto& nodes = network_info.ldn.nodes;
+ for (int i = 0; i < NodeCountMax; i++) {
+ if (nodes[i].is_connected != node_last_states[i]) {
+ if (nodes[i].is_connected) {
+ node_changes[i].state_change |= NodeStateChange::Connect;
+ } else {
+ node_changes[i].state_change |= NodeStateChange::Disconnect;
+ }
+ node_last_states[i] = nodes[i].is_connected;
+ changed = true;
+ }
+ }
+ return changed;
+}
+
+bool LANDiscovery::IsFlagSet(ScanFilterFlag flag, ScanFilterFlag search_flag) const {
+ const auto flag_value = static_cast<u32>(flag);
+ const auto search_flag_value = static_cast<u32>(search_flag);
+ return (flag_value & search_flag_value) == search_flag_value;
+}
+
+int LANDiscovery::GetStationCount() const {
+ return static_cast<int>(
+ std::count_if(stations.begin(), stations.end(), [](const auto& station) {
+ return station.GetStatus() != NodeStatus::Disconnected;
+ }));
+}
+
+MacAddress LANDiscovery::GetFakeMac() const {
+ MacAddress mac{};
+ mac.raw[0] = 0x02;
+ mac.raw[1] = 0x00;
+
+ const auto ip = GetLocalIp();
+ memcpy(mac.raw.data() + 2, &ip, sizeof(ip));
+
+ return mac;
+}
+
+Result LANDiscovery::GetNodeInfo(NodeInfo& node, const UserConfig& userConfig,
+ u16 localCommunicationVersion) {
+ const auto network_interface = Network::GetSelectedNetworkInterface();
+
+ if (!network_interface) {
+ LOG_ERROR(Service_LDN, "No network interface available");
+ return ResultNoIpAddress;
+ }
+
+ node.mac_address = GetFakeMac();
+ node.is_connected = 1;
+ std::memcpy(node.user_name.data(), userConfig.user_name.data(), UserNameBytesMax + 1);
+ node.local_communication_version = localCommunicationVersion;
+
+ Ipv4Address current_address = GetLocalIp();
+ std::reverse(std::begin(current_address), std::end(current_address)); // ntohl
+ node.ipv4_address = current_address;
+
+ return ResultSuccess;
+}
+
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/lan_discovery.h b/src/core/hle/service/ldn/lan_discovery.h
new file mode 100644
index 000000000..3833cd764
--- /dev/null
+++ b/src/core/hle/service/ldn/lan_discovery.h
@@ -0,0 +1,134 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <cstring>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <random>
+#include <span>
+#include <thread>
+#include <unordered_map>
+
+#include "common/logging/log.h"
+#include "common/socket_types.h"
+#include "core/hle/result.h"
+#include "core/hle/service/ldn/ldn_results.h"
+#include "core/hle/service/ldn/ldn_types.h"
+#include "network/network.h"
+
+namespace Service::LDN {
+
+class LANDiscovery;
+
+class LanStation {
+public:
+ LanStation(s8 node_id_, LANDiscovery* discovery_);
+ ~LanStation();
+
+ void OnClose();
+ NodeStatus GetStatus() const;
+ void Reset();
+ void OverrideInfo();
+
+protected:
+ friend class LANDiscovery;
+ NodeInfo* node_info;
+ NodeStatus status;
+ s8 node_id;
+ LANDiscovery* discovery;
+};
+
+class LANDiscovery {
+public:
+ using LanEventFunc = std::function<void()>;
+
+ LANDiscovery(Network::RoomNetwork& room_network_);
+ ~LANDiscovery();
+
+ State GetState() const;
+ void SetState(State new_state);
+
+ Result GetNetworkInfo(NetworkInfo& out_network) const;
+ Result GetNetworkInfo(NetworkInfo& out_network, std::vector<NodeLatestUpdate>& out_updates,
+ std::size_t buffer_count);
+
+ DisconnectReason GetDisconnectReason() const;
+ Result Scan(std::vector<NetworkInfo>& networks, u16& count, const ScanFilter& filter);
+ Result SetAdvertiseData(std::span<const u8> data);
+
+ Result OpenAccessPoint();
+ Result CloseAccessPoint();
+
+ Result OpenStation();
+ Result CloseStation();
+
+ Result CreateNetwork(const SecurityConfig& security_config, const UserConfig& user_config,
+ const NetworkConfig& network_config);
+ Result DestroyNetwork();
+
+ Result Connect(const NetworkInfo& network_info_, const UserConfig& user_config,
+ u16 local_communication_version);
+ Result Disconnect();
+
+ Result Initialize(LanEventFunc lan_event_ = empty_func, bool listening = true);
+ Result Finalize();
+
+ void ReceivePacket(const Network::LDNPacket& packet);
+
+protected:
+ friend class LanStation;
+
+ void InitNetworkInfo();
+ void InitNodeStateChange();
+
+ void ResetStations();
+ void UpdateNodes();
+
+ void OnSyncNetwork(const NetworkInfo& info);
+ void OnDisconnectFromHost();
+ void OnNetworkInfoChanged();
+
+ bool IsNodeStateChanged();
+ bool IsFlagSet(ScanFilterFlag flag, ScanFilterFlag search_flag) const;
+ int GetStationCount() const;
+ MacAddress GetFakeMac() const;
+ Result GetNodeInfo(NodeInfo& node, const UserConfig& user_config,
+ u16 local_communication_version);
+
+ Network::IPv4Address GetLocalIp() const;
+ template <typename Data>
+ void SendPacket(Network::LDNPacketType type, const Data& data, Ipv4Address remote_ip);
+ void SendPacket(Network::LDNPacketType type, Ipv4Address remote_ip);
+ template <typename Data>
+ void SendBroadcast(Network::LDNPacketType type, const Data& data);
+ void SendBroadcast(Network::LDNPacketType type);
+ void SendPacket(const Network::LDNPacket& packet);
+
+ static const LanEventFunc empty_func;
+ static constexpr Ssid fake_ssid{"YuzuFakeSsidForLdn"};
+
+ bool inited{};
+ std::mutex packet_mutex;
+ std::array<LanStation, StationCountMax> stations;
+ std::array<NodeLatestUpdate, NodeCountMax> node_changes{};
+ std::array<u8, NodeCountMax> node_last_states{};
+ std::unordered_map<MacAddress, NetworkInfo, MACAddressHash> scan_results{};
+ NodeInfo node_info{};
+ NetworkInfo network_info{};
+ State state{State::None};
+ DisconnectReason disconnect_reason{DisconnectReason::None};
+
+ // TODO (flTobi): Should this be an std::set?
+ std::vector<Ipv4Address> connected_clients;
+ std::optional<Ipv4Address> host_ip;
+
+ LanEventFunc lan_event;
+
+ Network::RoomNetwork& room_network;
+};
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp
index c11daff54..ea3e7e55a 100644
--- a/src/core/hle/service/ldn/ldn.cpp
+++ b/src/core/hle/service/ldn/ldn.cpp
@@ -4,11 +4,13 @@
#include <memory>
#include "core/core.h"
+#include "core/hle/service/ldn/lan_discovery.h"
#include "core/hle/service/ldn/ldn.h"
#include "core/hle/service/ldn/ldn_results.h"
#include "core/hle/service/ldn/ldn_types.h"
#include "core/internal_network/network.h"
#include "core/internal_network/network_interface.h"
+#include "network/network.h"
// This is defined by synchapi.h and conflicts with ServiceContext::CreateEvent
#undef CreateEvent
@@ -105,13 +107,13 @@ class IUserLocalCommunicationService final
public:
explicit IUserLocalCommunicationService(Core::System& system_)
: ServiceFramework{system_, "IUserLocalCommunicationService", ServiceThreadType::CreateNew},
- service_context{system, "IUserLocalCommunicationService"}, room_network{
- system_.GetRoomNetwork()} {
+ service_context{system, "IUserLocalCommunicationService"},
+ room_network{system_.GetRoomNetwork()}, lan_discovery{room_network} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IUserLocalCommunicationService::GetState, "GetState"},
{1, &IUserLocalCommunicationService::GetNetworkInfo, "GetNetworkInfo"},
- {2, nullptr, "GetIpv4Address"},
+ {2, &IUserLocalCommunicationService::GetIpv4Address, "GetIpv4Address"},
{3, &IUserLocalCommunicationService::GetDisconnectReason, "GetDisconnectReason"},
{4, &IUserLocalCommunicationService::GetSecurityParameter, "GetSecurityParameter"},
{5, &IUserLocalCommunicationService::GetNetworkConfig, "GetNetworkConfig"},
@@ -119,7 +121,7 @@ public:
{101, &IUserLocalCommunicationService::GetNetworkInfoLatestUpdate, "GetNetworkInfoLatestUpdate"},
{102, &IUserLocalCommunicationService::Scan, "Scan"},
{103, &IUserLocalCommunicationService::ScanPrivate, "ScanPrivate"},
- {104, nullptr, "SetWirelessControllerRestriction"},
+ {104, &IUserLocalCommunicationService::SetWirelessControllerRestriction, "SetWirelessControllerRestriction"},
{200, &IUserLocalCommunicationService::OpenAccessPoint, "OpenAccessPoint"},
{201, &IUserLocalCommunicationService::CloseAccessPoint, "CloseAccessPoint"},
{202, &IUserLocalCommunicationService::CreateNetwork, "CreateNetwork"},
@@ -148,16 +150,30 @@ public:
}
~IUserLocalCommunicationService() {
+ if (is_initialized) {
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ room_member->Unbind(ldn_packet_received);
+ }
+ }
+
service_context.CloseEvent(state_change_event);
}
+ /// Callback to parse and handle a received LDN packet.
+ void OnLDNPacketReceived(const Network::LDNPacket& packet) {
+ lan_discovery.ReceivePacket(packet);
+ }
+
void OnEventFired() {
state_change_event->GetWritableEvent().Signal();
}
void GetState(Kernel::HLERequestContext& ctx) {
State state = State::Error;
- LOG_WARNING(Service_LDN, "(STUBBED) called, state = {}", state);
+
+ if (is_initialized) {
+ state = lan_discovery.GetState();
+ }
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
@@ -175,7 +191,7 @@ public:
}
NetworkInfo network_info{};
- const auto rc = ResultSuccess;
+ const auto rc = lan_discovery.GetNetworkInfo(network_info);
if (rc.IsError()) {
LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
IPC::ResponseBuilder rb{ctx, 2};
@@ -183,28 +199,50 @@ public:
return;
}
- LOG_WARNING(Service_LDN, "(STUBBED) called, ssid='{}', nodes={}",
- network_info.common.ssid.GetStringValue(), network_info.ldn.node_count);
-
ctx.WriteBuffer<NetworkInfo>(network_info);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(rc);
+ rb.Push(ResultSuccess);
}
- void GetDisconnectReason(Kernel::HLERequestContext& ctx) {
- const auto disconnect_reason = DisconnectReason::None;
+ void GetIpv4Address(Kernel::HLERequestContext& ctx) {
+ const auto network_interface = Network::GetSelectedNetworkInterface();
+
+ if (!network_interface) {
+ LOG_ERROR(Service_LDN, "No network interface available");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultNoIpAddress);
+ return;
+ }
- LOG_WARNING(Service_LDN, "(STUBBED) called, disconnect_reason={}", disconnect_reason);
+ Ipv4Address current_address{Network::TranslateIPv4(network_interface->ip_address)};
+ Ipv4Address subnet_mask{Network::TranslateIPv4(network_interface->subnet_mask)};
+
+ // When we're connected to a room, spoof the hosts IP address
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ if (room_member->IsConnected()) {
+ current_address = room_member->GetFakeIpAddress();
+ }
+ }
+
+ std::reverse(std::begin(current_address), std::end(current_address)); // ntohl
+ std::reverse(std::begin(subnet_mask), std::end(subnet_mask)); // ntohl
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(current_address);
+ rb.PushRaw(subnet_mask);
+ }
+ void GetDisconnectReason(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.PushEnum(disconnect_reason);
+ rb.PushEnum(lan_discovery.GetDisconnectReason());
}
void GetSecurityParameter(Kernel::HLERequestContext& ctx) {
SecurityParameter security_parameter{};
NetworkInfo info{};
- const Result rc = ResultSuccess;
+ const Result rc = lan_discovery.GetNetworkInfo(info);
if (rc.IsError()) {
LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
@@ -217,8 +255,6 @@ public:
std::memcpy(security_parameter.data.data(), info.ldn.security_parameter.data(),
sizeof(SecurityParameter::data));
- LOG_WARNING(Service_LDN, "(STUBBED) called");
-
IPC::ResponseBuilder rb{ctx, 10};
rb.Push(rc);
rb.PushRaw<SecurityParameter>(security_parameter);
@@ -227,7 +263,7 @@ public:
void GetNetworkConfig(Kernel::HLERequestContext& ctx) {
NetworkConfig config{};
NetworkInfo info{};
- const Result rc = ResultSuccess;
+ const Result rc = lan_discovery.GetNetworkInfo(info);
if (rc.IsError()) {
LOG_ERROR(Service_LDN, "NetworkConfig is not valid {}", rc.raw);
@@ -241,12 +277,6 @@ public:
config.node_count_max = info.ldn.node_count_max;
config.local_communication_version = info.ldn.nodes[0].local_communication_version;
- LOG_WARNING(Service_LDN,
- "(STUBBED) called, intent_id={}/{}, channel={}, node_count_max={}, "
- "local_communication_version={}",
- config.intent_id.local_communication_id, config.intent_id.scene_id,
- config.channel, config.node_count_max, config.local_communication_version);
-
IPC::ResponseBuilder rb{ctx, 10};
rb.Push(rc);
rb.PushRaw<NetworkConfig>(config);
@@ -265,17 +295,17 @@ public:
const std::size_t node_buffer_count = ctx.GetWriteBufferSize(1) / sizeof(NodeLatestUpdate);
if (node_buffer_count == 0 || network_buffer_size != sizeof(NetworkInfo)) {
- LOG_ERROR(Service_LDN, "Invalid buffer size {}, {}", network_buffer_size,
+ LOG_ERROR(Service_LDN, "Invalid buffer, size = {}, count = {}", network_buffer_size,
node_buffer_count);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultBadInput);
return;
}
- NetworkInfo info;
+ NetworkInfo info{};
std::vector<NodeLatestUpdate> latest_update(node_buffer_count);
- const auto rc = ResultSuccess;
+ const auto rc = lan_discovery.GetNetworkInfo(info, latest_update, latest_update.size());
if (rc.IsError()) {
LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
IPC::ResponseBuilder rb{ctx, 2};
@@ -283,9 +313,6 @@ public:
return;
}
- LOG_WARNING(Service_LDN, "(STUBBED) called, ssid='{}', nodes={}",
- info.common.ssid.GetStringValue(), info.ldn.node_count);
-
ctx.WriteBuffer(info, 0);
ctx.WriteBuffer(latest_update, 1);
@@ -317,92 +344,78 @@ public:
u16 count = 0;
std::vector<NetworkInfo> network_infos(network_info_size);
+ Result rc = lan_discovery.Scan(network_infos, count, scan_filter);
- LOG_WARNING(Service_LDN,
- "(STUBBED) called, channel={}, filter_scan_flag={}, filter_network_type={}",
- channel, scan_filter.flag, scan_filter.network_type);
+ LOG_INFO(Service_LDN,
+ "called, channel={}, filter_scan_flag={}, filter_network_type={}, is_private={}",
+ channel, scan_filter.flag, scan_filter.network_type, is_private);
ctx.WriteBuffer(network_infos);
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
+ rb.Push(rc);
rb.Push<u32>(count);
}
- void OpenAccessPoint(Kernel::HLERequestContext& ctx) {
+ void SetWirelessControllerRestriction(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_LDN, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
+ void OpenAccessPoint(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_LDN, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(lan_discovery.OpenAccessPoint());
+ }
+
void CloseAccessPoint(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
+ LOG_INFO(Service_LDN, "called");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(lan_discovery.CloseAccessPoint());
}
void CreateNetwork(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- SecurityConfig security_config;
- UserConfig user_config;
- INSERT_PADDING_WORDS_NOINIT(1);
- NetworkConfig network_config;
- };
- static_assert(sizeof(Parameters) == 0x98, "Parameters has incorrect size.");
+ LOG_INFO(Service_LDN, "called");
- const auto parameters{rp.PopRaw<Parameters>()};
+ CreateNetworkImpl(ctx);
+ }
- LOG_WARNING(Service_LDN,
- "(STUBBED) called, passphrase_size={}, security_mode={}, "
- "local_communication_version={}",
- parameters.security_config.passphrase_size,
- parameters.security_config.security_mode,
- parameters.network_config.local_communication_version);
+ void CreateNetworkPrivate(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_LDN, "called");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ CreateNetworkImpl(ctx, true);
}
- void CreateNetworkPrivate(Kernel::HLERequestContext& ctx) {
+ void CreateNetworkImpl(Kernel::HLERequestContext& ctx, bool is_private = false) {
IPC::RequestParser rp{ctx};
- struct Parameters {
- SecurityConfig security_config;
- SecurityParameter security_parameter;
- UserConfig user_config;
- NetworkConfig network_config;
- };
- static_assert(sizeof(Parameters) == 0xB8, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
- LOG_WARNING(Service_LDN,
- "(STUBBED) called, passphrase_size={}, security_mode={}, "
- "local_communication_version={}",
- parameters.security_config.passphrase_size,
- parameters.security_config.security_mode,
- parameters.network_config.local_communication_version);
+ const auto security_config{rp.PopRaw<SecurityConfig>()};
+ [[maybe_unused]] const auto security_parameter{is_private ? rp.PopRaw<SecurityParameter>()
+ : SecurityParameter{}};
+ const auto user_config{rp.PopRaw<UserConfig>()};
+ rp.Pop<u32>(); // Padding
+ const auto network_Config{rp.PopRaw<NetworkConfig>()};
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(lan_discovery.CreateNetwork(security_config, user_config, network_Config));
}
void DestroyNetwork(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
+ LOG_INFO(Service_LDN, "called");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(lan_discovery.DestroyNetwork());
}
void SetAdvertiseData(Kernel::HLERequestContext& ctx) {
std::vector<u8> read_buffer = ctx.ReadBuffer();
- LOG_WARNING(Service_LDN, "(STUBBED) called, size {}", read_buffer.size());
-
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(lan_discovery.SetAdvertiseData(read_buffer));
}
void SetStationAcceptPolicy(Kernel::HLERequestContext& ctx) {
@@ -420,17 +433,17 @@ public:
}
void OpenStation(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
+ LOG_INFO(Service_LDN, "called");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(lan_discovery.OpenStation());
}
void CloseStation(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
+ LOG_INFO(Service_LDN, "called");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(lan_discovery.CloseStation());
}
void Connect(Kernel::HLERequestContext& ctx) {
@@ -445,16 +458,13 @@ public:
const auto parameters{rp.PopRaw<Parameters>()};
- LOG_WARNING(Service_LDN,
- "(STUBBED) called, passphrase_size={}, security_mode={}, "
- "local_communication_version={}",
- parameters.security_config.passphrase_size,
- parameters.security_config.security_mode,
- parameters.local_communication_version);
+ LOG_INFO(Service_LDN,
+ "called, passphrase_size={}, security_mode={}, "
+ "local_communication_version={}",
+ parameters.security_config.passphrase_size,
+ parameters.security_config.security_mode, parameters.local_communication_version);
const std::vector<u8> read_buffer = ctx.ReadBuffer();
- NetworkInfo network_info{};
-
if (read_buffer.size() != sizeof(NetworkInfo)) {
LOG_ERROR(Frontend, "NetworkInfo doesn't match read_buffer size!");
IPC::ResponseBuilder rb{ctx, 2};
@@ -462,40 +472,47 @@ public:
return;
}
+ NetworkInfo network_info{};
std::memcpy(&network_info, read_buffer.data(), read_buffer.size());
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(lan_discovery.Connect(network_info, parameters.user_config,
+ static_cast<u16>(parameters.local_communication_version)));
}
void Disconnect(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
+ LOG_INFO(Service_LDN, "called");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(lan_discovery.Disconnect());
}
- void Initialize(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
+ void Initialize(Kernel::HLERequestContext& ctx) {
const auto rc = InitializeImpl(ctx);
+ if (rc.IsError()) {
+ LOG_ERROR(Service_LDN, "Network isn't initialized, rc={}", rc.raw);
+ }
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(rc);
}
void Finalize(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ room_member->Unbind(ldn_packet_received);
+ }
is_initialized = false;
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(lan_discovery.Finalize());
}
void Initialize2(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
-
const auto rc = InitializeImpl(ctx);
+ if (rc.IsError()) {
+ LOG_ERROR(Service_LDN, "Network isn't initialized, rc={}", rc.raw);
+ }
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(rc);
@@ -508,14 +525,26 @@ public:
return ResultAirplaneModeEnabled;
}
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ ldn_packet_received = room_member->BindOnLdnPacketReceived(
+ [this](const Network::LDNPacket& packet) { OnLDNPacketReceived(packet); });
+ } else {
+ LOG_ERROR(Service_LDN, "Couldn't bind callback!");
+ return ResultAirplaneModeEnabled;
+ }
+
+ lan_discovery.Initialize([&]() { OnEventFired(); });
is_initialized = true;
- // TODO (flTobi): Change this to ResultSuccess when LDN is fully implemented
- return ResultAirplaneModeEnabled;
+ return ResultSuccess;
}
KernelHelpers::ServiceContext service_context;
Kernel::KEvent* state_change_event;
Network::RoomNetwork& room_network;
+ LANDiscovery lan_discovery;
+
+ // Callback identifier for the OnLDNPacketReceived event.
+ Network::RoomMember::CallbackHandle<Network::LDNPacket> ldn_packet_received;
bool is_initialized{};
};
diff --git a/src/core/hle/service/ldn/ldn_types.h b/src/core/hle/service/ldn/ldn_types.h
index 6231e936d..44c2c773b 100644
--- a/src/core/hle/service/ldn/ldn_types.h
+++ b/src/core/hle/service/ldn/ldn_types.h
@@ -31,6 +31,8 @@ enum class NodeStateChange : u8 {
DisconnectAndConnect,
};
+DECLARE_ENUM_FLAG_OPERATORS(NodeStateChange)
+
enum class ScanFilterFlag : u32 {
None = 0,
LocalCommunicationId = 1 << 0,
@@ -100,13 +102,13 @@ enum class AcceptPolicy : u8 {
enum class WifiChannel : s16 {
Default = 0,
- wifi24_1 = 1,
- wifi24_6 = 6,
- wifi24_11 = 11,
- wifi50_36 = 36,
- wifi50_40 = 40,
- wifi50_44 = 44,
- wifi50_48 = 48,
+ Wifi24_1 = 1,
+ Wifi24_6 = 6,
+ Wifi24_11 = 11,
+ Wifi50_36 = 36,
+ Wifi50_40 = 40,
+ Wifi50_44 = 44,
+ Wifi50_48 = 48,
};
enum class LinkLevel : s8 {
@@ -116,6 +118,11 @@ enum class LinkLevel : s8 {
Excellent,
};
+enum class NodeStatus : u8 {
+ Disconnected,
+ Connected,
+};
+
struct NodeLatestUpdate {
NodeStateChange state_change;
INSERT_PADDING_BYTES(0x7); // Unknown
@@ -150,7 +157,7 @@ struct Ssid {
Ssid() = default;
- explicit Ssid(std::string_view data) {
+ constexpr explicit Ssid(std::string_view data) {
length = static_cast<u8>(std::min(data.size(), SsidLengthMax));
data.copy(raw.data(), length);
raw[length] = 0;
@@ -159,19 +166,18 @@ struct Ssid {
std::string GetStringValue() const {
return std::string(raw.data());
}
-};
-static_assert(sizeof(Ssid) == 0x22, "Ssid is an invalid size");
-struct Ipv4Address {
- union {
- u32 raw{};
- std::array<u8, 4> bytes;
- };
+ bool operator==(const Ssid& b) const {
+ return (length == b.length) && (std::memcmp(raw.data(), b.raw.data(), length) == 0);
+ }
- std::string GetStringValue() const {
- return fmt::format("{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]);
+ bool operator!=(const Ssid& b) const {
+ return !operator==(b);
}
};
+static_assert(sizeof(Ssid) == 0x22, "Ssid is an invalid size");
+
+using Ipv4Address = std::array<u8, 4>;
static_assert(sizeof(Ipv4Address) == 0x4, "Ipv4Address is an invalid size");
struct MacAddress {
@@ -181,6 +187,14 @@ struct MacAddress {
};
static_assert(sizeof(MacAddress) == 0x6, "MacAddress is an invalid size");
+struct MACAddressHash {
+ size_t operator()(const MacAddress& address) const {
+ u64 value{};
+ std::memcpy(&value, address.raw.data(), sizeof(address.raw));
+ return value;
+ }
+};
+
struct ScanFilter {
NetworkId network_id;
NetworkType network_type;
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index e0ed3f771..037b86653 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -800,12 +800,12 @@ Result Module::Interface::Flush() {
// Return to the start of the file
if (!amiibo_file.Seek(0)) {
- LOG_ERROR(Service_NFP, "Error writting to file");
+ LOG_ERROR(Service_NFP, "Error writing to file");
return ErrCodes::WriteAmiiboFailed;
}
if (!amiibo_file.Write(encrypted_tag_data)) {
- LOG_ERROR(Service_NFP, "Error writting to file");
+ LOG_ERROR(Service_NFP, "Error writing to file");
return ErrCodes::WriteAmiiboFailed;
}
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index cc679cc81..9e94a462f 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -929,7 +929,7 @@ BSD::BSD(Core::System& system_, const char* name)
proxy_packet_received = room_member->BindOnProxyPacketReceived(
[this](const Network::ProxyPacket& packet) { OnProxyPacketReceived(packet); });
} else {
- LOG_ERROR(Service, "Network isn't initalized");
+ LOG_ERROR(Service, "Network isn't initialized");
}
}
diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp
index cdf38a2a4..447fbffaa 100644
--- a/src/core/internal_network/network.cpp
+++ b/src/core/internal_network/network.cpp
@@ -364,7 +364,7 @@ std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
std::vector<WSAPOLLFD> host_pollfds(pollfds.size());
std::transform(pollfds.begin(), pollfds.end(), host_pollfds.begin(), [](PollFD fd) {
WSAPOLLFD result;
- result.fd = fd.socket->fd;
+ result.fd = fd.socket->GetFD();
result.events = TranslatePollEvents(fd.events);
result.revents = 0;
return result;
@@ -430,12 +430,12 @@ std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() {
return {AcceptResult{}, GetAndLogLastError()};
}
- AcceptResult result;
- result.socket = std::make_unique<Socket>();
- result.socket->fd = new_socket;
-
ASSERT(addrlen == sizeof(sockaddr_in));
- result.sockaddr_in = TranslateToSockAddrIn(addr);
+
+ AcceptResult result{
+ .socket = std::make_unique<Socket>(new_socket),
+ .sockaddr_in = TranslateToSockAddrIn(addr),
+ };
return {std::move(result), Errno::SUCCESS};
}
diff --git a/src/core/internal_network/network_interface.cpp b/src/core/internal_network/network_interface.cpp
index 0f0a66160..057fd3661 100644
--- a/src/core/internal_network/network_interface.cpp
+++ b/src/core/internal_network/network_interface.cpp
@@ -188,7 +188,7 @@ std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
std::optional<NetworkInterface> GetSelectedNetworkInterface() {
const auto& selected_network_interface = Settings::values.network_interface.GetValue();
const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
- if (network_interfaces.size() == 0) {
+ if (network_interfaces.empty()) {
LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces");
return std::nullopt;
}
@@ -206,4 +206,14 @@ std::optional<NetworkInterface> GetSelectedNetworkInterface() {
return *res;
}
+void SelectFirstNetworkInterface() {
+ const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
+
+ if (network_interfaces.empty()) {
+ return;
+ }
+
+ Settings::values.network_interface.SetValue(network_interfaces[0].name);
+}
+
} // namespace Network
diff --git a/src/core/internal_network/network_interface.h b/src/core/internal_network/network_interface.h
index 9b98b6b42..175e61b1f 100644
--- a/src/core/internal_network/network_interface.h
+++ b/src/core/internal_network/network_interface.h
@@ -24,5 +24,6 @@ struct NetworkInterface {
std::vector<NetworkInterface> GetAvailableNetworkInterfaces();
std::optional<NetworkInterface> GetSelectedNetworkInterface();
+void SelectFirstNetworkInterface();
} // namespace Network
diff --git a/src/core/internal_network/socket_proxy.cpp b/src/core/internal_network/socket_proxy.cpp
index 0c746bd82..7d5d37bbc 100644
--- a/src/core/internal_network/socket_proxy.cpp
+++ b/src/core/internal_network/socket_proxy.cpp
@@ -6,6 +6,7 @@
#include "common/assert.h"
#include "common/logging/log.h"
+#include "common/zstd_compression.h"
#include "core/internal_network/network.h"
#include "core/internal_network/network_interface.h"
#include "core/internal_network/socket_proxy.h"
@@ -32,8 +33,11 @@ void ProxySocket::HandleProxyPacket(const ProxyPacket& packet) {
return;
}
+ auto decompressed = packet;
+ decompressed.data = Common::Compression::DecompressDataZSTD(packet.data);
+
std::lock_guard guard(packets_mutex);
- received_packets.push(packet);
+ received_packets.push(decompressed);
}
template <typename T>
@@ -185,6 +189,8 @@ std::pair<s32, Errno> ProxySocket::Send(const std::vector<u8>& message, int flag
void ProxySocket::SendPacket(ProxyPacket& packet) {
if (auto room_member = room_network.GetRoomMember().lock()) {
if (room_member->IsConnected()) {
+ packet.data = Common::Compression::CompressDataZSTDDefault(packet.data.data(),
+ packet.data.size());
room_member->SendProxyPacket(packet);
}
}
diff --git a/src/core/internal_network/sockets.h b/src/core/internal_network/sockets.h
index a70429b19..2e328c645 100644
--- a/src/core/internal_network/sockets.h
+++ b/src/core/internal_network/sockets.h
@@ -32,6 +32,10 @@ public:
std::unique_ptr<SocketBase> socket;
SockAddrIn sockaddr_in;
};
+
+ SocketBase() = default;
+ explicit SocketBase(SOCKET fd_) : fd{fd_} {}
+
virtual ~SocketBase() = default;
virtual SocketBase& operator=(const SocketBase&) = delete;
@@ -89,12 +93,19 @@ public:
virtual void HandleProxyPacket(const ProxyPacket& packet) = 0;
+ [[nodiscard]] SOCKET GetFD() const {
+ return fd;
+ }
+
+protected:
SOCKET fd = INVALID_SOCKET;
};
class Socket : public SocketBase {
public:
Socket() = default;
+ explicit Socket(SOCKET fd_) : SocketBase{fd_} {}
+
~Socket() override;
Socket(const Socket&) = delete;
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 104d16efa..f24474ed8 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -244,6 +244,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileSys::V
std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile file,
u64 program_id, std::size_t program_index) {
+ if (!file) {
+ return nullptr;
+ }
+
FileType type = IdentifyFile(file);
const FileType filename_type = GuessFromFilename(file->GetName());
diff --git a/src/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp
index 7b6deba41..359891883 100644
--- a/src/dedicated_room/yuzu_room.cpp
+++ b/src/dedicated_room/yuzu_room.cpp
@@ -76,7 +76,18 @@ static constexpr char BanListMagic[] = "YuzuRoom-BanList-1";
static constexpr char token_delimiter{':'};
static void PadToken(std::string& token) {
- while (token.size() % 4 != 0) {
+ std::size_t outlen = 0;
+
+ std::array<unsigned char, 512> output{};
+ std::array<unsigned char, 2048> roundtrip{};
+ for (size_t i = 0; i < 3; i++) {
+ mbedtls_base64_decode(output.data(), output.size(), &outlen,
+ reinterpret_cast<const unsigned char*>(token.c_str()),
+ token.length());
+ mbedtls_base64_encode(roundtrip.data(), roundtrip.size(), &outlen, output.data(), outlen);
+ if (memcmp(roundtrip.data(), token.data(), token.size()) == 0) {
+ break;
+ }
token.push_back('=');
}
}
diff --git a/src/network/network.cpp b/src/network/network.cpp
index 0841e4134..6652a186b 100644
--- a/src/network/network.cpp
+++ b/src/network/network.cpp
@@ -15,7 +15,7 @@ RoomNetwork::RoomNetwork() {
bool RoomNetwork::Init() {
if (enet_initialize() != 0) {
- LOG_ERROR(Network, "Error initalizing ENet");
+ LOG_ERROR(Network, "Error initializing ENet");
return false;
}
m_room = std::make_shared<Room>();
diff --git a/src/network/room.cpp b/src/network/room.cpp
index 8c63b255b..dc5dbce7f 100644
--- a/src/network/room.cpp
+++ b/src/network/room.cpp
@@ -212,6 +212,12 @@ public:
void HandleProxyPacket(const ENetEvent* event);
/**
+ * Broadcasts this packet to all members except the sender.
+ * @param event The ENet event containing the data
+ */
+ void HandleLdnPacket(const ENetEvent* event);
+
+ /**
* Extracts a chat entry from a received ENet packet and adds it to the chat queue.
* @param event The ENet event that was received.
*/
@@ -247,6 +253,9 @@ void Room::RoomImpl::ServerLoop() {
case IdProxyPacket:
HandleProxyPacket(&event);
break;
+ case IdLdnPacket:
+ HandleLdnPacket(&event);
+ break;
case IdChatMessage:
HandleChatPacket(&event);
break;
@@ -861,6 +870,60 @@ void Room::RoomImpl::HandleProxyPacket(const ENetEvent* event) {
enet_host_flush(server);
}
+void Room::RoomImpl::HandleLdnPacket(const ENetEvent* event) {
+ Packet in_packet;
+ in_packet.Append(event->packet->data, event->packet->dataLength);
+
+ in_packet.IgnoreBytes(sizeof(u8)); // Message type
+
+ in_packet.IgnoreBytes(sizeof(u8)); // LAN packet type
+ in_packet.IgnoreBytes(sizeof(IPv4Address)); // Local IP
+
+ IPv4Address remote_ip;
+ in_packet.Read(remote_ip); // Remote IP
+
+ bool broadcast;
+ in_packet.Read(broadcast); // Broadcast
+
+ Packet out_packet;
+ out_packet.Append(event->packet->data, event->packet->dataLength);
+ ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(),
+ ENET_PACKET_FLAG_RELIABLE);
+
+ const auto& destination_address = remote_ip;
+ if (broadcast) { // Send the data to everyone except the sender
+ std::lock_guard lock(member_mutex);
+ bool sent_packet = false;
+ for (const auto& member : members) {
+ if (member.peer != event->peer) {
+ sent_packet = true;
+ enet_peer_send(member.peer, 0, enet_packet);
+ }
+ }
+
+ if (!sent_packet) {
+ enet_packet_destroy(enet_packet);
+ }
+ } else {
+ std::lock_guard lock(member_mutex);
+ auto member = std::find_if(members.begin(), members.end(),
+ [destination_address](const Member& member_entry) -> bool {
+ return member_entry.fake_ip == destination_address;
+ });
+ if (member != members.end()) {
+ enet_peer_send(member->peer, 0, enet_packet);
+ } else {
+ LOG_ERROR(Network,
+ "Attempting to send to unknown IP address: "
+ "{}.{}.{}.{}",
+ destination_address[0], destination_address[1], destination_address[2],
+ destination_address[3]);
+ enet_packet_destroy(enet_packet);
+ }
+ }
+ enet_host_flush(server);
+}
+
void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) {
Packet in_packet;
in_packet.Append(event->packet->data, event->packet->dataLength);
diff --git a/src/network/room.h b/src/network/room.h
index c2a4b1a70..edbd3ecfb 100644
--- a/src/network/room.h
+++ b/src/network/room.h
@@ -40,6 +40,7 @@ enum RoomMessageTypes : u8 {
IdRoomInformation,
IdSetGameInfo,
IdProxyPacket,
+ IdLdnPacket,
IdChatMessage,
IdNameCollision,
IdIpCollision,
diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp
index 06818af78..b94cb24ad 100644
--- a/src/network/room_member.cpp
+++ b/src/network/room_member.cpp
@@ -58,6 +58,7 @@ public:
private:
CallbackSet<ProxyPacket> callback_set_proxy_packet;
+ CallbackSet<LDNPacket> callback_set_ldn_packet;
CallbackSet<ChatEntry> callback_set_chat_messages;
CallbackSet<StatusMessageEntry> callback_set_status_messages;
CallbackSet<RoomInformation> callback_set_room_information;
@@ -108,6 +109,12 @@ public:
void HandleProxyPackets(const ENetEvent* event);
/**
+ * Extracts an LdnPacket from a received ENet packet.
+ * @param event The ENet event that was received.
+ */
+ void HandleLdnPackets(const ENetEvent* event);
+
+ /**
* Extracts a chat entry from a received ENet packet and adds it to the chat queue.
* @param event The ENet event that was received.
*/
@@ -166,6 +173,9 @@ void RoomMember::RoomMemberImpl::MemberLoop() {
case IdProxyPacket:
HandleProxyPackets(&event);
break;
+ case IdLdnPacket:
+ HandleLdnPackets(&event);
+ break;
case IdChatMessage:
HandleChatPacket(&event);
break;
@@ -372,6 +382,27 @@ void RoomMember::RoomMemberImpl::HandleProxyPackets(const ENetEvent* event) {
Invoke<ProxyPacket>(proxy_packet);
}
+void RoomMember::RoomMemberImpl::HandleLdnPackets(const ENetEvent* event) {
+ LDNPacket ldn_packet{};
+ Packet packet;
+ packet.Append(event->packet->data, event->packet->dataLength);
+
+ // Ignore the first byte, which is the message id.
+ packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
+
+ u8 packet_type;
+ packet.Read(packet_type);
+ ldn_packet.type = static_cast<LDNPacketType>(packet_type);
+
+ packet.Read(ldn_packet.local_ip);
+ packet.Read(ldn_packet.remote_ip);
+ packet.Read(ldn_packet.broadcast);
+
+ packet.Read(ldn_packet.data);
+
+ Invoke<LDNPacket>(ldn_packet);
+}
+
void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) {
Packet packet;
packet.Append(event->packet->data, event->packet->dataLength);
@@ -450,6 +481,11 @@ RoomMember::RoomMemberImpl::CallbackSet<ProxyPacket>& RoomMember::RoomMemberImpl
}
template <>
+RoomMember::RoomMemberImpl::CallbackSet<LDNPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() {
+ return callback_set_ldn_packet;
+}
+
+template <>
RoomMember::RoomMemberImpl::CallbackSet<RoomMember::State>&
RoomMember::RoomMemberImpl::Callbacks::Get() {
return callback_set_state;
@@ -607,6 +643,21 @@ void RoomMember::SendProxyPacket(const ProxyPacket& proxy_packet) {
room_member_impl->Send(std::move(packet));
}
+void RoomMember::SendLdnPacket(const LDNPacket& ldn_packet) {
+ Packet packet;
+ packet.Write(static_cast<u8>(IdLdnPacket));
+
+ packet.Write(static_cast<u8>(ldn_packet.type));
+
+ packet.Write(ldn_packet.local_ip);
+ packet.Write(ldn_packet.remote_ip);
+ packet.Write(ldn_packet.broadcast);
+
+ packet.Write(ldn_packet.data);
+
+ room_member_impl->Send(std::move(packet));
+}
+
void RoomMember::SendChatMessage(const std::string& message) {
Packet packet;
packet.Write(static_cast<u8>(IdChatMessage));
@@ -663,6 +714,11 @@ RoomMember::CallbackHandle<ProxyPacket> RoomMember::BindOnProxyPacketReceived(
return room_member_impl->Bind(callback);
}
+RoomMember::CallbackHandle<LDNPacket> RoomMember::BindOnLdnPacketReceived(
+ std::function<void(const LDNPacket&)> callback) {
+ return room_member_impl->Bind(std::move(callback));
+}
+
RoomMember::CallbackHandle<RoomInformation> RoomMember::BindOnRoomInformationChanged(
std::function<void(const RoomInformation&)> callback) {
return room_member_impl->Bind(callback);
@@ -699,6 +755,7 @@ void RoomMember::Leave() {
}
template void RoomMember::Unbind(CallbackHandle<ProxyPacket>);
+template void RoomMember::Unbind(CallbackHandle<LDNPacket>);
template void RoomMember::Unbind(CallbackHandle<RoomMember::State>);
template void RoomMember::Unbind(CallbackHandle<RoomMember::Error>);
template void RoomMember::Unbind(CallbackHandle<RoomInformation>);
diff --git a/src/network/room_member.h b/src/network/room_member.h
index f578f7f6a..0d6417294 100644
--- a/src/network/room_member.h
+++ b/src/network/room_member.h
@@ -17,7 +17,24 @@ namespace Network {
using AnnounceMultiplayerRoom::GameInfo;
using AnnounceMultiplayerRoom::RoomInformation;
-/// Information about the received WiFi packets.
+enum class LDNPacketType : u8 {
+ Scan,
+ ScanResp,
+ Connect,
+ SyncNetwork,
+ Disconnect,
+ DestroyNetwork,
+};
+
+struct LDNPacket {
+ LDNPacketType type;
+ IPv4Address local_ip;
+ IPv4Address remote_ip;
+ bool broadcast;
+ std::vector<u8> data;
+};
+
+/// Information about the received proxy packets.
struct ProxyPacket {
SockAddrIn local_endpoint;
SockAddrIn remote_endpoint;
@@ -152,6 +169,12 @@ public:
void SendProxyPacket(const ProxyPacket& packet);
/**
+ * Sends an LDN packet to the room.
+ * @param packet The WiFi packet to send.
+ */
+ void SendLdnPacket(const LDNPacket& packet);
+
+ /**
* Sends a chat message to the room.
* @param message The contents of the message.
*/
@@ -205,6 +228,16 @@ public:
std::function<void(const ProxyPacket&)> callback);
/**
+ * Binds a function to an event that will be triggered every time an LDNPacket is received.
+ * The function wil be called everytime the event is triggered.
+ * The callback function must not bind or unbind a function. Doing so will cause a deadlock
+ * @param callback The function to call
+ * @return A handle used for removing the function from the registered list
+ */
+ CallbackHandle<LDNPacket> BindOnLdnPacketReceived(
+ std::function<void(const LDNPacket&)> callback);
+
+ /**
* Binds a function to an event that will be triggered every time the RoomInformation changes.
* The function wil be called every time the event is triggered.
* The callback function must not bind or unbind a function. Doing so will cause a deadlock
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm.cpp b/src/shader_recompiler/backend/glasm/emit_glasm.cpp
index 97a6b383b..01f9abc71 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm.cpp
@@ -175,7 +175,7 @@ bool IsReference(IR::Inst& inst) {
}
void PrecolorInst(IR::Inst& phi) {
- // Insert phi moves before references to avoid overwritting other phis
+ // Insert phi moves before references to avoid overwriting other phis
const size_t num_args{phi.NumArgs()};
for (size_t i = 0; i < num_args; ++i) {
IR::Block& phi_block{*phi.PhiBlock(i)};
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl.cpp b/src/shader_recompiler/backend/glsl/emit_glsl.cpp
index 76c18e488..e8a4390f6 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl.cpp
@@ -101,7 +101,7 @@ bool IsReference(IR::Inst& inst) {
}
void PrecolorInst(IR::Inst& phi) {
- // Insert phi moves before references to avoid overwritting other phis
+ // Insert phi moves before references to avoid overwriting other phis
const size_t num_args{phi.NumArgs()};
for (size_t i = 0; i < num_args; ++i) {
IR::Block& phi_block{*phi.PhiBlock(i)};
diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp
index 3441a5fe5..d608678a3 100644
--- a/src/video_core/host_shaders/astc_decoder.comp
+++ b/src/video_core/host_shaders/astc_decoder.comp
@@ -1065,7 +1065,7 @@ TexelWeightParams DecodeBlockInfo() {
void FillError(ivec3 coord) {
for (uint j = 0; j < block_dims.y; j++) {
for (uint i = 0; i < block_dims.x; i++) {
- imageStore(dest_image, coord + ivec3(i, j, 0), vec4(1.0, 1.0, 0.0, 1.0));
+ imageStore(dest_image, coord + ivec3(i, j, 0), vec4(0.0, 0.0, 0.0, 0.0));
}
}
}
diff --git a/src/video_core/macro/macro_hle.cpp b/src/video_core/macro/macro_hle.cpp
index 58382755b..cabe8dcbf 100644
--- a/src/video_core/macro/macro_hle.cpp
+++ b/src/video_core/macro/macro_hle.cpp
@@ -3,6 +3,8 @@
#include <array>
#include <vector>
+#include "common/scope_exit.h"
+#include "video_core/dirty_flags.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/macro/macro.h"
#include "video_core/macro/macro_hle.h"
@@ -58,6 +60,7 @@ void HLE_0217920100488FF7(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&
maxwell3d.regs.index_array.first = parameters[3];
maxwell3d.regs.reg_array[0x446] = element_base; // vertex id base?
maxwell3d.regs.index_array.count = parameters[1];
+ maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
maxwell3d.regs.vb_element_base = element_base;
maxwell3d.regs.vb_base_instance = base_instance;
maxwell3d.mme_draw.instance_count = instance_count;
@@ -80,10 +83,67 @@ void HLE_0217920100488FF7(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&
maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
}
-constexpr std::array<std::pair<u64, HLEFunction>, 3> hle_funcs{{
+// Multidraw Indirect
+void HLE_3F5E74B9C9A50164(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& parameters) {
+ SCOPE_EXIT({
+ // Clean everything.
+ maxwell3d.regs.reg_array[0x446] = 0x0; // vertex id base?
+ maxwell3d.regs.index_array.count = 0;
+ maxwell3d.regs.vb_element_base = 0x0;
+ maxwell3d.regs.vb_base_instance = 0x0;
+ maxwell3d.mme_draw.instance_count = 0;
+ maxwell3d.CallMethodFromMME(0x8e3, 0x640);
+ maxwell3d.CallMethodFromMME(0x8e4, 0x0);
+ maxwell3d.CallMethodFromMME(0x8e5, 0x0);
+ maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
+ maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
+ });
+ const u32 start_indirect = parameters[0];
+ const u32 end_indirect = parameters[1];
+ if (start_indirect >= end_indirect) {
+ // Nothing to do.
+ return;
+ }
+ const auto topology =
+ static_cast<Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology>(parameters[2]);
+ maxwell3d.regs.draw.topology.Assign(topology);
+ const u32 padding = parameters[3];
+ const std::size_t max_draws = parameters[4];
+
+ const u32 indirect_words = 5 + padding;
+ const std::size_t first_draw = start_indirect;
+ const std::size_t effective_draws = end_indirect - start_indirect;
+ const std::size_t last_draw = start_indirect + std::min(effective_draws, max_draws);
+
+ for (std::size_t index = first_draw; index < last_draw; index++) {
+ const std::size_t base = index * indirect_words + 5;
+ const u32 num_vertices = parameters[base];
+ const u32 instance_count = parameters[base + 1];
+ const u32 first_index = parameters[base + 2];
+ const u32 base_vertex = parameters[base + 3];
+ const u32 base_instance = parameters[base + 4];
+ maxwell3d.regs.index_array.first = first_index;
+ maxwell3d.regs.reg_array[0x446] = base_vertex;
+ maxwell3d.regs.index_array.count = num_vertices;
+ maxwell3d.regs.vb_element_base = base_vertex;
+ maxwell3d.regs.vb_base_instance = base_instance;
+ maxwell3d.mme_draw.instance_count = instance_count;
+ maxwell3d.CallMethodFromMME(0x8e3, 0x640);
+ maxwell3d.CallMethodFromMME(0x8e4, base_vertex);
+ maxwell3d.CallMethodFromMME(0x8e5, base_instance);
+ maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
+ if (maxwell3d.ShouldExecute()) {
+ maxwell3d.Rasterizer().Draw(true, true);
+ }
+ maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
+ }
+}
+
+constexpr std::array<std::pair<u64, HLEFunction>, 4> hle_funcs{{
{0x771BB18C62444DA0, &HLE_771BB18C62444DA0},
{0x0D61FC9FAAC9FCAD, &HLE_0D61FC9FAAC9FCAD},
{0x0217920100488FF7, &HLE_0217920100488FF7},
+ {0x3F5E74B9C9A50164, &HLE_3F5E74B9C9A50164},
}};
class HLEMacroImpl final : public CachedMacro {
@@ -99,6 +159,7 @@ private:
Engines::Maxwell3D& maxwell3d;
HLEFunction func;
};
+
} // Anonymous namespace
HLEMacro::HLEMacro(Engines::Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} {}
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index 32450ee1d..08f4d69ab 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -168,7 +168,7 @@ void BufferCacheRuntime::BindIndexBuffer(Buffer& buffer, u32 offset, u32 size) {
if (has_unified_vertex_buffers) {
buffer.MakeResident(GL_READ_ONLY);
glBufferAddressRangeNV(GL_ELEMENT_ARRAY_ADDRESS_NV, 0, buffer.HostGpuAddr() + offset,
- static_cast<GLsizeiptr>(size));
+ static_cast<GLsizeiptr>(Common::AlignUp(size, 4)));
} else {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.Handle());
index_buffer_offset = offset;
diff --git a/src/video_core/textures/astc.cpp b/src/video_core/textures/astc.cpp
index b159494c5..e3742ddf5 100644
--- a/src/video_core/textures/astc.cpp
+++ b/src/video_core/textures/astc.cpp
@@ -1413,7 +1413,7 @@ static void FillVoidExtentLDR(InputBitStream& strm, std::span<u32> outBuf, u32 b
static void FillError(std::span<u32> outBuf, u32 blockWidth, u32 blockHeight) {
for (u32 j = 0; j < blockHeight; j++) {
for (u32 i = 0; i < blockWidth; i++) {
- outBuf[j * blockWidth + i] = 0xFFFF00FF;
+ outBuf[j * blockWidth + i] = 0x00000000;
}
}
}
diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp
index 1d8072243..12efdc216 100644
--- a/src/yuzu/applets/qt_controller.cpp
+++ b/src/yuzu/applets/qt_controller.cpp
@@ -291,7 +291,7 @@ bool QtControllerSelectorDialog::CheckIfParametersMet() {
// Here, we check and validate the current configuration against all applicable parameters.
const auto num_connected_players = static_cast<int>(
std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
- [this](const QGroupBox* player) { return player->isChecked(); }));
+ [](const QGroupBox* player) { return player->isChecked(); }));
const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index a4ed68422..195074bf2 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -546,6 +546,7 @@ void Config::ReadDebuggingValues() {
ReadBasicSetting(Settings::values.use_auto_stub);
ReadBasicSetting(Settings::values.enable_all_controllers);
ReadBasicSetting(Settings::values.create_crash_dumps);
+ ReadBasicSetting(Settings::values.perform_vulkan_check);
qt_config->endGroup();
}
@@ -1162,6 +1163,7 @@ void Config::SaveDebuggingValues() {
WriteBasicSetting(Settings::values.disable_macro_jit);
WriteBasicSetting(Settings::values.enable_all_controllers);
WriteBasicSetting(Settings::values.create_crash_dumps);
+ WriteBasicSetting(Settings::values.perform_vulkan_check);
qt_config->endGroup();
}
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index 622808e94..dacc75a20 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -77,6 +77,7 @@ void ConfigureDebug::SetConfiguration() {
ui->disable_loop_safety_checks->setChecked(
Settings::values.disable_shader_loop_safety_checks.GetValue());
ui->extended_logging->setChecked(Settings::values.extended_logging.GetValue());
+ ui->perform_vulkan_check->setChecked(Settings::values.perform_vulkan_check.GetValue());
#ifdef YUZU_USE_QT_WEB_ENGINE
ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue());
@@ -117,6 +118,7 @@ void ConfigureDebug::ApplyConfiguration() {
ui->disable_loop_safety_checks->isChecked();
Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked();
Settings::values.extended_logging = ui->extended_logging->isChecked();
+ Settings::values.perform_vulkan_check = ui->perform_vulkan_check->isChecked();
UISettings::values.disable_web_applet = ui->disable_web_applet->isChecked();
Debugger::ToggleConsole();
Common::Log::Filter filter;
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 314d47af5..102c8c66c 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -313,6 +313,16 @@
</property>
</widget>
</item>
+ <item row="3" column="0">
+ <widget class="QCheckBox" name="perform_vulkan_check">
+ <property name="toolTip">
+ <string>Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu.</string>
+ </property>
+ <property name="text">
+ <string>Perform Startup Vulkan Check</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index cb55472c9..1db374d4a 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -163,10 +163,9 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
[this, input_subsystem, &hid_core] {
CallConfigureDialog<ConfigureRingController>(*this, input_subsystem, hid_core);
});
- connect(advanced, &ConfigureInputAdvanced::CallCameraDialog,
- [this, input_subsystem, &hid_core] {
- CallConfigureDialog<ConfigureCamera>(*this, input_subsystem);
- });
+ connect(advanced, &ConfigureInputAdvanced::CallCameraDialog, [this, input_subsystem] {
+ CallConfigureDialog<ConfigureCamera>(*this, input_subsystem);
+ });
connect(ui->vibrationButton, &QPushButton::clicked,
[this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); });
diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp
index 807afbeb2..9bb69cab1 100644
--- a/src/yuzu/configuration/input_profiles.cpp
+++ b/src/yuzu/configuration/input_profiles.cpp
@@ -67,6 +67,8 @@ std::vector<std::string> InputProfiles::GetInputProfileNames() {
profile_names.push_back(profile_name);
}
+ std::stable_sort(profile_names.begin(), profile_names.end());
+
return profile_names;
}
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 3c1bd19db..612b8dbb8 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -899,8 +899,8 @@ void GMainWindow::InitializeWidgets() {
}
// TODO (flTobi): Add the widget when multiplayer is fully implemented
- // statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0);
- // statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0);
+ statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0);
+ statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0);
tas_label = new QLabel();
tas_label->setObjectName(QStringLiteral("TASlabel"));
@@ -1299,6 +1299,7 @@ void GMainWindow::ConnectMenuEvents() {
&MultiplayerState::OnDirectConnectToRoom);
connect(ui->action_Show_Room, &QAction::triggered, multiplayer_state,
&MultiplayerState::OnOpenNetworkRoom);
+ connect(multiplayer_state, &MultiplayerState::SaveConfig, this, &GMainWindow::OnSaveConfig);
// Tools
connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this,
@@ -1339,6 +1340,8 @@ void GMainWindow::UpdateMenuState() {
} else {
ui->action_Pause->setText(tr("&Pause"));
}
+
+ multiplayer_state->UpdateNotificationStatus();
}
void GMainWindow::OnDisplayTitleBars(bool show) {
@@ -2000,7 +2003,7 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src
}
void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type) {
- const QString entry_type = [this, type] {
+ const QString entry_type = [type] {
switch (type) {
case InstalledEntryType::Game:
return tr("Contents");
@@ -2097,7 +2100,7 @@ void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type)
void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
const std::string& game_path) {
- const QString question = [this, target] {
+ const QString question = [target] {
switch (target) {
case GameListRemoveTarget::GlShaderCache:
return tr("Delete OpenGL Transferable Shader Cache?");
@@ -2770,6 +2773,11 @@ void GMainWindow::OnExit() {
OnStopGame();
}
+void GMainWindow::OnSaveConfig() {
+ system->ApplySettings();
+ config->Save();
+}
+
void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_text) {
OverlayDialog dialog(render_window, *system, error_code, error_text, QString{}, tr("OK"),
Qt::AlignLeft | Qt::AlignVCenter);
@@ -4086,7 +4094,8 @@ int main(int argc, char* argv[]) {
}
#endif
- if (StartupChecks(argv[0], &has_broken_vulkan)) {
+ if (StartupChecks(argv[0], &has_broken_vulkan,
+ Settings::values.perform_vulkan_check.GetValue())) {
return 0;
}
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 716aef063..f7aa8e417 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -169,6 +169,7 @@ public slots:
void OnLoadComplete();
void OnExecuteProgram(std::size_t program_index);
void OnExit();
+ void OnSaveConfig();
void ControllerSelectorReconfigureControllers(
const Core::Frontend::ControllerParameters& parameters);
void SoftwareKeyboardInitialize(
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index cdf31b417..74d49dbd4 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -120,6 +120,20 @@
<addaction name="menu_Reset_Window_Size"/>
<addaction name="menu_View_Debugging"/>
</widget>
+ <widget class="QMenu" name="menu_Multiplayer">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="title">
+ <string>&amp;Multiplayer</string>
+ </property>
+ <addaction name="action_View_Lobby"/>
+ <addaction name="action_Start_Room"/>
+ <addaction name="action_Connect_To_Room"/>
+ <addaction name="separator"/>
+ <addaction name="action_Show_Room"/>
+ <addaction name="action_Leave_Room"/>
+ </widget>
<widget class="QMenu" name="menu_Tools">
<property name="title">
<string>&amp;Tools</string>
@@ -251,7 +265,7 @@
<bool>true</bool>
</property>
<property name="text">
- <string>Browse Public Game Lobby</string>
+ <string>&amp;Browse Public Game Lobby</string>
</property>
</action>
<action name="action_Start_Room">
@@ -259,7 +273,7 @@
<bool>true</bool>
</property>
<property name="text">
- <string>Create Room</string>
+ <string>&amp;Create Room</string>
</property>
</action>
<action name="action_Leave_Room">
@@ -267,12 +281,12 @@
<bool>false</bool>
</property>
<property name="text">
- <string>Leave Room</string>
+ <string>&amp;Leave Room</string>
</property>
</action>
<action name="action_Connect_To_Room">
<property name="text">
- <string>Direct Connect to Room</string>
+ <string>&amp;Direct Connect to Room</string>
</property>
</action>
<action name="action_Show_Room">
@@ -280,7 +294,7 @@
<bool>false</bool>
</property>
<property name="text">
- <string>Show Current Room</string>
+ <string>&amp;Show Current Room</string>
</property>
</action>
<action name="action_Fullscreen">
diff --git a/src/yuzu/multiplayer/chat_room.cpp b/src/yuzu/multiplayer/chat_room.cpp
index 9e672f82e..dec9696c1 100644
--- a/src/yuzu/multiplayer/chat_room.cpp
+++ b/src/yuzu/multiplayer/chat_room.cpp
@@ -61,7 +61,10 @@ public:
/// Format the message using the players color
QString GetPlayerChatMessage(u16 player) const {
- auto color = player_color[player % 16];
+ const bool is_dark_theme = QIcon::themeName().contains(QStringLiteral("dark")) ||
+ QIcon::themeName().contains(QStringLiteral("midnight"));
+ auto color =
+ is_dark_theme ? player_color_dark[player % 16] : player_color_default[player % 16];
QString name;
if (username.isEmpty() || username == nickname) {
name = nickname;
@@ -84,9 +87,12 @@ public:
}
private:
- static constexpr std::array<const char*, 16> player_color = {
+ static constexpr std::array<const char*, 16> player_color_default = {
{"#0000FF", "#FF0000", "#8A2BE2", "#FF69B4", "#1E90FF", "#008000", "#00FF7F", "#B22222",
- "#DAA520", "#FF4500", "#2E8B57", "#5F9EA0", "#D2691E", "#9ACD32", "#FF7F50", "FFFF00"}};
+ "#DAA520", "#FF4500", "#2E8B57", "#5F9EA0", "#D2691E", "#9ACD32", "#FF7F50", "#FFFF00"}};
+ static constexpr std::array<const char*, 16> player_color_dark = {
+ {"#559AD1", "#4EC9A8", "#D69D85", "#C6C923", "#B975B5", "#D81F1F", "#7EAE39", "#4F8733",
+ "#F7CD8A", "#6FCACF", "#CE4897", "#8A2BE2", "#D2691E", "#9ACD32", "#FF7F50", "#152ccd"}};
static constexpr char ping_color[] = "#FFFF00";
QString timestamp;
diff --git a/src/yuzu/multiplayer/client_room.cpp b/src/yuzu/multiplayer/client_room.cpp
index b34a8d004..caf34a414 100644
--- a/src/yuzu/multiplayer/client_room.cpp
+++ b/src/yuzu/multiplayer/client_room.cpp
@@ -97,8 +97,9 @@ void ClientRoomWindow::UpdateView() {
auto memberlist = member->GetMemberInformation();
ui->chat->SetPlayerList(memberlist);
const auto information = member->GetRoomInformation();
- setWindowTitle(QString(tr("%1 (%2/%3 members) - connected"))
+ setWindowTitle(QString(tr("%1 - %2 (%3/%4 members) - connected"))
.arg(QString::fromStdString(information.name))
+ .arg(QString::fromStdString(information.preferred_game.name))
.arg(memberlist.size())
.arg(information.member_slots));
ui->description->setText(QString::fromStdString(information.description));
diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp
index 017063074..10bf0a4fb 100644
--- a/src/yuzu/multiplayer/direct_connect.cpp
+++ b/src/yuzu/multiplayer/direct_connect.cpp
@@ -106,6 +106,8 @@ void DirectConnectWindow::Connect() {
UISettings::values.multiplayer_port = UISettings::values.multiplayer_port.GetDefault();
}
+ emit SaveConfig();
+
// attempt to connect in a different thread
QFuture<void> f = QtConcurrent::run([&] {
if (auto room_member = room_network.GetRoomMember().lock()) {
diff --git a/src/yuzu/multiplayer/direct_connect.h b/src/yuzu/multiplayer/direct_connect.h
index e39dd1e0d..b8f66cfb2 100644
--- a/src/yuzu/multiplayer/direct_connect.h
+++ b/src/yuzu/multiplayer/direct_connect.h
@@ -31,6 +31,7 @@ signals:
* connections that it might have.
*/
void Closed();
+ void SaveConfig();
private slots:
void OnConnection();
diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp
index 0c6adfd04..a8faa5b24 100644
--- a/src/yuzu/multiplayer/host_room.cpp
+++ b/src/yuzu/multiplayer/host_room.cpp
@@ -232,6 +232,7 @@ void HostRoomWindow::Host() {
}
UISettings::values.multiplayer_room_description = ui->room_description->toPlainText();
ui->host->setEnabled(true);
+ emit SaveConfig();
close();
}
}
diff --git a/src/yuzu/multiplayer/host_room.h b/src/yuzu/multiplayer/host_room.h
index 034cb2eef..ae816e2e0 100644
--- a/src/yuzu/multiplayer/host_room.h
+++ b/src/yuzu/multiplayer/host_room.h
@@ -46,6 +46,9 @@ public:
void UpdateGameList(QStandardItemModel* list);
void RetranslateUi();
+signals:
+ void SaveConfig();
+
private:
void Host();
std::unique_ptr<Network::VerifyUser::Backend> CreateVerifyBackend(bool use_validation) const;
diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp
index 107d40547..08c275696 100644
--- a/src/yuzu/multiplayer/lobby.cpp
+++ b/src/yuzu/multiplayer/lobby.cpp
@@ -7,6 +7,7 @@
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
+#include "core/hle/service/acc/profile_manager.h"
#include "core/internal_network/network_interface.h"
#include "network/network.h"
#include "ui_lobby.h"
@@ -26,9 +27,9 @@
Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
std::shared_ptr<Core::AnnounceMultiplayerSession> session, Core::System& system_)
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
- ui(std::make_unique<Ui::Lobby>()),
- announce_multiplayer_session(session), system{system_}, room_network{
- system.GetRoomNetwork()} {
+ ui(std::make_unique<Ui::Lobby>()), announce_multiplayer_session(session),
+ profile_manager(std::make_unique<Service::Account::ProfileManager>()), system{system_},
+ room_network{system.GetRoomNetwork()} {
ui->setupUi(this);
// setup the watcher for background connections
@@ -60,9 +61,17 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
ui->nickname->setValidator(validation.GetNickname());
ui->nickname->setText(UISettings::values.multiplayer_nickname.GetValue());
- if (ui->nickname->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) {
- // Use yuzu Web Service user name as nickname by default
- ui->nickname->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
+
+ // Try find the best nickname by default
+ if (ui->nickname->text().isEmpty() || ui->nickname->text() == QStringLiteral("yuzu")) {
+ if (!Settings::values.yuzu_username.GetValue().empty()) {
+ ui->nickname->setText(
+ QString::fromStdString(Settings::values.yuzu_username.GetValue()));
+ } else if (!GetProfileUsername().empty()) {
+ ui->nickname->setText(QString::fromStdString(GetProfileUsername()));
+ } else {
+ ui->nickname->setText(QStringLiteral("yuzu"));
+ }
}
// UI Buttons
@@ -76,12 +85,6 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
// Actions
connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this,
&Lobby::OnRefreshLobby);
-
- // manually start a refresh when the window is opening
- // TODO(jroweboy): if this refresh is slow for people with bad internet, then don't do it as
- // part of the constructor, but offload the refresh until after the window shown. perhaps emit a
- // refreshroomlist signal from places that open the lobby
- RefreshLobby();
}
Lobby::~Lobby() = default;
@@ -96,6 +99,7 @@ void Lobby::UpdateGameList(QStandardItemModel* list) {
}
if (proxy)
proxy->UpdateGameList(game_list);
+ ui->room_list->sortByColumn(Column::GAME_NAME, Qt::AscendingOrder);
}
void Lobby::RetranslateUi() {
@@ -117,6 +121,11 @@ void Lobby::OnExpandRoom(const QModelIndex& index) {
void Lobby::OnJoinRoom(const QModelIndex& source) {
if (!Network::GetSelectedNetworkInterface()) {
+ LOG_INFO(WebService, "Automatically selected network interface for room network.");
+ Network::SelectFirstNetworkInterface();
+ }
+
+ if (!Network::GetSelectedNetworkInterface()) {
NetworkMessage::ErrorManager::ShowError(
NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED);
return;
@@ -197,16 +206,16 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
proxy->data(connection_index, LobbyItemHost::HostIPRole).toString();
UISettings::values.multiplayer_port =
proxy->data(connection_index, LobbyItemHost::HostPortRole).toInt();
+ emit SaveConfig();
}
void Lobby::ResetModel() {
model->clear();
model->insertColumns(0, Column::TOTAL);
- model->setHeaderData(Column::EXPAND, Qt::Horizontal, QString(), Qt::DisplayRole);
+ model->setHeaderData(Column::MEMBER, Qt::Horizontal, tr("Players"), Qt::DisplayRole);
model->setHeaderData(Column::ROOM_NAME, Qt::Horizontal, tr("Room Name"), Qt::DisplayRole);
model->setHeaderData(Column::GAME_NAME, Qt::Horizontal, tr("Preferred Game"), Qt::DisplayRole);
model->setHeaderData(Column::HOST, Qt::Horizontal, tr("Host"), Qt::DisplayRole);
- model->setHeaderData(Column::MEMBER, Qt::Horizontal, tr("Players"), Qt::DisplayRole);
}
void Lobby::RefreshLobby() {
@@ -229,6 +238,7 @@ void Lobby::OnRefreshLobby() {
for (int r = 0; r < game_list->rowCount(); ++r) {
auto index = game_list->index(r, 0);
auto game_id = game_list->data(index, GameListItemPath::ProgramIdRole).toULongLong();
+
if (game_id != 0 && room.information.preferred_game.id == game_id) {
smdh_icon = game_list->data(index, Qt::DecorationRole).value<QPixmap>();
}
@@ -243,17 +253,16 @@ void Lobby::OnRefreshLobby() {
members.append(var);
}
- auto first_item = new LobbyItem();
+ auto first_item = new LobbyItemGame(
+ room.information.preferred_game.id,
+ QString::fromStdString(room.information.preferred_game.name), smdh_icon);
auto row = QList<QStandardItem*>({
first_item,
new LobbyItemName(room.has_password, QString::fromStdString(room.information.name)),
- new LobbyItemGame(room.information.preferred_game.id,
- QString::fromStdString(room.information.preferred_game.name),
- smdh_icon),
+ new LobbyItemMemberList(members, room.information.member_slots),
new LobbyItemHost(QString::fromStdString(room.information.host_username),
QString::fromStdString(room.ip), room.information.port,
QString::fromStdString(room.verify_uid)),
- new LobbyItemMemberList(members, room.information.member_slots),
});
model->appendRow(row);
// To make the rows expandable, add the member data as a child of the first column of the
@@ -283,6 +292,26 @@ void Lobby::OnRefreshLobby() {
ui->room_list->setFirstColumnSpanned(j, proxy->index(i, 0), true);
}
}
+
+ ui->room_list->sortByColumn(Column::GAME_NAME, Qt::AscendingOrder);
+}
+
+std::string Lobby::GetProfileUsername() {
+ const auto& current_user = profile_manager->GetUser(Settings::values.current_user.GetValue());
+ Service::Account::ProfileBase profile{};
+
+ if (!current_user.has_value()) {
+ return "";
+ }
+
+ if (!profile_manager->GetProfileBase(*current_user, profile)) {
+ return "";
+ }
+
+ const auto text = Common::StringFromFixedZeroTerminatedBuffer(
+ reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
+
+ return text;
}
LobbyFilterProxyModel::LobbyFilterProxyModel(QWidget* parent, QStandardItemModel* list)
diff --git a/src/yuzu/multiplayer/lobby.h b/src/yuzu/multiplayer/lobby.h
index 2696aec21..300dad13e 100644
--- a/src/yuzu/multiplayer/lobby.h
+++ b/src/yuzu/multiplayer/lobby.h
@@ -24,6 +24,10 @@ namespace Core {
class System;
}
+namespace Service::Account {
+class ProfileManager;
+}
+
/**
* Listing of all public games pulled from services. The lobby should be simple enough for users to
* find the game they want to play, and join it.
@@ -75,8 +79,11 @@ private slots:
signals:
void StateChanged(const Network::RoomMember::State&);
+ void SaveConfig();
private:
+ std::string GetProfileUsername();
+
/**
* Removes all entries in the Lobby before refreshing.
*/
@@ -96,6 +103,7 @@ private:
QFutureWatcher<AnnounceMultiplayerRoom::RoomList> room_list_watcher;
std::weak_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
+ std::unique_ptr<Service::Account::ProfileManager> profile_manager;
QFutureWatcher<void>* watcher;
Validation validation;
Core::System& system;
diff --git a/src/yuzu/multiplayer/lobby_p.h b/src/yuzu/multiplayer/lobby_p.h
index 8071cede4..068c95aca 100644
--- a/src/yuzu/multiplayer/lobby_p.h
+++ b/src/yuzu/multiplayer/lobby_p.h
@@ -11,11 +11,10 @@
namespace Column {
enum List {
- EXPAND,
- ROOM_NAME,
GAME_NAME,
- HOST,
+ ROOM_NAME,
MEMBER,
+ HOST,
TOTAL,
};
}
@@ -91,6 +90,8 @@ public:
setData(game_name, GameNameRole);
if (!smdh_icon.isNull()) {
setData(smdh_icon, GameIconRole);
+ } else {
+ setData(QIcon::fromTheme(QStringLiteral("chip")).pixmap(32), GameIconRole);
}
}
@@ -98,7 +99,12 @@ public:
if (role == Qt::DecorationRole) {
auto val = data(GameIconRole);
if (val.isValid()) {
- val = val.value<QPixmap>().scaled(16, 16, Qt::KeepAspectRatio);
+ val = val.value<QPixmap>().scaled(32, 32, Qt::KeepAspectRatio,
+ Qt::TransformationMode::SmoothTransformation);
+ } else {
+ auto blank_image = QPixmap(32, 32);
+ blank_image.fill(Qt::black);
+ val = blank_image;
}
return val;
} else if (role != Qt::DisplayRole) {
@@ -191,8 +197,8 @@ public:
return LobbyItem::data(role);
}
auto members = data(MemberListRole).toList();
- return QStringLiteral("%1 / %2").arg(QString::number(members.size()),
- data(MaxPlayerRole).toString());
+ return QStringLiteral("%1 / %2 ")
+ .arg(QString::number(members.size()), data(MaxPlayerRole).toString());
}
bool operator<(const QStandardItem& other) const override {
diff --git a/src/yuzu/multiplayer/message.cpp b/src/yuzu/multiplayer/message.cpp
index 758b5b731..6d8f18274 100644
--- a/src/yuzu/multiplayer/message.cpp
+++ b/src/yuzu/multiplayer/message.cpp
@@ -49,9 +49,9 @@ const ConnectionError ErrorManager::PERMISSION_DENIED(
QT_TR_NOOP("You do not have enough permission to perform this action."));
const ConnectionError ErrorManager::NO_SUCH_USER(QT_TR_NOOP(
"The user you are trying to kick/ban could not be found.\nThey may have left the room."));
-const ConnectionError ErrorManager::NO_INTERFACE_SELECTED(
- QT_TR_NOOP("No network interface is selected.\nPlease go to Configure -> System -> Network and "
- "make a selection."));
+const ConnectionError ErrorManager::NO_INTERFACE_SELECTED(QT_TR_NOOP(
+ "No valid network interface is selected.\nPlease go to Configure -> System -> Network and "
+ "make a selection."));
static bool WarnMessage(const std::string& title, const std::string& text) {
return QMessageBox::Ok == QMessageBox::warning(nullptr, QObject::tr(title.c_str()),
diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp
index 66e098296..ae2738ad4 100644
--- a/src/yuzu/multiplayer/state.cpp
+++ b/src/yuzu/multiplayer/state.cpp
@@ -44,9 +44,6 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis
status_text = new ClickableLabel(this);
status_icon = new ClickableLabel(this);
- status_text->setToolTip(tr("Current connection status"));
- status_text->setText(tr("Not Connected. Click here to find a room!"));
- status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16));
connect(status_text, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom);
connect(status_icon, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom);
@@ -57,6 +54,8 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis
HideNotification();
}
});
+
+ retranslateUi();
}
MultiplayerState::~MultiplayerState() = default;
@@ -90,14 +89,7 @@ void MultiplayerState::Close() {
void MultiplayerState::retranslateUi() {
status_text->setToolTip(tr("Current connection status"));
- if (current_state == Network::RoomMember::State::Uninitialized) {
- status_text->setText(tr("Not Connected. Click here to find a room!"));
- } else if (current_state == Network::RoomMember::State::Joined ||
- current_state == Network::RoomMember::State::Moderator) {
- status_text->setText(tr("Connected"));
- } else {
- status_text->setText(tr("Not Connected"));
- }
+ UpdateNotificationStatus();
if (lobby) {
lobby->RetranslateUi();
@@ -113,21 +105,55 @@ void MultiplayerState::retranslateUi() {
}
}
+void MultiplayerState::SetNotificationStatus(NotificationStatus status) {
+ notification_status = status;
+ UpdateNotificationStatus();
+}
+
+void MultiplayerState::UpdateNotificationStatus() {
+ switch (notification_status) {
+ case NotificationStatus::Unitialized:
+ status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16));
+ status_text->setText(tr("Not Connected. Click here to find a room!"));
+ leave_room->setEnabled(false);
+ show_room->setEnabled(false);
+ break;
+ case NotificationStatus::Disconnected:
+ status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16));
+ status_text->setText(tr("Not Connected"));
+ leave_room->setEnabled(false);
+ show_room->setEnabled(false);
+ break;
+ case NotificationStatus::Connected:
+ status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16));
+ status_text->setText(tr("Connected"));
+ leave_room->setEnabled(true);
+ show_room->setEnabled(true);
+ break;
+ case NotificationStatus::Notification:
+ status_icon->setPixmap(
+ QIcon::fromTheme(QStringLiteral("connected_notification")).pixmap(16));
+ status_text->setText(tr("New Messages Received"));
+ leave_room->setEnabled(true);
+ show_room->setEnabled(true);
+ break;
+ }
+
+ // Clean up status bar if game is running
+ if (system.IsPoweredOn()) {
+ status_text->clear();
+ }
+}
+
void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& state) {
LOG_DEBUG(Frontend, "Network State: {}", Network::GetStateStr(state));
if (state == Network::RoomMember::State::Joined ||
state == Network::RoomMember::State::Moderator) {
OnOpenNetworkRoom();
- status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16));
- status_text->setText(tr("Connected"));
- leave_room->setEnabled(true);
- show_room->setEnabled(true);
+ SetNotificationStatus(NotificationStatus::Connected);
} else {
- status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16));
- status_text->setText(tr("Not Connected"));
- leave_room->setEnabled(false);
- show_room->setEnabled(false);
+ SetNotificationStatus(NotificationStatus::Disconnected);
}
current_state = state;
@@ -185,6 +211,10 @@ void MultiplayerState::OnAnnounceFailed(const WebService::WebResult& result) {
QMessageBox::Ok);
}
+void MultiplayerState::OnSaveConfig() {
+ emit SaveConfig();
+}
+
void MultiplayerState::UpdateThemedIcons() {
if (show_notification) {
status_icon->setPixmap(
@@ -209,13 +239,16 @@ static void BringWidgetToFront(QWidget* widget) {
void MultiplayerState::OnViewLobby() {
if (lobby == nullptr) {
lobby = new Lobby(this, game_list_model, announce_multiplayer_session, system);
+ connect(lobby, &Lobby::SaveConfig, this, &MultiplayerState::OnSaveConfig);
}
+ lobby->RefreshLobby();
BringWidgetToFront(lobby);
}
void MultiplayerState::OnCreateRoom() {
if (host_room == nullptr) {
host_room = new HostRoomWindow(this, game_list_model, announce_multiplayer_session, system);
+ connect(host_room, &HostRoomWindow::SaveConfig, this, &MultiplayerState::OnSaveConfig);
}
BringWidgetToFront(host_room);
}
@@ -249,14 +282,13 @@ void MultiplayerState::ShowNotification() {
return; // Do not show notification if the chat window currently has focus
show_notification = true;
QApplication::alert(nullptr);
- status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected_notification")).pixmap(16));
- status_text->setText(tr("New Messages Received"));
+ QApplication::beep();
+ SetNotificationStatus(NotificationStatus::Notification);
}
void MultiplayerState::HideNotification() {
show_notification = false;
- status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16));
- status_text->setText(tr("Connected"));
+ SetNotificationStatus(NotificationStatus::Connected);
}
void MultiplayerState::OnOpenNetworkRoom() {
@@ -279,6 +311,8 @@ void MultiplayerState::OnOpenNetworkRoom() {
void MultiplayerState::OnDirectConnectToRoom() {
if (direct_connect == nullptr) {
direct_connect = new DirectConnectWindow(system, this);
+ connect(direct_connect, &DirectConnectWindow::SaveConfig, this,
+ &MultiplayerState::OnSaveConfig);
}
BringWidgetToFront(direct_connect);
}
diff --git a/src/yuzu/multiplayer/state.h b/src/yuzu/multiplayer/state.h
index c92496413..5d681c5c6 100644
--- a/src/yuzu/multiplayer/state.h
+++ b/src/yuzu/multiplayer/state.h
@@ -22,6 +22,13 @@ class MultiplayerState : public QWidget {
Q_OBJECT;
public:
+ enum class NotificationStatus {
+ Unitialized,
+ Disconnected,
+ Connected,
+ Notification,
+ };
+
explicit MultiplayerState(QWidget* parent, QStandardItemModel* game_list, QAction* leave_room,
QAction* show_room, Core::System& system_);
~MultiplayerState();
@@ -31,6 +38,10 @@ public:
*/
void Close();
+ void SetNotificationStatus(NotificationStatus state);
+
+ void UpdateNotificationStatus();
+
ClickableLabel* GetStatusText() const {
return status_text;
}
@@ -64,6 +75,7 @@ public slots:
void OnOpenNetworkRoom();
void OnDirectConnectToRoom();
void OnAnnounceFailed(const WebService::WebResult&);
+ void OnSaveConfig();
void UpdateThemedIcons();
void ShowNotification();
void HideNotification();
@@ -72,6 +84,7 @@ signals:
void NetworkStateChanged(const Network::RoomMember::State&);
void NetworkError(const Network::RoomMember::Error&);
void AnnounceFailed(const WebService::WebResult&);
+ void SaveConfig();
private:
Lobby* lobby = nullptr;
@@ -85,6 +98,7 @@ private:
QAction* show_room;
std::shared_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
Network::RoomMember::State current_state = Network::RoomMember::State::Uninitialized;
+ NotificationStatus notification_status = NotificationStatus::Unitialized;
bool has_mod_perms = false;
Network::RoomMember::CallbackHandle<Network::RoomMember::State> state_callback_handle;
Network::RoomMember::CallbackHandle<Network::RoomMember::Error> error_callback_handle;
diff --git a/src/yuzu/startup_checks.cpp b/src/yuzu/startup_checks.cpp
index 29b87da05..fc2693f9d 100644
--- a/src/yuzu/startup_checks.cpp
+++ b/src/yuzu/startup_checks.cpp
@@ -57,7 +57,7 @@ bool CheckEnvVars(bool* is_child) {
return false;
}
-bool StartupChecks(const char* arg0, bool* has_broken_vulkan) {
+bool StartupChecks(const char* arg0, bool* has_broken_vulkan, bool perform_vulkan_check) {
#ifdef _WIN32
// Set the startup variable for child processes
const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, ENV_VAR_ENABLED_TEXT);
@@ -67,29 +67,32 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan) {
return false;
}
- PROCESS_INFORMATION process_info;
- std::memset(&process_info, '\0', sizeof(process_info));
-
- if (!SpawnChild(arg0, &process_info, 0)) {
- return false;
- }
-
- // Wait until the processs exits and get exit code from it
- WaitForSingleObject(process_info.hProcess, INFINITE);
- DWORD exit_code = STILL_ACTIVE;
- const int err = GetExitCodeProcess(process_info.hProcess, &exit_code);
- if (err == 0) {
- std::fprintf(stderr, "GetExitCodeProcess failed with error %d\n", GetLastError());
- }
-
- // Vulkan is broken if the child crashed (return value is not zero)
- *has_broken_vulkan = (exit_code != 0);
-
- if (CloseHandle(process_info.hProcess) == 0) {
- std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError());
- }
- if (CloseHandle(process_info.hThread) == 0) {
- std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError());
+ if (perform_vulkan_check) {
+ // Spawn child process that performs Vulkan check
+ PROCESS_INFORMATION process_info;
+ std::memset(&process_info, '\0', sizeof(process_info));
+
+ if (!SpawnChild(arg0, &process_info, 0)) {
+ return false;
+ }
+
+ // Wait until the processs exits and get exit code from it
+ WaitForSingleObject(process_info.hProcess, INFINITE);
+ DWORD exit_code = STILL_ACTIVE;
+ const int err = GetExitCodeProcess(process_info.hProcess, &exit_code);
+ if (err == 0) {
+ std::fprintf(stderr, "GetExitCodeProcess failed with error %d\n", GetLastError());
+ }
+
+ // Vulkan is broken if the child crashed (return value is not zero)
+ *has_broken_vulkan = (exit_code != 0);
+
+ if (CloseHandle(process_info.hProcess) == 0) {
+ std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError());
+ }
+ if (CloseHandle(process_info.hThread) == 0) {
+ std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError());
+ }
}
if (!SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, nullptr)) {
@@ -98,26 +101,28 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan) {
}
#elif defined(YUZU_UNIX)
- const pid_t pid = fork();
- if (pid == 0) {
- CheckVulkan();
- return true;
- } else if (pid == -1) {
- const int err = errno;
- std::fprintf(stderr, "fork failed with error %d\n", err);
- return false;
- }
-
- // Get exit code from child process
- int status;
- const int r_val = wait(&status);
- if (r_val == -1) {
- const int err = errno;
- std::fprintf(stderr, "wait failed with error %d\n", err);
- return false;
+ if (perform_vulkan_check) {
+ const pid_t pid = fork();
+ if (pid == 0) {
+ CheckVulkan();
+ return true;
+ } else if (pid == -1) {
+ const int err = errno;
+ std::fprintf(stderr, "fork failed with error %d\n", err);
+ return false;
+ }
+
+ // Get exit code from child process
+ int status;
+ const int r_val = wait(&status);
+ if (r_val == -1) {
+ const int err = errno;
+ std::fprintf(stderr, "wait failed with error %d\n", err);
+ return false;
+ }
+ // Vulkan is broken if the child crashed (return value is not zero)
+ *has_broken_vulkan = (status != 0);
}
- // Vulkan is broken if the child crashed (return value is not zero)
- *has_broken_vulkan = (status != 0);
#endif
return false;
}
diff --git a/src/yuzu/startup_checks.h b/src/yuzu/startup_checks.h
index f2fc2d9d4..d8e563be6 100644
--- a/src/yuzu/startup_checks.h
+++ b/src/yuzu/startup_checks.h
@@ -13,7 +13,7 @@ constexpr char ENV_VAR_ENABLED_TEXT[] = "ON";
void CheckVulkan();
bool CheckEnvVars(bool* is_child);
-bool StartupChecks(const char* arg0, bool* has_broken_vulkan);
+bool StartupChecks(const char* arg0, bool* has_broken_vulkan, bool perform_vulkan_check);
#ifdef _WIN32
bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags);
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index e12d414d9..753797efc 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -102,7 +102,7 @@ struct Values {
Settings::Setting<uint32_t> callout_flags{0, "calloutFlags"};
// multiplayer settings
- Settings::Setting<QString> multiplayer_nickname{QStringLiteral("yuzu"), "nickname"};
+ Settings::Setting<QString> multiplayer_nickname{{}, "nickname"};
Settings::Setting<QString> multiplayer_ip{{}, "ip"};
Settings::SwitchableSetting<uint, true> multiplayer_port{24872, 0, UINT16_MAX, "port"};
Settings::Setting<QString> multiplayer_room_nickname{{}, "room_nickname"};