ldn: Initial implementation
This commit is contained in:
		@@ -500,6 +500,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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										644
									
								
								src/core/hle/service/ldn/lan_discovery.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										644
									
								
								src/core/hle/service/ldn/lan_discovery.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,644 @@
 | 
			
		||||
// 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_} {
 | 
			
		||||
    LOG_INFO(Service_LDN, "LANDiscovery");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LANDiscovery::~LANDiscovery() {
 | 
			
		||||
    LOG_INFO(Service_LDN, "~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 : nodeChanges) {
 | 
			
		||||
        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 = nodeChanges[i].state_change;
 | 
			
		||||
            nodeChanges[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::vector<u8>& data) {
 | 
			
		||||
    std::scoped_lock lock{packet_mutex};
 | 
			
		||||
    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);
 | 
			
		||||
    LanEvent();
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
    LanEvent();
 | 
			
		||||
 | 
			
		||||
    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();
 | 
			
		||||
    LanEvent = 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);
 | 
			
		||||
        LanEvent();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LANDiscovery::OnNetworkInfoChanged() {
 | 
			
		||||
    if (IsNodeStateChanged()) {
 | 
			
		||||
        LanEvent();
 | 
			
		||||
    }
 | 
			
		||||
    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.clear();
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
    packet.data.clear();
 | 
			
		||||
    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.clear();
 | 
			
		||||
    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();
 | 
			
		||||
 | 
			
		||||
    packet.data.clear();
 | 
			
		||||
    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) {
 | 
			
		||||
                nodeChanges[i].state_change |= NodeStateChange::Connect;
 | 
			
		||||
            } else {
 | 
			
		||||
                nodeChanges[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() {
 | 
			
		||||
    int count = 0;
 | 
			
		||||
    for (const auto& station : stations) {
 | 
			
		||||
        if (station.GetStatus() != NodeStatus::Disconnected) {
 | 
			
		||||
            count++;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return count;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
							
								
								
									
										133
									
								
								src/core/hle/service/ldn/lan_discovery.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								src/core/hle/service/ldn/lan_discovery.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,133 @@
 | 
			
		||||
// 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 <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:
 | 
			
		||||
    typedef std::function<void()> LanEventFunc;
 | 
			
		||||
 | 
			
		||||
    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::vector<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();
 | 
			
		||||
    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;
 | 
			
		||||
    const Ssid fake_ssid{"YuzuFakeSsidForLdn"};
 | 
			
		||||
 | 
			
		||||
    bool inited{};
 | 
			
		||||
    std::mutex packet_mutex;
 | 
			
		||||
    std::array<LanStation, StationCountMax> stations;
 | 
			
		||||
    std::array<NodeLatestUpdate, NodeCountMax> nodeChanges{};
 | 
			
		||||
    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 = std::nullopt;
 | 
			
		||||
 | 
			
		||||
    LanEventFunc LanEvent;
 | 
			
		||||
 | 
			
		||||
    Network::RoomNetwork& room_network;
 | 
			
		||||
};
 | 
			
		||||
} // namespace Service::LDN
 | 
			
		||||
@@ -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,52 @@ 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 GetIpv4Address(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        LOG_CRITICAL(Service_LDN, "called");
 | 
			
		||||
 | 
			
		||||
        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(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        const auto disconnect_reason = DisconnectReason::None;
 | 
			
		||||
 | 
			
		||||
        LOG_WARNING(Service_LDN, "(STUBBED) called, disconnect_reason={}", disconnect_reason);
 | 
			
		||||
 | 
			
		||||
        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 +257,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 +265,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 +279,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 +297,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 +315,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 +346,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>()};
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
        rb.Push(ResultSuccess);
 | 
			
		||||
        CreateNetworkImpl(ctx);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void CreateNetworkPrivate(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        LOG_INFO(Service_LDN, "called");
 | 
			
		||||
 | 
			
		||||
        CreateNetworkImpl(ctx, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 +435,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 +460,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 +474,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 +527,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{};
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,14 @@ enum class NodeStateChange : u8 {
 | 
			
		||||
    DisconnectAndConnect,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
inline NodeStateChange operator|(NodeStateChange a, NodeStateChange b) {
 | 
			
		||||
    return static_cast<NodeStateChange>(static_cast<u8>(a) | static_cast<u8>(b));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline NodeStateChange operator|=(NodeStateChange& a, NodeStateChange b) {
 | 
			
		||||
    return a = a | b;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum class ScanFilterFlag : u32 {
 | 
			
		||||
    None = 0,
 | 
			
		||||
    LocalCommunicationId = 1 << 0,
 | 
			
		||||
@@ -100,13 +108,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 +124,11 @@ enum class LinkLevel : s8 {
 | 
			
		||||
    Excellent,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class NodeStatus : u8 {
 | 
			
		||||
    Disconnected,
 | 
			
		||||
    Connected,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct NodeLatestUpdate {
 | 
			
		||||
    NodeStateChange state_change;
 | 
			
		||||
    INSERT_PADDING_BYTES(0x7); // Unknown
 | 
			
		||||
@@ -159,19 +172,14 @@ struct Ssid {
 | 
			
		||||
    std::string GetStringValue() const {
 | 
			
		||||
        return std::string(raw.data());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool operator==(const Ssid& b) const {
 | 
			
		||||
        return (length == b.length) && (std::memcmp(raw.data(), b.raw.data(), length) == 0);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(Ssid) == 0x22, "Ssid is an invalid size");
 | 
			
		||||
 | 
			
		||||
struct Ipv4Address {
 | 
			
		||||
    union {
 | 
			
		||||
        u32 raw{};
 | 
			
		||||
        std::array<u8, 4> bytes;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    std::string GetStringValue() const {
 | 
			
		||||
        return fmt::format("{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
using Ipv4Address = std::array<u8, 4>;
 | 
			
		||||
static_assert(sizeof(Ipv4Address) == 0x4, "Ipv4Address is an invalid size");
 | 
			
		||||
 | 
			
		||||
struct MacAddress {
 | 
			
		||||
@@ -181,6 +189,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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -76,7 +76,8 @@ static constexpr char BanListMagic[] = "YuzuRoom-BanList-1";
 | 
			
		||||
static constexpr char token_delimiter{':'};
 | 
			
		||||
 | 
			
		||||
static void PadToken(std::string& token) {
 | 
			
		||||
    while (token.size() % 4 != 0) {
 | 
			
		||||
    const auto remainder = token.size() % 3;
 | 
			
		||||
    for (size_t i = 0; i < (3 - remainder); i++) {
 | 
			
		||||
        token.push_back('=');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -211,6 +211,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);
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,7 @@ enum RoomMessageTypes : u8 {
 | 
			
		||||
    IdRoomInformation,
 | 
			
		||||
    IdSetGameInfo,
 | 
			
		||||
    IdProxyPacket,
 | 
			
		||||
    IdLdnPacket,
 | 
			
		||||
    IdChatMessage,
 | 
			
		||||
    IdNameCollision,
 | 
			
		||||
    IdIpCollision,
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
@@ -107,6 +108,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);
 | 
			
		||||
@@ -449,6 +480,11 @@ RoomMember::RoomMemberImpl::CallbackSet<ProxyPacket>& RoomMember::RoomMemberImpl
 | 
			
		||||
    return callback_set_proxy_packet;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <>
 | 
			
		||||
RoomMember::RoomMemberImpl::CallbackSet<LDNPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() {
 | 
			
		||||
    return callback_set_ldn_packet;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <>
 | 
			
		||||
RoomMember::RoomMemberImpl::CallbackSet<RoomMember::State>&
 | 
			
		||||
RoomMember::RoomMemberImpl::Callbacks::Get() {
 | 
			
		||||
@@ -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(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>);
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
@@ -151,6 +168,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.
 | 
			
		||||
@@ -204,6 +227,16 @@ public:
 | 
			
		||||
    CallbackHandle<ProxyPacket> BindOnProxyPacketReceived(
 | 
			
		||||
        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.
 | 
			
		||||
 
 | 
			
		||||
@@ -896,8 +896,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"));
 | 
			
		||||
 
 | 
			
		||||
@@ -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>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>&Tools</string>
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -249,6 +249,7 @@ void MultiplayerState::ShowNotification() {
 | 
			
		||||
        return; // Do not show notification if the chat window currently has focus
 | 
			
		||||
    show_notification = true;
 | 
			
		||||
    QApplication::alert(nullptr);
 | 
			
		||||
    QApplication::beep();
 | 
			
		||||
    status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected_notification")).pixmap(16));
 | 
			
		||||
    status_text->setText(tr("New Messages Received"));
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user