Browse Source

Merge pull request #12974 from german77/ldn-interface

service: ldn: Migrate and refractor service to new IPC
liamwhite 2 years ago
parent
commit
95d96cfe66

+ 12 - 0
src/core/CMakeLists.txt

@@ -668,6 +668,18 @@ add_library(core STATIC
     hle/service/ldn/ldn.h
     hle/service/ldn/ldn_results.h
     hle/service/ldn/ldn_types.h
+    hle/service/ldn/monitor_service.cpp
+    hle/service/ldn/monitor_service.h
+    hle/service/ldn/sf_monitor_service.cpp
+    hle/service/ldn/sf_monitor_service.h
+    hle/service/ldn/sf_service.cpp
+    hle/service/ldn/sf_service.h
+    hle/service/ldn/sf_service_monitor.cpp
+    hle/service/ldn/sf_service_monitor.h
+    hle/service/ldn/system_local_communication_service.cpp
+    hle/service/ldn/system_local_communication_service.h
+    hle/service/ldn/user_local_communication_service.cpp
+    hle/service/ldn/user_local_communication_service.h
     hle/service/ldr/ldr.cpp
     hle/service/ldr/ldr.h
     hle/service/lm/lm.cpp

+ 6 - 14
src/core/hle/service/ldn/lan_discovery.cpp

@@ -85,15 +85,14 @@ Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network) const {
 }
 
 Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network,
-                                    std::vector<NodeLatestUpdate>& out_updates,
-                                    std::size_t buffer_count) {
-    if (buffer_count > NodeCountMax) {
+                                    std::span<NodeLatestUpdate> out_updates) {
+    if (out_updates.size() > 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++) {
+        for (std::size_t i = 0; i < out_updates.size(); i++) {
             out_updates[i].state_change = node_changes[i].state_change;
             node_changes[i].state_change = NodeStateChange::None;
         }
@@ -107,15 +106,8 @@ DisconnectReason LANDiscovery::GetDisconnectReason() const {
     return disconnect_reason;
 }
 
-Result LANDiscovery::Scan(std::vector<NetworkInfo>& networks, u16& count,
+Result LANDiscovery::Scan(std::span<NetworkInfo> out_networks, s16& out_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();
@@ -128,7 +120,7 @@ Result LANDiscovery::Scan(std::vector<NetworkInfo>& networks, u16& count,
 
     std::scoped_lock lock{packet_mutex};
     for (const auto& [key, info] : scan_results) {
-        if (count >= networks.size()) {
+        if (out_count >= static_cast<s16>(out_networks.size())) {
             break;
         }
 
@@ -159,7 +151,7 @@ Result LANDiscovery::Scan(std::vector<NetworkInfo>& networks, u16& count,
             }
         }
 
-        networks[count++] = info;
+        out_networks[out_count++] = info;
     }
 
     return ResultSuccess;

+ 2 - 3
src/core/hle/service/ldn/lan_discovery.h

@@ -54,11 +54,10 @@ public:
     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);
+    Result GetNetworkInfo(NetworkInfo& out_network, std::span<NodeLatestUpdate> out_updates);
 
     DisconnectReason GetDisconnectReason() const;
-    Result Scan(std::vector<NetworkInfo>& networks, u16& count, const ScanFilter& filter);
+    Result Scan(std::span<NetworkInfo> out_networks, s16& out_count, const ScanFilter& filter);
     Result SetAdvertiseData(std::span<const u8> data);
 
     Result OpenAccessPoint();

+ 58 - 744
src/core/hle/service/ldn/ldn.cpp

@@ -1,36 +1,24 @@
 // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
-#include <memory>
-
 #include "core/core.h"
-#include "core/hle/service/ldn/lan_discovery.h"
+#include "core/hle/service/cmif_serialization.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/hle/service/server_manager.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
+#include "core/hle/service/ldn/monitor_service.h"
+#include "core/hle/service/ldn/sf_monitor_service.h"
+#include "core/hle/service/ldn/sf_service.h"
+#include "core/hle/service/ldn/sf_service_monitor.h"
+#include "core/hle/service/ldn/system_local_communication_service.h"
+#include "core/hle/service/ldn/user_local_communication_service.h"
 
 namespace Service::LDN {
 
-class IMonitorService final : public ServiceFramework<IMonitorService> {
+class IMonitorServiceCreator final : public ServiceFramework<IMonitorServiceCreator> {
 public:
-    explicit IMonitorService(Core::System& system_) : ServiceFramework{system_, "IMonitorService"} {
+    explicit IMonitorServiceCreator(Core::System& system_) : ServiceFramework{system_, "ldn:m"} {
         // clang-format off
         static const FunctionInfo functions[] = {
-            {0, &IMonitorService::GetStateForMonitor, "GetStateForMonitor"},
-            {1, nullptr, "GetNetworkInfoForMonitor"},
-            {2, nullptr, "GetIpv4AddressForMonitor"},
-            {3, nullptr, "GetDisconnectReasonForMonitor"},
-            {4, nullptr, "GetSecurityParameterForMonitor"},
-            {5, nullptr, "GetNetworkConfigForMonitor"},
-            {100, &IMonitorService::InitializeMonitor, "InitializeMonitor"},
-            {101, nullptr, "FinalizeMonitor"},
+            {0, C<&IMonitorServiceCreator::CreateMonitorService>, "CreateMonitorService"}
         };
         // clang-format on
 
@@ -38,84 +26,20 @@ public:
     }
 
 private:
-    void GetStateForMonitor(HLERequestContext& ctx) {
-        LOG_INFO(Service_LDN, "called");
-
-        IPC::ResponseBuilder rb{ctx, 3};
-        rb.Push(ResultSuccess);
-        rb.PushEnum(state);
-    }
-
-    void InitializeMonitor(HLERequestContext& ctx) {
-        LOG_INFO(Service_LDN, "called");
-
-        state = State::Initialized;
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ResultSuccess);
-    }
-
-    State state{State::None};
-};
-
-class LDNM final : public ServiceFramework<LDNM> {
-public:
-    explicit LDNM(Core::System& system_) : ServiceFramework{system_, "ldn:m"} {
-        // clang-format off
-        static const FunctionInfo functions[] = {
-            {0, &LDNM::CreateMonitorService, "CreateMonitorService"}
-        };
-        // clang-format on
-
-        RegisterHandlers(functions);
-    }
-
-    void CreateMonitorService(HLERequestContext& ctx) {
+    Result CreateMonitorService(OutInterface<IMonitorService> out_interface) {
         LOG_DEBUG(Service_LDN, "called");
 
-        IPC::ResponseBuilder rb{ctx, 2, 0, 1};
-        rb.Push(ResultSuccess);
-        rb.PushIpcInterface<IMonitorService>(system);
+        *out_interface = std::make_shared<IMonitorService>(system);
+        R_SUCCEED();
     }
 };
 
-class ISystemLocalCommunicationService final
-    : public ServiceFramework<ISystemLocalCommunicationService> {
+class ISystemServiceCreator final : public ServiceFramework<ISystemServiceCreator> {
 public:
-    explicit ISystemLocalCommunicationService(Core::System& system_)
-        : ServiceFramework{system_, "ISystemLocalCommunicationService"} {
+    explicit ISystemServiceCreator(Core::System& system_) : ServiceFramework{system_, "ldn:s"} {
         // clang-format off
         static const FunctionInfo functions[] = {
-            {0, nullptr, "GetState"},
-            {1, nullptr, "GetNetworkInfo"},
-            {2, nullptr, "GetIpv4Address"},
-            {3, nullptr, "GetDisconnectReason"},
-            {4, nullptr, "GetSecurityParameter"},
-            {5, nullptr, "GetNetworkConfig"},
-            {100, nullptr, "AttachStateChangeEvent"},
-            {101, nullptr, "GetNetworkInfoLatestUpdate"},
-            {102, nullptr, "Scan"},
-            {103, nullptr, "ScanPrivate"},
-            {104, nullptr, "SetWirelessControllerRestriction"},
-            {200, nullptr, "OpenAccessPoint"},
-            {201, nullptr, "CloseAccessPoint"},
-            {202, nullptr, "CreateNetwork"},
-            {203, nullptr, "CreateNetworkPrivate"},
-            {204, nullptr, "DestroyNetwork"},
-            {205, nullptr, "Reject"},
-            {206, nullptr, "SetAdvertiseData"},
-            {207, nullptr, "SetStationAcceptPolicy"},
-            {208, nullptr, "AddAcceptFilterEntry"},
-            {209, nullptr, "ClearAcceptFilter"},
-            {300, nullptr, "OpenStation"},
-            {301, nullptr, "CloseStation"},
-            {302, nullptr, "Connect"},
-            {303, nullptr, "ConnectPrivate"},
-            {304, nullptr, "Disconnect"},
-            {400, nullptr, "InitializeSystem"},
-            {401, nullptr, "FinalizeSystem"},
-            {402, nullptr, "SetOperationMode"},
-            {403, &ISystemLocalCommunicationService::InitializeSystem2, "InitializeSystem2"},
+            {0, C<&ISystemServiceCreator::CreateSystemLocalCommunicationService>, "CreateSystemLocalCommunicationService"},
         };
         // clang-format on
 
@@ -123,687 +47,78 @@ public:
     }
 
 private:
-    void InitializeSystem2(HLERequestContext& ctx) {
-        LOG_WARNING(Service_LDN, "(STUBBED) called");
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ResultSuccess);
-    }
-};
-
-class IUserLocalCommunicationService final
-    : public ServiceFramework<IUserLocalCommunicationService> {
-public:
-    explicit IUserLocalCommunicationService(Core::System& system_)
-        : ServiceFramework{system_, "IUserLocalCommunicationService"},
-          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, &IUserLocalCommunicationService::GetIpv4Address, "GetIpv4Address"},
-            {3, &IUserLocalCommunicationService::GetDisconnectReason, "GetDisconnectReason"},
-            {4, &IUserLocalCommunicationService::GetSecurityParameter, "GetSecurityParameter"},
-            {5, &IUserLocalCommunicationService::GetNetworkConfig, "GetNetworkConfig"},
-            {100, &IUserLocalCommunicationService::AttachStateChangeEvent, "AttachStateChangeEvent"},
-            {101, &IUserLocalCommunicationService::GetNetworkInfoLatestUpdate, "GetNetworkInfoLatestUpdate"},
-            {102, &IUserLocalCommunicationService::Scan, "Scan"},
-            {103, &IUserLocalCommunicationService::ScanPrivate, "ScanPrivate"},
-            {104, &IUserLocalCommunicationService::SetWirelessControllerRestriction, "SetWirelessControllerRestriction"},
-            {200, &IUserLocalCommunicationService::OpenAccessPoint, "OpenAccessPoint"},
-            {201, &IUserLocalCommunicationService::CloseAccessPoint, "CloseAccessPoint"},
-            {202, &IUserLocalCommunicationService::CreateNetwork, "CreateNetwork"},
-            {203, &IUserLocalCommunicationService::CreateNetworkPrivate, "CreateNetworkPrivate"},
-            {204, &IUserLocalCommunicationService::DestroyNetwork, "DestroyNetwork"},
-            {205, nullptr, "Reject"},
-            {206, &IUserLocalCommunicationService::SetAdvertiseData, "SetAdvertiseData"},
-            {207, &IUserLocalCommunicationService::SetStationAcceptPolicy, "SetStationAcceptPolicy"},
-            {208, &IUserLocalCommunicationService::AddAcceptFilterEntry, "AddAcceptFilterEntry"},
-            {209, nullptr, "ClearAcceptFilter"},
-            {300, &IUserLocalCommunicationService::OpenStation, "OpenStation"},
-            {301, &IUserLocalCommunicationService::CloseStation, "CloseStation"},
-            {302, &IUserLocalCommunicationService::Connect, "Connect"},
-            {303, nullptr, "ConnectPrivate"},
-            {304, &IUserLocalCommunicationService::Disconnect, "Disconnect"},
-            {400, &IUserLocalCommunicationService::Initialize, "Initialize"},
-            {401, &IUserLocalCommunicationService::Finalize, "Finalize"},
-            {402, &IUserLocalCommunicationService::Initialize2, "Initialize2"},
-        };
-        // clang-format on
-
-        RegisterHandlers(functions);
-
-        state_change_event =
-            service_context.CreateEvent("IUserLocalCommunicationService:StateChangeEvent");
-    }
-
-    ~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->Signal();
-    }
-
-    void GetState(HLERequestContext& ctx) {
-        State state = State::Error;
-
-        if (is_initialized) {
-            state = lan_discovery.GetState();
-        }
-
-        IPC::ResponseBuilder rb{ctx, 3};
-        rb.Push(ResultSuccess);
-        rb.PushEnum(state);
-    }
-
-    void GetNetworkInfo(HLERequestContext& ctx) {
-        const auto write_buffer_size = ctx.GetWriteBufferSize();
-
-        if (write_buffer_size != sizeof(NetworkInfo)) {
-            LOG_ERROR(Service_LDN, "Invalid buffer size {}", write_buffer_size);
-            IPC::ResponseBuilder rb{ctx, 2};
-            rb.Push(ResultBadInput);
-            return;
-        }
-
-        NetworkInfo network_info{};
-        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};
-            rb.Push(rc);
-            return;
-        }
-
-        ctx.WriteBuffer<NetworkInfo>(network_info);
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ResultSuccess);
-    }
-
-    void GetIpv4Address(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;
-        }
-
-        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(HLERequestContext& ctx) {
-        IPC::ResponseBuilder rb{ctx, 3};
-        rb.Push(ResultSuccess);
-        rb.PushEnum(lan_discovery.GetDisconnectReason());
-    }
-
-    void GetSecurityParameter(HLERequestContext& ctx) {
-        SecurityParameter security_parameter{};
-        NetworkInfo info{};
-        const Result rc = lan_discovery.GetNetworkInfo(info);
-
-        if (rc.IsError()) {
-            LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
-            IPC::ResponseBuilder rb{ctx, 2};
-            rb.Push(rc);
-            return;
-        }
-
-        security_parameter.session_id = info.network_id.session_id;
-        std::memcpy(security_parameter.data.data(), info.ldn.security_parameter.data(),
-                    sizeof(SecurityParameter::data));
-
-        IPC::ResponseBuilder rb{ctx, 10};
-        rb.Push(rc);
-        rb.PushRaw<SecurityParameter>(security_parameter);
-    }
-
-    void GetNetworkConfig(HLERequestContext& ctx) {
-        NetworkConfig config{};
-        NetworkInfo info{};
-        const Result rc = lan_discovery.GetNetworkInfo(info);
-
-        if (rc.IsError()) {
-            LOG_ERROR(Service_LDN, "NetworkConfig is not valid {}", rc.raw);
-            IPC::ResponseBuilder rb{ctx, 2};
-            rb.Push(rc);
-            return;
-        }
-
-        config.intent_id = info.network_id.intent_id;
-        config.channel = info.common.channel;
-        config.node_count_max = info.ldn.node_count_max;
-        config.local_communication_version = info.ldn.nodes[0].local_communication_version;
-
-        IPC::ResponseBuilder rb{ctx, 10};
-        rb.Push(rc);
-        rb.PushRaw<NetworkConfig>(config);
-    }
-
-    void AttachStateChangeEvent(HLERequestContext& ctx) {
-        LOG_INFO(Service_LDN, "called");
-
-        IPC::ResponseBuilder rb{ctx, 2, 1};
-        rb.Push(ResultSuccess);
-        rb.PushCopyObjects(state_change_event->GetReadableEvent());
-    }
-
-    void GetNetworkInfoLatestUpdate(HLERequestContext& ctx) {
-        const std::size_t network_buffer_size = ctx.GetWriteBufferSize(0);
-        const std::size_t node_buffer_count = ctx.GetWriteBufferNumElements<NodeLatestUpdate>(1);
-
-        if (node_buffer_count == 0 || network_buffer_size != sizeof(NetworkInfo)) {
-            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{};
-        std::vector<NodeLatestUpdate> latest_update(node_buffer_count);
-
-        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};
-            rb.Push(rc);
-            return;
-        }
-
-        ctx.WriteBuffer(info, 0);
-        ctx.WriteBuffer(latest_update, 1);
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ResultSuccess);
-    }
-
-    void Scan(HLERequestContext& ctx) {
-        ScanImpl(ctx);
-    }
-
-    void ScanPrivate(HLERequestContext& ctx) {
-        ScanImpl(ctx, true);
-    }
-
-    void ScanImpl(HLERequestContext& ctx, bool is_private = false) {
-        IPC::RequestParser rp{ctx};
-        const auto channel{rp.PopEnum<WifiChannel>()};
-        const auto scan_filter{rp.PopRaw<ScanFilter>()};
-
-        const std::size_t network_info_size = ctx.GetWriteBufferNumElements<NetworkInfo>();
-
-        if (network_info_size == 0) {
-            LOG_ERROR(Service_LDN, "Invalid buffer size {}", network_info_size);
-            IPC::ResponseBuilder rb{ctx, 2};
-            rb.Push(ResultBadInput);
-            return;
-        }
-
-        u16 count = 0;
-        std::vector<NetworkInfo> network_infos(network_info_size);
-        Result rc = lan_discovery.Scan(network_infos, count, scan_filter);
-
-        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(rc);
-        rb.Push<u32>(count);
-    }
-
-    void SetWirelessControllerRestriction(HLERequestContext& ctx) {
-        LOG_WARNING(Service_LDN, "(STUBBED) called");
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ResultSuccess);
-    }
-
-    void OpenAccessPoint(HLERequestContext& ctx) {
-        LOG_INFO(Service_LDN, "called");
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(lan_discovery.OpenAccessPoint());
-    }
-
-    void CloseAccessPoint(HLERequestContext& ctx) {
-        LOG_INFO(Service_LDN, "called");
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(lan_discovery.CloseAccessPoint());
-    }
-
-    void CreateNetwork(HLERequestContext& ctx) {
-        LOG_INFO(Service_LDN, "called");
-
-        CreateNetworkImpl(ctx);
-    }
-
-    void CreateNetworkPrivate(HLERequestContext& ctx) {
-        LOG_INFO(Service_LDN, "called");
-
-        CreateNetworkImpl(ctx, true);
-    }
-
-    void CreateNetworkImpl(HLERequestContext& ctx, bool is_private = false) {
-        IPC::RequestParser rp{ctx};
-
-        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(lan_discovery.CreateNetwork(security_config, user_config, network_Config));
-    }
-
-    void DestroyNetwork(HLERequestContext& ctx) {
-        LOG_INFO(Service_LDN, "called");
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(lan_discovery.DestroyNetwork());
-    }
-
-    void SetAdvertiseData(HLERequestContext& ctx) {
-        const auto read_buffer = ctx.ReadBuffer();
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(lan_discovery.SetAdvertiseData(read_buffer));
-    }
-
-    void SetStationAcceptPolicy(HLERequestContext& ctx) {
-        LOG_WARNING(Service_LDN, "(STUBBED) called");
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ResultSuccess);
-    }
-
-    void AddAcceptFilterEntry(HLERequestContext& ctx) {
-        LOG_WARNING(Service_LDN, "(STUBBED) called");
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ResultSuccess);
-    }
-
-    void OpenStation(HLERequestContext& ctx) {
-        LOG_INFO(Service_LDN, "called");
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(lan_discovery.OpenStation());
-    }
-
-    void CloseStation(HLERequestContext& ctx) {
-        LOG_INFO(Service_LDN, "called");
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(lan_discovery.CloseStation());
-    }
-
-    void Connect(HLERequestContext& ctx) {
-        IPC::RequestParser rp{ctx};
-        struct Parameters {
-            SecurityConfig security_config;
-            UserConfig user_config;
-            u32 local_communication_version;
-            u32 option;
-        };
-        static_assert(sizeof(Parameters) == 0x7C, "Parameters has incorrect size.");
-
-        const auto parameters{rp.PopRaw<Parameters>()};
-
-        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 auto read_buffer = ctx.ReadBuffer();
-        if (read_buffer.size() != sizeof(NetworkInfo)) {
-            LOG_ERROR(Frontend, "NetworkInfo doesn't match read_buffer size!");
-            IPC::ResponseBuilder rb{ctx, 2};
-            rb.Push(ResultBadInput);
-            return;
-        }
-
-        NetworkInfo network_info{};
-        std::memcpy(&network_info, read_buffer.data(), read_buffer.size());
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(lan_discovery.Connect(network_info, parameters.user_config,
-                                      static_cast<u16>(parameters.local_communication_version)));
-    }
-
-    void Disconnect(HLERequestContext& ctx) {
-        LOG_INFO(Service_LDN, "called");
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(lan_discovery.Disconnect());
-    }
-
-    void Initialize(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(HLERequestContext& ctx) {
-        if (auto room_member = room_network.GetRoomMember().lock()) {
-            room_member->Unbind(ldn_packet_received);
-        }
-
-        is_initialized = false;
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(lan_discovery.Finalize());
-    }
-
-    void Initialize2(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);
-    }
-
-    Result InitializeImpl(HLERequestContext& ctx) {
-        const auto network_interface = Network::GetSelectedNetworkInterface();
-        if (!network_interface) {
-            LOG_ERROR(Service_LDN, "No network interface is set");
-            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;
-        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{};
-};
-
-class LDNS final : public ServiceFramework<LDNS> {
-public:
-    explicit LDNS(Core::System& system_) : ServiceFramework{system_, "ldn:s"} {
-        // clang-format off
-        static const FunctionInfo functions[] = {
-            {0, &LDNS::CreateSystemLocalCommunicationService, "CreateSystemLocalCommunicationService"},
-        };
-        // clang-format on
-
-        RegisterHandlers(functions);
-    }
-
-    void CreateSystemLocalCommunicationService(HLERequestContext& ctx) {
+    Result CreateSystemLocalCommunicationService(
+        OutInterface<ISystemLocalCommunicationService> out_interface) {
         LOG_DEBUG(Service_LDN, "called");
 
-        IPC::ResponseBuilder rb{ctx, 2, 0, 1};
-        rb.Push(ResultSuccess);
-        rb.PushIpcInterface<ISystemLocalCommunicationService>(system);
+        *out_interface = std::make_shared<ISystemLocalCommunicationService>(system);
+        R_SUCCEED();
     }
 };
 
-class LDNU final : public ServiceFramework<LDNU> {
+class IUserServiceCreator final : public ServiceFramework<IUserServiceCreator> {
 public:
-    explicit LDNU(Core::System& system_) : ServiceFramework{system_, "ldn:u"} {
+    explicit IUserServiceCreator(Core::System& system_) : ServiceFramework{system_, "ldn:u"} {
         // clang-format off
         static const FunctionInfo functions[] = {
-            {0, &LDNU::CreateUserLocalCommunicationService, "CreateUserLocalCommunicationService"},
+            {0, C<&IUserServiceCreator::CreateUserLocalCommunicationService>, "CreateUserLocalCommunicationService"},
         };
         // clang-format on
 
         RegisterHandlers(functions);
     }
 
-    void CreateUserLocalCommunicationService(HLERequestContext& ctx) {
+private:
+    Result CreateUserLocalCommunicationService(
+        OutInterface<IUserLocalCommunicationService> out_interface) {
         LOG_DEBUG(Service_LDN, "called");
 
-        IPC::ResponseBuilder rb{ctx, 2, 0, 1};
-        rb.Push(ResultSuccess);
-        rb.PushIpcInterface<IUserLocalCommunicationService>(system);
+        *out_interface = std::make_shared<IUserLocalCommunicationService>(system);
+        R_SUCCEED();
     }
 };
 
-class INetworkService final : public ServiceFramework<INetworkService> {
+class ISfServiceCreator final : public ServiceFramework<ISfServiceCreator> {
 public:
-    explicit INetworkService(Core::System& system_) : ServiceFramework{system_, "INetworkService"} {
+    explicit ISfServiceCreator(Core::System& system_, bool is_system_, const char* name_)
+        : ServiceFramework{system_, name_}, is_system{is_system_} {
         // clang-format off
         static const FunctionInfo functions[] = {
-            {0, nullptr, "Initialize"},
-            {256, nullptr, "AttachNetworkInterfaceStateChangeEvent"},
-            {264, nullptr, "GetNetworkInterfaceLastError"},
-            {272, nullptr, "GetRole"},
-            {280, nullptr, "GetAdvertiseData"},
-            {288, nullptr, "GetGroupInfo"},
-            {296, nullptr, "GetGroupInfo2"},
-            {304, nullptr, "GetGroupOwner"},
-            {312, nullptr, "GetIpConfig"},
-            {320, nullptr, "GetLinkLevel"},
-            {512, nullptr, "Scan"},
-            {768, nullptr, "CreateGroup"},
-            {776, nullptr, "DestroyGroup"},
-            {784, nullptr, "SetAdvertiseData"},
-            {1536, nullptr, "SendToOtherGroup"},
-            {1544, nullptr, "RecvFromOtherGroup"},
-            {1552, nullptr, "AddAcceptableGroupId"},
-            {1560, nullptr, "ClearAcceptableGroupId"},
+            {0, C<&ISfServiceCreator::CreateNetworkService>, "CreateNetworkService"},
+            {8, C<&ISfServiceCreator::CreateNetworkServiceMonitor>, "CreateNetworkServiceMonitor"},
         };
         // clang-format on
 
         RegisterHandlers(functions);
     }
-};
-
-class INetworkServiceMonitor final : public ServiceFramework<INetworkServiceMonitor> {
-public:
-    explicit INetworkServiceMonitor(Core::System& system_)
-        : ServiceFramework{system_, "INetworkServiceMonitor"} {
-        // clang-format off
-        static const FunctionInfo functions[] = {
-            {0, &INetworkServiceMonitor::Initialize, "Initialize"},
-            {256, nullptr, "AttachNetworkInterfaceStateChangeEvent"},
-            {264, nullptr, "GetNetworkInterfaceLastError"},
-            {272, nullptr, "GetRole"},
-            {280, nullptr, "GetAdvertiseData"},
-            {281, nullptr, "GetAdvertiseData2"},
-            {288, nullptr, "GetGroupInfo"},
-            {296, nullptr, "GetGroupInfo2"},
-            {304, nullptr, "GetGroupOwner"},
-            {312, nullptr, "GetIpConfig"},
-            {320, nullptr, "GetLinkLevel"},
-            {328, nullptr, "AttachJoinEvent"},
-            {336, nullptr, "GetMembers"},
-        };
-        // clang-format on
-
-        RegisterHandlers(functions);
-    }
-
-    void Initialize(HLERequestContext& ctx) {
-        LOG_WARNING(Service_LDN, "(STUBBED) called");
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ResultDisabled);
-    }
-};
-
-class LP2PAPP final : public ServiceFramework<LP2PAPP> {
-public:
-    explicit LP2PAPP(Core::System& system_) : ServiceFramework{system_, "lp2p:app"} {
-        // clang-format off
-        static const FunctionInfo functions[] = {
-            {0, &LP2PAPP::CreateMonitorService, "CreateNetworkService"},
-            {8, &LP2PAPP::CreateMonitorService, "CreateNetworkServiceMonitor"},
-        };
-        // clang-format on
-
-        RegisterHandlers(functions);
-    }
-
-    void CreateNetworkervice(HLERequestContext& ctx) {
-        IPC::RequestParser rp{ctx};
-        const u64 reserved_input = rp.Pop<u64>();
-        const u32 input = rp.Pop<u32>();
-
-        LOG_WARNING(Service_LDN, "(STUBBED) called reserved_input={} input={}", reserved_input,
-                    input);
-
-        IPC::ResponseBuilder rb{ctx, 2, 0, 1};
-        rb.Push(ResultSuccess);
-        rb.PushIpcInterface<INetworkService>(system);
-    }
-
-    void CreateMonitorService(HLERequestContext& ctx) {
-        IPC::RequestParser rp{ctx};
-        const u64 reserved_input = rp.Pop<u64>();
-
-        LOG_WARNING(Service_LDN, "(STUBBED) called reserved_input={}", reserved_input);
-
-        IPC::ResponseBuilder rb{ctx, 2, 0, 1};
-        rb.Push(ResultSuccess);
-        rb.PushIpcInterface<INetworkServiceMonitor>(system);
-    }
-};
-
-class LP2PSYS final : public ServiceFramework<LP2PSYS> {
-public:
-    explicit LP2PSYS(Core::System& system_) : ServiceFramework{system_, "lp2p:sys"} {
-        // clang-format off
-        static const FunctionInfo functions[] = {
-            {0, &LP2PSYS::CreateMonitorService, "CreateNetworkService"},
-            {8, &LP2PSYS::CreateMonitorService, "CreateNetworkServiceMonitor"},
-        };
-        // clang-format on
-
-        RegisterHandlers(functions);
-    }
-
-    void CreateNetworkervice(HLERequestContext& ctx) {
-        IPC::RequestParser rp{ctx};
-        const u64 reserved_input = rp.Pop<u64>();
-        const u32 input = rp.Pop<u32>();
 
+private:
+    Result CreateNetworkService(OutInterface<ISfService> out_interface, u32 input,
+                                u64 reserved_input) {
         LOG_WARNING(Service_LDN, "(STUBBED) called reserved_input={} input={}", reserved_input,
                     input);
 
-        IPC::ResponseBuilder rb{ctx, 2, 0, 1};
-        rb.Push(ResultSuccess);
-        rb.PushIpcInterface<INetworkService>(system);
+        *out_interface = std::make_shared<ISfService>(system);
+        R_SUCCEED();
     }
 
-    void CreateMonitorService(HLERequestContext& ctx) {
-        IPC::RequestParser rp{ctx};
-        const u64 reserved_input = rp.Pop<u64>();
-
+    Result CreateNetworkServiceMonitor(OutInterface<ISfServiceMonitor> out_interface,
+                                       u64 reserved_input) {
         LOG_WARNING(Service_LDN, "(STUBBED) called reserved_input={}", reserved_input);
 
-        IPC::ResponseBuilder rb{ctx, 2, 0, 1};
-        rb.Push(ResultSuccess);
-        rb.PushIpcInterface<INetworkServiceMonitor>(system);
+        *out_interface = std::make_shared<ISfServiceMonitor>(system);
+        R_SUCCEED();
     }
-};
 
-class ISfMonitorService final : public ServiceFramework<ISfMonitorService> {
-public:
-    explicit ISfMonitorService(Core::System& system_)
-        : ServiceFramework{system_, "ISfMonitorService"} {
-        // clang-format off
-        static const FunctionInfo functions[] = {
-            {0, &ISfMonitorService::Initialize, "Initialize"},
-            {288, &ISfMonitorService::GetGroupInfo, "GetGroupInfo"},
-            {320, nullptr, "GetLinkLevel"},
-        };
-        // clang-format on
-
-        RegisterHandlers(functions);
-    }
-
-private:
-    void Initialize(HLERequestContext& ctx) {
-        LOG_WARNING(Service_LDN, "(STUBBED) called");
-
-        IPC::ResponseBuilder rb{ctx, 3};
-        rb.Push(ResultSuccess);
-        rb.Push(0);
-    }
-
-    void GetGroupInfo(HLERequestContext& ctx) {
-        LOG_WARNING(Service_LDN, "(STUBBED) called");
-
-        struct GroupInfo {
-            std::array<u8, 0x200> info;
-        };
-
-        GroupInfo group_info{};
-
-        ctx.WriteBuffer(group_info);
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ResultSuccess);
-    }
+    bool is_system{};
 };
 
-class LP2PM final : public ServiceFramework<LP2PM> {
+class ISfMonitorServiceCreator final : public ServiceFramework<ISfMonitorServiceCreator> {
 public:
-    explicit LP2PM(Core::System& system_) : ServiceFramework{system_, "lp2p:m"} {
+    explicit ISfMonitorServiceCreator(Core::System& system_) : ServiceFramework{system_, "lp2p:m"} {
         // clang-format off
         static const FunctionInfo functions[] = {
-            {0, &LP2PM::CreateMonitorService, "CreateMonitorService"},
+            {0, C<&ISfMonitorServiceCreator::CreateMonitorService>, "CreateMonitorService"},
         };
         // clang-format on
 
@@ -811,28 +126,27 @@ public:
     }
 
 private:
-    void CreateMonitorService(HLERequestContext& ctx) {
-        IPC::RequestParser rp{ctx};
-        const u64 reserved_input = rp.Pop<u64>();
-
+    Result CreateMonitorService(OutInterface<ISfMonitorService> out_interface, u64 reserved_input) {
         LOG_INFO(Service_LDN, "called, reserved_input={}", reserved_input);
 
-        IPC::ResponseBuilder rb{ctx, 2, 0, 1};
-        rb.Push(ResultSuccess);
-        rb.PushIpcInterface<ISfMonitorService>(system);
+        *out_interface = std::make_shared<ISfMonitorService>(system);
+        R_SUCCEED();
     }
 };
 
 void LoopProcess(Core::System& system) {
     auto server_manager = std::make_unique<ServerManager>(system);
 
-    server_manager->RegisterNamedService("ldn:m", std::make_shared<LDNM>(system));
-    server_manager->RegisterNamedService("ldn:s", std::make_shared<LDNS>(system));
-    server_manager->RegisterNamedService("ldn:u", std::make_shared<LDNU>(system));
+    server_manager->RegisterNamedService("ldn:m", std::make_shared<IMonitorServiceCreator>(system));
+    server_manager->RegisterNamedService("ldn:s", std::make_shared<ISystemServiceCreator>(system));
+    server_manager->RegisterNamedService("ldn:u", std::make_shared<IUserServiceCreator>(system));
 
-    server_manager->RegisterNamedService("lp2p:app", std::make_shared<LP2PAPP>(system));
-    server_manager->RegisterNamedService("lp2p:sys", std::make_shared<LP2PSYS>(system));
-    server_manager->RegisterNamedService("lp2p:m", std::make_shared<LP2PM>(system));
+    server_manager->RegisterNamedService(
+        "lp2p:app", std::make_shared<ISfServiceCreator>(system, false, "lp2p:app"));
+    server_manager->RegisterNamedService(
+        "lp2p:sys", std::make_shared<ISfServiceCreator>(system, true, "lp2p:sys"));
+    server_manager->RegisterNamedService("lp2p:m",
+                                         std::make_shared<ISfMonitorServiceCreator>(system));
 
     ServerManager::RunServer(std::move(server_manager));
 }

+ 0 - 6
src/core/hle/service/ldn/ldn.h

@@ -3,12 +3,6 @@
 
 #pragma once
 
-#include "core/hle/kernel/k_event.h"
-#include "core/hle/result.h"
-#include "core/hle/service/ipc_helpers.h"
-#include "core/hle/service/kernel_helpers.h"
-#include "core/hle/service/sm/sm.h"
-
 namespace Core {
 class System;
 }

+ 57 - 11
src/core/hle/service/ldn/ldn_types.h

@@ -123,6 +123,18 @@ enum class NodeStatus : u8 {
     Connected,
 };
 
+enum class WirelessControllerRestriction : u32 {
+    None,
+    Default,
+};
+
+struct ConnectOption {
+    union {
+        u32 raw;
+    };
+};
+static_assert(sizeof(ConnectOption) == 0x4, "ConnectOption is an invalid size");
+
 struct NodeLatestUpdate {
     NodeStateChange state_change;
     INSERT_PADDING_BYTES(0x7); // Unknown
@@ -139,9 +151,9 @@ static_assert(sizeof(SessionId) == 0x10, "SessionId is an invalid size");
 
 struct IntentId {
     u64 local_communication_id;
-    INSERT_PADDING_BYTES(0x2); // Reserved
+    INSERT_PADDING_BYTES_NOINIT(0x2); // Reserved
     u16 scene_id;
-    INSERT_PADDING_BYTES(0x4); // Reserved
+    INSERT_PADDING_BYTES_NOINIT(0x4); // Reserved
 };
 static_assert(sizeof(IntentId) == 0x10, "IntentId is an invalid size");
 
@@ -152,13 +164,14 @@ struct NetworkId {
 static_assert(sizeof(NetworkId) == 0x20, "NetworkId is an invalid size");
 
 struct Ssid {
-    u8 length{};
-    std::array<char, SsidLengthMax + 1> raw{};
+    u8 length;
+    std::array<char, SsidLengthMax + 1> raw;
 
     Ssid() = default;
 
     constexpr explicit Ssid(std::string_view data) {
         length = static_cast<u8>(std::min(data.size(), SsidLengthMax));
+        raw = {};
         data.copy(raw.data(), length);
         raw[length] = 0;
     }
@@ -181,7 +194,7 @@ using Ipv4Address = std::array<u8, 4>;
 static_assert(sizeof(Ipv4Address) == 0x4, "Ipv4Address is an invalid size");
 
 struct MacAddress {
-    std::array<u8, 6> raw{};
+    std::array<u8, 6> raw;
 
     friend bool operator==(const MacAddress& lhs, const MacAddress& rhs) = default;
 };
@@ -211,7 +224,7 @@ struct CommonNetworkInfo {
     WifiChannel channel;
     LinkLevel link_level;
     PackedNetworkType network_type;
-    INSERT_PADDING_BYTES(0x4);
+    INSERT_PADDING_BYTES_NOINIT(0x4);
 };
 static_assert(sizeof(CommonNetworkInfo) == 0x30, "CommonNetworkInfo is an invalid size");
 
@@ -221,9 +234,9 @@ struct NodeInfo {
     s8 node_id;
     u8 is_connected;
     std::array<u8, UserNameBytesMax + 1> user_name;
-    INSERT_PADDING_BYTES(0x1); // Reserved
+    INSERT_PADDING_BYTES_NOINIT(0x1); // Reserved
     s16 local_communication_version;
-    INSERT_PADDING_BYTES(0x10); // Reserved
+    INSERT_PADDING_BYTES_NOINIT(0x10); // Reserved
 };
 static_assert(sizeof(NodeInfo) == 0x40, "NodeInfo is an invalid size");
 
@@ -232,14 +245,14 @@ struct LdnNetworkInfo {
     SecurityMode security_mode;
     AcceptPolicy station_accept_policy;
     u8 has_action_frame;
-    INSERT_PADDING_BYTES(0x2); // Padding
+    INSERT_PADDING_BYTES_NOINIT(0x2); // Padding
     u8 node_count_max;
     u8 node_count;
     std::array<NodeInfo, NodeCountMax> nodes;
-    INSERT_PADDING_BYTES(0x2); // Reserved
+    INSERT_PADDING_BYTES_NOINIT(0x2); // Reserved
     u16 advertise_data_size;
     std::array<u8, AdvertiseDataSizeMax> advertise_data;
-    INSERT_PADDING_BYTES(0x8C); // Reserved
+    INSERT_PADDING_BYTES_NOINIT(0x8C); // Reserved
     u64 random_authentication_id;
 };
 static_assert(sizeof(LdnNetworkInfo) == 0x430, "LdnNetworkInfo is an invalid size");
@@ -250,6 +263,7 @@ struct NetworkInfo {
     LdnNetworkInfo ldn;
 };
 static_assert(sizeof(NetworkInfo) == 0x480, "NetworkInfo is an invalid size");
+static_assert(std::is_trivial_v<NetworkInfo>, "NetworkInfo type must be trivially copyable.");
 
 struct SecurityConfig {
     SecurityMode security_mode;
@@ -303,4 +317,36 @@ struct AddressList {
 };
 static_assert(sizeof(AddressList) == 0x60, "AddressList is an invalid size");
 
+struct GroupInfo {
+    std::array<u8, 0x200> info;
+};
+
+struct CreateNetworkConfig {
+    SecurityConfig security_config;
+    UserConfig user_config;
+    INSERT_PADDING_BYTES(0x4);
+    NetworkConfig network_config;
+};
+static_assert(sizeof(CreateNetworkConfig) == 0x98, "CreateNetworkConfig is an invalid size");
+
+#pragma pack(push, 4)
+struct CreateNetworkConfigPrivate {
+    SecurityConfig security_config;
+    SecurityParameter security_parameter;
+    UserConfig user_config;
+    INSERT_PADDING_BYTES(0x4);
+    NetworkConfig network_config;
+};
+#pragma pack(pop)
+static_assert(sizeof(CreateNetworkConfigPrivate) == 0xB8,
+              "CreateNetworkConfigPrivate is an invalid size");
+
+struct ConnectNetworkData {
+    SecurityConfig security_config;
+    UserConfig user_config;
+    s32 local_communication_version;
+    ConnectOption option;
+};
+static_assert(sizeof(ConnectNetworkData) == 0x7c, "ConnectNetworkData is an invalid size");
+
 } // namespace Service::LDN

+ 43 - 0
src/core/hle/service/ldn/monitor_service.cpp

@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ldn/monitor_service.h"
+
+namespace Service::LDN {
+
+IMonitorService::IMonitorService(Core::System& system_)
+    : ServiceFramework{system_, "IMonitorService"} {
+    // clang-format off
+    static const FunctionInfo functions[] = {
+        {0, C<&IMonitorService::GetStateForMonitor>, "GetStateForMonitor"},
+        {1, nullptr, "GetNetworkInfoForMonitor"},
+        {2, nullptr, "GetIpv4AddressForMonitor"},
+        {3, nullptr, "GetDisconnectReasonForMonitor"},
+        {4, nullptr, "GetSecurityParameterForMonitor"},
+        {5, nullptr, "GetNetworkConfigForMonitor"},
+        {100, C<&IMonitorService::InitializeMonitor>, "InitializeMonitor"},
+        {101, nullptr, "FinalizeMonitor"},
+    };
+    // clang-format on
+
+    RegisterHandlers(functions);
+}
+
+IMonitorService::~IMonitorService() = default;
+
+Result IMonitorService::GetStateForMonitor(Out<State> out_state) {
+    LOG_INFO(Service_LDN, "called");
+
+    *out_state = state;
+    R_SUCCEED();
+}
+
+Result IMonitorService::InitializeMonitor() {
+    LOG_INFO(Service_LDN, "called");
+
+    state = State::Initialized;
+    R_SUCCEED();
+}
+
+} // namespace Service::LDN

+ 28 - 0
src/core/hle/service/ldn/monitor_service.h

@@ -0,0 +1,28 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/ldn/ldn_types.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::LDN {
+
+class IMonitorService final : public ServiceFramework<IMonitorService> {
+public:
+    explicit IMonitorService(Core::System& system_);
+    ~IMonitorService() override;
+
+private:
+    Result GetStateForMonitor(Out<State> out_state);
+    Result InitializeMonitor();
+
+    State state{State::None};
+};
+
+} // namespace Service::LDN

+ 40 - 0
src/core/hle/service/ldn/sf_monitor_service.cpp

@@ -0,0 +1,40 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ldn/ldn_types.h"
+#include "core/hle/service/ldn/sf_monitor_service.h"
+
+namespace Service::LDN {
+
+ISfMonitorService::ISfMonitorService(Core::System& system_)
+    : ServiceFramework{system_, "ISfMonitorService"} {
+    // clang-format off
+        static const FunctionInfo functions[] = {
+            {0, C<&ISfMonitorService::Initialize>, "Initialize"},
+            {288, C<&ISfMonitorService::GetGroupInfo>, "GetGroupInfo"},
+            {320, nullptr, "GetLinkLevel"},
+        };
+    // clang-format on
+
+    RegisterHandlers(functions);
+}
+
+ISfMonitorService::~ISfMonitorService() = default;
+
+Result ISfMonitorService::Initialize(Out<u32> out_value) {
+    LOG_WARNING(Service_LDN, "(STUBBED) called");
+
+    *out_value = 0;
+    R_SUCCEED();
+}
+
+Result ISfMonitorService::GetGroupInfo(
+    OutLargeData<GroupInfo, BufferAttr_HipcAutoSelect> out_group_info) {
+    LOG_WARNING(Service_LDN, "(STUBBED) called");
+
+    *out_group_info = GroupInfo{};
+    R_SUCCEED();
+}
+
+} // namespace Service::LDN

+ 26 - 0
src/core/hle/service/ldn/sf_monitor_service.h

@@ -0,0 +1,26 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::LDN {
+struct GroupInfo;
+
+class ISfMonitorService final : public ServiceFramework<ISfMonitorService> {
+public:
+    explicit ISfMonitorService(Core::System& system_);
+    ~ISfMonitorService() override;
+
+private:
+    Result Initialize(Out<u32> out_value);
+    Result GetGroupInfo(OutLargeData<GroupInfo, BufferAttr_HipcAutoSelect> out_group_info);
+};
+
+} // namespace Service::LDN

+ 37 - 0
src/core/hle/service/ldn/sf_service.cpp

@@ -0,0 +1,37 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/ldn/sf_service.h"
+
+namespace Service::LDN {
+
+ISfService::ISfService(Core::System& system_) : ServiceFramework{system_, "ISfService"} {
+    // clang-format off
+    static const FunctionInfo functions[] = {
+            {0, nullptr, "Initialize"},
+            {256, nullptr, "AttachNetworkInterfaceStateChangeEvent"},
+            {264, nullptr, "GetNetworkInterfaceLastError"},
+            {272, nullptr, "GetRole"},
+            {280, nullptr, "GetAdvertiseData"},
+            {288, nullptr, "GetGroupInfo"},
+            {296, nullptr, "GetGroupInfo2"},
+            {304, nullptr, "GetGroupOwner"},
+            {312, nullptr, "GetIpConfig"},
+            {320, nullptr, "GetLinkLevel"},
+            {512, nullptr, "Scan"},
+            {768, nullptr, "CreateGroup"},
+            {776, nullptr, "DestroyGroup"},
+            {784, nullptr, "SetAdvertiseData"},
+            {1536, nullptr, "SendToOtherGroup"},
+            {1544, nullptr, "RecvFromOtherGroup"},
+            {1552, nullptr, "AddAcceptableGroupId"},
+            {1560, nullptr, "ClearAcceptableGroupId"},
+    };
+    // clang-format on
+
+    RegisterHandlers(functions);
+}
+
+ISfService::~ISfService() = default;
+
+} // namespace Service::LDN

+ 21 - 0
src/core/hle/service/ldn/sf_service.h

@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::LDN {
+
+class ISfService final : public ServiceFramework<ISfService> {
+public:
+    explicit ISfService(Core::System& system_);
+    ~ISfService() override;
+};
+
+} // namespace Service::LDN

+ 50 - 0
src/core/hle/service/ldn/sf_service_monitor.cpp

@@ -0,0 +1,50 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ldn/ldn_types.h"
+#include "core/hle/service/ldn/sf_service_monitor.h"
+
+namespace Service::LDN {
+
+ISfServiceMonitor::ISfServiceMonitor(Core::System& system_)
+    : ServiceFramework{system_, "ISfServiceMonitor"} {
+    // clang-format off
+        static const FunctionInfo functions[] = {
+            {0, C<&ISfServiceMonitor::Initialize>, "Initialize"},
+            {256, nullptr, "AttachNetworkInterfaceStateChangeEvent"},
+            {264, nullptr, "GetNetworkInterfaceLastError"},
+            {272, nullptr, "GetRole"},
+            {280, nullptr, "GetAdvertiseData"},
+            {281, nullptr, "GetAdvertiseData2"},
+            {288, C<&ISfServiceMonitor::GetGroupInfo>, "GetGroupInfo"},
+            {296, nullptr, "GetGroupInfo2"},
+            {304, nullptr, "GetGroupOwner"},
+            {312, nullptr, "GetIpConfig"},
+            {320, nullptr, "GetLinkLevel"},
+            {328, nullptr, "AttachJoinEvent"},
+            {336, nullptr, "GetMembers"},
+        };
+    // clang-format on
+
+    RegisterHandlers(functions);
+}
+
+ISfServiceMonitor::~ISfServiceMonitor() = default;
+
+Result ISfServiceMonitor::Initialize(Out<u32> out_value) {
+    LOG_WARNING(Service_LDN, "(STUBBED) called");
+
+    *out_value = 0;
+    R_SUCCEED();
+}
+
+Result ISfServiceMonitor::GetGroupInfo(
+    OutLargeData<GroupInfo, BufferAttr_HipcAutoSelect> out_group_info) {
+    LOG_WARNING(Service_LDN, "(STUBBED) called");
+
+    *out_group_info = GroupInfo{};
+    R_SUCCEED();
+}
+
+} // namespace Service::LDN

+ 26 - 0
src/core/hle/service/ldn/sf_service_monitor.h

@@ -0,0 +1,26 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::LDN {
+struct GroupInfo;
+
+class ISfServiceMonitor final : public ServiceFramework<ISfServiceMonitor> {
+public:
+    explicit ISfServiceMonitor(Core::System& system_);
+    ~ISfServiceMonitor() override;
+
+private:
+    Result Initialize(Out<u32> out_value);
+    Result GetGroupInfo(OutLargeData<GroupInfo, BufferAttr_HipcAutoSelect> out_group_info);
+};
+
+} // namespace Service::LDN

+ 56 - 0
src/core/hle/service/ldn/system_local_communication_service.cpp

@@ -0,0 +1,56 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ldn/system_local_communication_service.h"
+
+namespace Service::LDN {
+
+ISystemLocalCommunicationService::ISystemLocalCommunicationService(Core::System& system_)
+    : ServiceFramework{system_, "ISystemLocalCommunicationService"} {
+    // clang-format off
+        static const FunctionInfo functions[] = {
+            {0, nullptr, "GetState"},
+            {1, nullptr, "GetNetworkInfo"},
+            {2, nullptr, "GetIpv4Address"},
+            {3, nullptr, "GetDisconnectReason"},
+            {4, nullptr, "GetSecurityParameter"},
+            {5, nullptr, "GetNetworkConfig"},
+            {100, nullptr, "AttachStateChangeEvent"},
+            {101, nullptr, "GetNetworkInfoLatestUpdate"},
+            {102, nullptr, "Scan"},
+            {103, nullptr, "ScanPrivate"},
+            {104, nullptr, "SetWirelessControllerRestriction"},
+            {200, nullptr, "OpenAccessPoint"},
+            {201, nullptr, "CloseAccessPoint"},
+            {202, nullptr, "CreateNetwork"},
+            {203, nullptr, "CreateNetworkPrivate"},
+            {204, nullptr, "DestroyNetwork"},
+            {205, nullptr, "Reject"},
+            {206, nullptr, "SetAdvertiseData"},
+            {207, nullptr, "SetStationAcceptPolicy"},
+            {208, nullptr, "AddAcceptFilterEntry"},
+            {209, nullptr, "ClearAcceptFilter"},
+            {300, nullptr, "OpenStation"},
+            {301, nullptr, "CloseStation"},
+            {302, nullptr, "Connect"},
+            {303, nullptr, "ConnectPrivate"},
+            {304, nullptr, "Disconnect"},
+            {400, nullptr, "InitializeSystem"},
+            {401, nullptr, "FinalizeSystem"},
+            {402, nullptr, "SetOperationMode"},
+            {403, C<&ISystemLocalCommunicationService::InitializeSystem2>, "InitializeSystem2"},
+        };
+    // clang-format on
+
+    RegisterHandlers(functions);
+}
+
+ISystemLocalCommunicationService::~ISystemLocalCommunicationService() = default;
+
+Result ISystemLocalCommunicationService::InitializeSystem2() {
+    LOG_WARNING(Service_LDN, "(STUBBED) called");
+    R_SUCCEED();
+}
+
+} // namespace Service::LDN

+ 25 - 0
src/core/hle/service/ldn/system_local_communication_service.h

@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::LDN {
+
+class ISystemLocalCommunicationService final
+    : public ServiceFramework<ISystemLocalCommunicationService> {
+public:
+    explicit ISystemLocalCommunicationService(Core::System& system_);
+    ~ISystemLocalCommunicationService() override;
+
+private:
+    Result InitializeSystem2();
+};
+
+} // namespace Service::LDN

+ 320 - 0
src/core/hle/service/ldn/user_local_communication_service.cpp

@@ -0,0 +1,320 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <memory>
+
+#include "core/core.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/ldn/ldn_results.h"
+#include "core/hle/service/ldn/ldn_types.h"
+#include "core/hle/service/ldn/user_local_communication_service.h"
+#include "core/hle/service/server_manager.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
+
+namespace Service::LDN {
+
+IUserLocalCommunicationService::IUserLocalCommunicationService(Core::System& system_)
+    : ServiceFramework{system_, "IUserLocalCommunicationService"},
+      service_context{system, "IUserLocalCommunicationService"},
+      room_network{system_.GetRoomNetwork()}, lan_discovery{room_network} {
+    // clang-format off
+        static const FunctionInfo functions[] = {
+            {0, C<&IUserLocalCommunicationService::GetState>, "GetState"},
+            {1, C<&IUserLocalCommunicationService::GetNetworkInfo>, "GetNetworkInfo"},
+            {2, C<&IUserLocalCommunicationService::GetIpv4Address>, "GetIpv4Address"},
+            {3, C<&IUserLocalCommunicationService::GetDisconnectReason>, "GetDisconnectReason"},
+            {4, C<&IUserLocalCommunicationService::GetSecurityParameter>, "GetSecurityParameter"},
+            {5, C<&IUserLocalCommunicationService::GetNetworkConfig>, "GetNetworkConfig"},
+            {100, C<&IUserLocalCommunicationService::AttachStateChangeEvent>, "AttachStateChangeEvent"},
+            {101, C<&IUserLocalCommunicationService::GetNetworkInfoLatestUpdate>, "GetNetworkInfoLatestUpdate"},
+            {102, C<&IUserLocalCommunicationService::Scan>, "Scan"},
+            {103, C<&IUserLocalCommunicationService::ScanPrivate>, "ScanPrivate"},
+            {104, C<&IUserLocalCommunicationService::SetWirelessControllerRestriction>, "SetWirelessControllerRestriction"},
+            {200, C<&IUserLocalCommunicationService::OpenAccessPoint>, "OpenAccessPoint"},
+            {201, C<&IUserLocalCommunicationService::CloseAccessPoint>, "CloseAccessPoint"},
+            {202, C<&IUserLocalCommunicationService::CreateNetwork>, "CreateNetwork"},
+            {203, C<&IUserLocalCommunicationService::CreateNetworkPrivate>, "CreateNetworkPrivate"},
+            {204, C<&IUserLocalCommunicationService::DestroyNetwork>, "DestroyNetwork"},
+            {205, nullptr, "Reject"},
+            {206, C<&IUserLocalCommunicationService::SetAdvertiseData>, "SetAdvertiseData"},
+            {207, C<&IUserLocalCommunicationService::SetStationAcceptPolicy>, "SetStationAcceptPolicy"},
+            {208, C<&IUserLocalCommunicationService::AddAcceptFilterEntry>, "AddAcceptFilterEntry"},
+            {209, nullptr, "ClearAcceptFilter"},
+            {300, C<&IUserLocalCommunicationService::OpenStation>, "OpenStation"},
+            {301, C<&IUserLocalCommunicationService::CloseStation>, "CloseStation"},
+            {302, C<&IUserLocalCommunicationService::Connect>, "Connect"},
+            {303, nullptr, "ConnectPrivate"},
+            {304, C<&IUserLocalCommunicationService::Disconnect>, "Disconnect"},
+            {400, C<&IUserLocalCommunicationService::Initialize>, "Initialize"},
+            {401, C<&IUserLocalCommunicationService::Finalize>, "Finalize"},
+            {402, C<&IUserLocalCommunicationService::Initialize2>, "Initialize2"},
+        };
+    // clang-format on
+
+    RegisterHandlers(functions);
+
+    state_change_event =
+        service_context.CreateEvent("IUserLocalCommunicationService:StateChangeEvent");
+}
+
+IUserLocalCommunicationService::~IUserLocalCommunicationService() {
+    if (is_initialized) {
+        if (auto room_member = room_network.GetRoomMember().lock()) {
+            room_member->Unbind(ldn_packet_received);
+        }
+    }
+
+    service_context.CloseEvent(state_change_event);
+}
+
+Result IUserLocalCommunicationService::GetState(Out<State> out_state) {
+    *out_state = State::Error;
+
+    if (is_initialized) {
+        *out_state = lan_discovery.GetState();
+    }
+
+    LOG_INFO(Service_LDN, "called, state={}", *out_state);
+
+    R_SUCCEED();
+}
+
+Result IUserLocalCommunicationService::GetNetworkInfo(
+    OutLargeData<NetworkInfo, BufferAttr_HipcPointer> out_network_info) {
+    LOG_INFO(Service_LDN, "called");
+
+    R_RETURN(lan_discovery.GetNetworkInfo(*out_network_info));
+}
+
+Result IUserLocalCommunicationService::GetIpv4Address(Out<Ipv4Address> out_current_address,
+                                                      Out<Ipv4Address> out_subnet_mask) {
+    LOG_INFO(Service_LDN, "called");
+    const auto network_interface = Network::GetSelectedNetworkInterface();
+
+    R_UNLESS(network_interface.has_value(), ResultNoIpAddress);
+
+    *out_current_address = {Network::TranslateIPv4(network_interface->ip_address)};
+    *out_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()) {
+            *out_current_address = room_member->GetFakeIpAddress();
+        }
+    }
+
+    std::reverse(std::begin(*out_current_address), std::end(*out_current_address)); // ntohl
+    std::reverse(std::begin(*out_subnet_mask), std::end(*out_subnet_mask));         // ntohl
+    R_SUCCEED();
+}
+
+Result IUserLocalCommunicationService::GetDisconnectReason(
+    Out<DisconnectReason> out_disconnect_reason) {
+    LOG_INFO(Service_LDN, "called");
+
+    *out_disconnect_reason = lan_discovery.GetDisconnectReason();
+    R_SUCCEED();
+}
+
+Result IUserLocalCommunicationService::GetSecurityParameter(
+    Out<SecurityParameter> out_security_parameter) {
+    LOG_INFO(Service_LDN, "called");
+
+    NetworkInfo info{};
+    R_TRY(lan_discovery.GetNetworkInfo(info));
+
+    out_security_parameter->session_id = info.network_id.session_id;
+    std::memcpy(out_security_parameter->data.data(), info.ldn.security_parameter.data(),
+                sizeof(SecurityParameter::data));
+    R_SUCCEED();
+}
+
+Result IUserLocalCommunicationService::GetNetworkConfig(Out<NetworkConfig> out_network_config) {
+    LOG_INFO(Service_LDN, "called");
+
+    NetworkInfo info{};
+    R_TRY(lan_discovery.GetNetworkInfo(info));
+
+    out_network_config->intent_id = info.network_id.intent_id;
+    out_network_config->channel = info.common.channel;
+    out_network_config->node_count_max = info.ldn.node_count_max;
+    out_network_config->local_communication_version = info.ldn.nodes[0].local_communication_version;
+    R_SUCCEED();
+}
+
+Result IUserLocalCommunicationService::AttachStateChangeEvent(
+    OutCopyHandle<Kernel::KReadableEvent> out_event) {
+    LOG_INFO(Service_LDN, "called");
+
+    *out_event = &state_change_event->GetReadableEvent();
+    R_SUCCEED();
+}
+
+Result IUserLocalCommunicationService::GetNetworkInfoLatestUpdate(
+    OutLargeData<NetworkInfo, BufferAttr_HipcPointer> out_network_info,
+    OutArray<NodeLatestUpdate, BufferAttr_HipcPointer> out_node_latest_update) {
+    LOG_INFO(Service_LDN, "called");
+
+    R_UNLESS(!out_node_latest_update.empty(), ResultBadInput);
+
+    R_RETURN(lan_discovery.GetNetworkInfo(*out_network_info, out_node_latest_update));
+}
+
+Result IUserLocalCommunicationService::Scan(
+    Out<s16> network_count, WifiChannel channel, const ScanFilter& scan_filter,
+    OutArray<NetworkInfo, BufferAttr_HipcAutoSelect> out_network_info) {
+    LOG_INFO(Service_LDN, "called, channel={}, filter_scan_flag={}, filter_network_type={}",
+             channel, scan_filter.flag, scan_filter.network_type);
+
+    R_UNLESS(!out_network_info.empty(), ResultBadInput);
+    R_RETURN(lan_discovery.Scan(out_network_info, *network_count, scan_filter));
+}
+
+Result IUserLocalCommunicationService::ScanPrivate(
+    Out<s16> network_count, WifiChannel channel, const ScanFilter& scan_filter,
+    OutArray<NetworkInfo, BufferAttr_HipcAutoSelect> out_network_info) {
+    LOG_INFO(Service_LDN, "called, channel={}, filter_scan_flag={}, filter_network_type={}",
+             channel, scan_filter.flag, scan_filter.network_type);
+
+    R_UNLESS(out_network_info.empty(), ResultBadInput);
+    R_RETURN(lan_discovery.Scan(out_network_info, *network_count, scan_filter));
+}
+
+Result IUserLocalCommunicationService::SetWirelessControllerRestriction(
+    WirelessControllerRestriction wireless_restriction) {
+    LOG_WARNING(Service_LDN, "(STUBBED) called");
+    R_SUCCEED();
+}
+
+Result IUserLocalCommunicationService::OpenAccessPoint() {
+    LOG_INFO(Service_LDN, "called");
+
+    R_RETURN(lan_discovery.OpenAccessPoint());
+}
+
+Result IUserLocalCommunicationService::CloseAccessPoint() {
+    LOG_INFO(Service_LDN, "called");
+
+    R_RETURN(lan_discovery.CloseAccessPoint());
+}
+
+Result IUserLocalCommunicationService::CreateNetwork(const CreateNetworkConfig& create_config) {
+    LOG_INFO(Service_LDN, "called");
+
+    R_RETURN(lan_discovery.CreateNetwork(create_config.security_config, create_config.user_config,
+                                         create_config.network_config));
+}
+
+Result IUserLocalCommunicationService::CreateNetworkPrivate(
+    const CreateNetworkConfigPrivate& create_config,
+    InArray<AddressEntry, BufferAttr_HipcPointer> address_list) {
+    LOG_INFO(Service_LDN, "called");
+
+    R_RETURN(lan_discovery.CreateNetwork(create_config.security_config, create_config.user_config,
+                                         create_config.network_config));
+}
+
+Result IUserLocalCommunicationService::DestroyNetwork() {
+    LOG_INFO(Service_LDN, "called");
+
+    R_RETURN(lan_discovery.DestroyNetwork());
+}
+
+Result IUserLocalCommunicationService::SetAdvertiseData(
+    InBuffer<BufferAttr_HipcAutoSelect> buffer_data) {
+    LOG_INFO(Service_LDN, "called");
+
+    R_RETURN(lan_discovery.SetAdvertiseData(buffer_data));
+}
+
+Result IUserLocalCommunicationService::SetStationAcceptPolicy(AcceptPolicy accept_policy) {
+    LOG_WARNING(Service_LDN, "(STUBBED) called");
+    R_SUCCEED();
+}
+
+Result IUserLocalCommunicationService::AddAcceptFilterEntry(MacAddress mac_address) {
+    LOG_WARNING(Service_LDN, "(STUBBED) called");
+    R_SUCCEED();
+}
+
+Result IUserLocalCommunicationService::OpenStation() {
+    LOG_INFO(Service_LDN, "called");
+
+    R_RETURN(lan_discovery.OpenStation());
+}
+
+Result IUserLocalCommunicationService::CloseStation() {
+    LOG_INFO(Service_LDN, "called");
+
+    R_RETURN(lan_discovery.CloseStation());
+}
+
+Result IUserLocalCommunicationService::Connect(
+    const ConnectNetworkData& connect_data,
+    InLargeData<NetworkInfo, BufferAttr_HipcPointer> network_info) {
+    LOG_INFO(Service_LDN,
+             "called, passphrase_size={}, security_mode={}, "
+             "local_communication_version={}",
+             connect_data.security_config.passphrase_size,
+             connect_data.security_config.security_mode, connect_data.local_communication_version);
+
+    R_RETURN(lan_discovery.Connect(*network_info, connect_data.user_config,
+                                   static_cast<u16>(connect_data.local_communication_version)));
+}
+
+Result IUserLocalCommunicationService::Disconnect() {
+    LOG_INFO(Service_LDN, "called");
+
+    R_RETURN(lan_discovery.Disconnect());
+}
+
+Result IUserLocalCommunicationService::Initialize(ClientProcessId aruid) {
+    LOG_INFO(Service_LDN, "called, process_id={}", aruid.pid);
+
+    const auto network_interface = Network::GetSelectedNetworkInterface();
+    R_UNLESS(network_interface, 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!");
+        R_RETURN(ResultAirplaneModeEnabled);
+    }
+
+    lan_discovery.Initialize([&]() { OnEventFired(); });
+    is_initialized = true;
+    R_SUCCEED();
+}
+
+Result IUserLocalCommunicationService::Finalize() {
+    LOG_INFO(Service_LDN, "called");
+    if (auto room_member = room_network.GetRoomMember().lock()) {
+        room_member->Unbind(ldn_packet_received);
+    }
+
+    is_initialized = false;
+
+    R_RETURN(lan_discovery.Finalize());
+}
+
+Result IUserLocalCommunicationService::Initialize2(u32 version, ClientProcessId process_id) {
+    LOG_INFO(Service_LDN, "called, version={}, process_id={}", version, process_id.pid);
+    R_RETURN(Initialize(process_id));
+}
+
+void IUserLocalCommunicationService::OnLDNPacketReceived(const Network::LDNPacket& packet) {
+    lan_discovery.ReceivePacket(packet);
+}
+
+void IUserLocalCommunicationService::OnEventFired() {
+    state_change_event->Signal();
+}
+
+} // namespace Service::LDN

+ 103 - 0
src/core/hle/service/ldn/user_local_communication_service.h

@@ -0,0 +1,103 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/ldn/lan_discovery.h"
+#include "core/hle/service/ldn/ldn_types.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Network {
+class RoomNetwork;
+}
+
+namespace Service::LDN {
+
+class IUserLocalCommunicationService final
+    : public ServiceFramework<IUserLocalCommunicationService> {
+public:
+    explicit IUserLocalCommunicationService(Core::System& system_);
+    ~IUserLocalCommunicationService() override;
+
+private:
+    Result GetState(Out<State> out_state);
+
+    Result GetNetworkInfo(OutLargeData<NetworkInfo, BufferAttr_HipcPointer> out_network_info);
+
+    Result GetIpv4Address(Out<Ipv4Address> out_current_address, Out<Ipv4Address> out_subnet_mask);
+
+    Result GetDisconnectReason(Out<DisconnectReason> out_disconnect_reason);
+
+    Result GetSecurityParameter(Out<SecurityParameter> out_security_parameter);
+
+    Result GetNetworkConfig(Out<NetworkConfig> out_network_config);
+
+    Result AttachStateChangeEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
+
+    Result GetNetworkInfoLatestUpdate(
+        OutLargeData<NetworkInfo, BufferAttr_HipcPointer> out_network_info,
+        OutArray<NodeLatestUpdate, BufferAttr_HipcPointer> out_node_latest_update);
+
+    Result Scan(Out<s16> network_count, WifiChannel channel, const ScanFilter& scan_filter,
+                OutArray<NetworkInfo, BufferAttr_HipcAutoSelect> out_network_info);
+
+    Result ScanPrivate(Out<s16> network_count, WifiChannel channel, const ScanFilter& scan_filter,
+                       OutArray<NetworkInfo, BufferAttr_HipcAutoSelect> out_network_info);
+
+    Result SetWirelessControllerRestriction(WirelessControllerRestriction wireless_restriction);
+
+    Result OpenAccessPoint();
+
+    Result CloseAccessPoint();
+
+    Result CreateNetwork(const CreateNetworkConfig& create_network_Config);
+
+    Result CreateNetworkPrivate(const CreateNetworkConfigPrivate& create_network_Config,
+                                InArray<AddressEntry, BufferAttr_HipcPointer> address_list);
+
+    Result DestroyNetwork();
+
+    Result SetAdvertiseData(InBuffer<BufferAttr_HipcAutoSelect> buffer_data);
+
+    Result SetStationAcceptPolicy(AcceptPolicy accept_policy);
+
+    Result AddAcceptFilterEntry(MacAddress mac_address);
+
+    Result OpenStation();
+
+    Result CloseStation();
+
+    Result Connect(const ConnectNetworkData& connect_data,
+                   InLargeData<NetworkInfo, BufferAttr_HipcPointer> network_info);
+
+    Result Disconnect();
+
+    Result Initialize(ClientProcessId aruid);
+
+    Result Finalize();
+
+    Result Initialize2(u32 version, ClientProcessId aruid);
+
+private:
+    /// Callback to parse and handle a received LDN packet.
+    void OnLDNPacketReceived(const Network::LDNPacket& packet);
+    void OnEventFired();
+
+    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{};
+};
+
+} // namespace Service::LDN