diff --git a/patches/1001-fix-token-handling.patch b/patches/1001-fix-token-handling.patch deleted file mode 100644 index 2e22c0a..0000000 --- a/patches/1001-fix-token-handling.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 047c9a2f793cc426e281bb50aa7d073e8f638269 Mon Sep 17 00:00:00 2001 -From: liushuyu -Date: Mon, 12 Sep 2022 23:17:21 -0600 -Subject: [PATCH] dedicated_room: fix token padding ... - -... mebedtls' base64 routine has a strange behavioral issue where if the -input is invalid, it will not report it as invalid, but rather returning -a bunch of garbage data. This new round-tripping padding method should -eliminate such issue. ---- - src/dedicated_room/yuzu_room.cpp | 13 ++++++++++++- - 1 file changed, 12 insertions(+), 1 deletion(-) - -diff --git a/src/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp -index 7b6deba41..359891883 100644 ---- a/src/dedicated_room/yuzu_room.cpp -+++ b/src/dedicated_room/yuzu_room.cpp -@@ -76,7 +76,18 @@ static constexpr char BanListMagic[] = "YuzuRoom-BanList-1"; - static constexpr char token_delimiter{':'}; - - static void PadToken(std::string& token) { -- while (token.size() % 4 != 0) { -+ std::size_t outlen = 0; -+ -+ std::array output{}; -+ std::array roundtrip{}; -+ for (size_t i = 0; i < 3; i++) { -+ mbedtls_base64_decode(output.data(), output.size(), &outlen, -+ reinterpret_cast(token.c_str()), -+ token.length()); -+ mbedtls_base64_encode(roundtrip.data(), roundtrip.size(), &outlen, output.data(), outlen); -+ if (memcmp(roundtrip.data(), token.data(), token.size()) == 0) { -+ break; -+ } - token.push_back('='); - } - } --- -2.37.3 - diff --git a/patches/1001-ldn-full-impl.patch b/patches/1001-ldn-full-impl.patch new file mode 100644 index 0000000..42f905b --- /dev/null +++ b/patches/1001-ldn-full-impl.patch @@ -0,0 +1,2835 @@ +From f5e635addaef59159bf6bc529b17954eda3684a1 Mon Sep 17 00:00:00 2001 +From: FearlessTobi +Date: Sun, 31 Jul 2022 04:46:26 +0200 +Subject: [PATCH 1/5] ldn: Initial implementation + +--- + src/core/CMakeLists.txt | 2 + + src/core/hle/service/ldn/lan_discovery.cpp | 644 +++++++++++++++++++++ + src/core/hle/service/ldn/lan_discovery.h | 133 +++++ + src/core/hle/service/ldn/ldn.cpp | 229 ++++---- + src/core/hle/service/ldn/ldn_types.h | 50 +- + src/core/internal_network/socket_proxy.cpp | 8 +- + src/dedicated_room/yuzu_room.cpp | 3 +- + src/network/room.cpp | 63 ++ + src/network/room.h | 1 + + src/network/room_member.cpp | 57 ++ + src/network/room_member.h | 35 +- + src/yuzu/main.cpp | 4 +- + src/yuzu/main.ui | 14 + + src/yuzu/multiplayer/chat_room.cpp | 12 +- + src/yuzu/multiplayer/state.cpp | 1 + + 15 files changed, 1132 insertions(+), 124 deletions(-) + create mode 100644 src/core/hle/service/ldn/lan_discovery.cpp + create mode 100644 src/core/hle/service/ldn/lan_discovery.h + +diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt +index 806e7ff6c0c1..52017878c9da 100644 +--- a/src/core/CMakeLists.txt ++++ b/src/core/CMakeLists.txt +@@ -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 +diff --git a/src/core/hle/service/ldn/lan_discovery.cpp b/src/core/hle/service/ldn/lan_discovery.cpp +new file mode 100644 +index 000000000000..b04c990771ad +--- /dev/null ++++ b/src/core/hle/service/ldn/lan_discovery.cpp +@@ -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(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& 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& 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(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& 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(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 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(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 ++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 ++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(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(flag); ++ const auto search_flag_value = static_cast(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 +diff --git a/src/core/hle/service/ldn/lan_discovery.h b/src/core/hle/service/ldn/lan_discovery.h +new file mode 100644 +index 000000000000..255342456eba +--- /dev/null ++++ b/src/core/hle/service/ldn/lan_discovery.h +@@ -0,0 +1,133 @@ ++// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#pragma once ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 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& out_updates, ++ std::size_t buffer_count); ++ ++ DisconnectReason GetDisconnectReason() const; ++ Result Scan(std::vector& networks, u16& count, const ScanFilter& filter); ++ Result SetAdvertiseData(std::vector& 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 ++ void SendPacket(Network::LDNPacketType type, const Data& data, Ipv4Address remote_ip); ++ void SendPacket(Network::LDNPacketType type, Ipv4Address remote_ip); ++ template ++ 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 stations; ++ std::array nodeChanges{}; ++ std::array node_last_states{}; ++ std::unordered_map 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 connected_clients; ++ std::optional host_ip = std::nullopt; ++ ++ LanEventFunc LanEvent; ++ ++ Network::RoomNetwork& room_network; ++}; ++} // namespace Service::LDN +diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp +index c11daff547b6..6537f49cf2e9 100644 +--- a/src/core/hle/service/ldn/ldn.cpp ++++ b/src/core/hle/service/ldn/ldn.cpp +@@ -4,11 +4,13 @@ + #include + + #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 @@ class IUserLocalCommunicationService final + {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 @@ class IUserLocalCommunicationService final + } + + ~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 @@ class IUserLocalCommunicationService final + } + + 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 @@ class IUserLocalCommunicationService final + return; + } + +- LOG_WARNING(Service_LDN, "(STUBBED) called, ssid='{}', nodes={}", +- network_info.common.ssid.GetStringValue(), network_info.ldn.node_count); +- + ctx.WriteBuffer(network_info); + IPC::ResponseBuilder rb{ctx, 2}; +- rb.Push(rc); ++ rb.Push(ResultSuccess); + } + +- void GetDisconnectReason(Kernel::HLERequestContext& ctx) { +- const auto disconnect_reason = DisconnectReason::None; ++ void GetIpv4Address(Kernel::HLERequestContext& ctx) { ++ LOG_CRITICAL(Service_LDN, "called"); + +- LOG_WARNING(Service_LDN, "(STUBBED) called, disconnect_reason={}", disconnect_reason); ++ 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) { + 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 @@ class IUserLocalCommunicationService final + 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(security_parameter); +@@ -227,7 +265,7 @@ class IUserLocalCommunicationService final + 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 @@ class IUserLocalCommunicationService final + 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(config); +@@ -265,17 +297,17 @@ class IUserLocalCommunicationService final + 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 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 @@ class IUserLocalCommunicationService final + 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 @@ class IUserLocalCommunicationService final + + u16 count = 0; + std::vector 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(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()}; ++ CreateNetworkImpl(ctx); ++ } + +- LOG_WARNING(Service_LDN, +- "(STUBBED) called, passphrase_size={}, security_mode={}, " +- "local_communication_version={}", +- parameters.security_config.passphrase_size, +- parameters.security_config.security_mode, +- parameters.network_config.local_communication_version); ++ void CreateNetworkPrivate(Kernel::HLERequestContext& ctx) { ++ LOG_INFO(Service_LDN, "called"); + +- IPC::ResponseBuilder rb{ctx, 2}; +- rb.Push(ResultSuccess); ++ CreateNetworkImpl(ctx, true); + } + +- void CreateNetworkPrivate(Kernel::HLERequestContext& ctx) { ++ void CreateNetworkImpl(Kernel::HLERequestContext& ctx, bool is_private = false) { + IPC::RequestParser rp{ctx}; +- struct Parameters { +- SecurityConfig security_config; +- SecurityParameter security_parameter; +- UserConfig user_config; +- NetworkConfig network_config; +- }; +- static_assert(sizeof(Parameters) == 0xB8, "Parameters has incorrect size."); +- +- const auto parameters{rp.PopRaw()}; + +- 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()}; ++ [[maybe_unused]] const auto security_parameter{is_private ? rp.PopRaw() ++ : SecurityParameter{}}; ++ const auto user_config{rp.PopRaw()}; ++ rp.Pop(); // Padding ++ const auto network_Config{rp.PopRaw()}; + + 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 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 @@ class IUserLocalCommunicationService final + } + + 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 @@ class IUserLocalCommunicationService final + + const auto parameters{rp.PopRaw()}; + +- 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 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 @@ class IUserLocalCommunicationService final + 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(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 @@ class IUserLocalCommunicationService final + 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 ldn_packet_received; + + bool is_initialized{}; + }; +diff --git a/src/core/hle/service/ldn/ldn_types.h b/src/core/hle/service/ldn/ldn_types.h +index 6231e936dade..d6609fff55fd 100644 +--- a/src/core/hle/service/ldn/ldn_types.h ++++ b/src/core/hle/service/ldn/ldn_types.h +@@ -31,6 +31,14 @@ enum class NodeStateChange : u8 { + DisconnectAndConnect, + }; + ++inline NodeStateChange operator|(NodeStateChange a, NodeStateChange b) { ++ return static_cast(static_cast(a) | static_cast(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()); + } +-}; +-static_assert(sizeof(Ssid) == 0x22, "Ssid is an invalid size"); +- +-struct Ipv4Address { +- union { +- u32 raw{}; +- std::array bytes; +- }; + +- std::string GetStringValue() const { +- return fmt::format("{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]); ++ 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"); ++ ++using Ipv4Address = std::array; + 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; +diff --git a/src/core/internal_network/socket_proxy.cpp b/src/core/internal_network/socket_proxy.cpp +index 0c746bd82427..7d5d37bbcd7f 100644 +--- a/src/core/internal_network/socket_proxy.cpp ++++ b/src/core/internal_network/socket_proxy.cpp +@@ -6,6 +6,7 @@ + + #include "common/assert.h" + #include "common/logging/log.h" ++#include "common/zstd_compression.h" + #include "core/internal_network/network.h" + #include "core/internal_network/network_interface.h" + #include "core/internal_network/socket_proxy.h" +@@ -32,8 +33,11 @@ void ProxySocket::HandleProxyPacket(const ProxyPacket& packet) { + return; + } + ++ auto decompressed = packet; ++ decompressed.data = Common::Compression::DecompressDataZSTD(packet.data); ++ + std::lock_guard guard(packets_mutex); +- received_packets.push(packet); ++ received_packets.push(decompressed); + } + + template +@@ -185,6 +189,8 @@ std::pair ProxySocket::Send(const std::vector& message, int flag + void ProxySocket::SendPacket(ProxyPacket& packet) { + if (auto room_member = room_network.GetRoomMember().lock()) { + if (room_member->IsConnected()) { ++ packet.data = Common::Compression::CompressDataZSTDDefault(packet.data.data(), ++ packet.data.size()); + room_member->SendProxyPacket(packet); + } + } +diff --git a/src/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp +index 7b6deba417fd..8d8ac1ed74f1 100644 +--- a/src/dedicated_room/yuzu_room.cpp ++++ b/src/dedicated_room/yuzu_room.cpp +@@ -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('='); + } + } +diff --git a/src/network/room.cpp b/src/network/room.cpp +index 8c63b255bc29..dc5dbce7fa95 100644 +--- a/src/network/room.cpp ++++ b/src/network/room.cpp +@@ -211,6 +211,12 @@ class Room::RoomImpl { + */ + void HandleProxyPacket(const ENetEvent* event); + ++ /** ++ * Broadcasts this packet to all members except the sender. ++ * @param event The ENet event containing the data ++ */ ++ void HandleLdnPacket(const ENetEvent* event); ++ + /** + * Extracts a chat entry from a received ENet packet and adds it to the chat queue. + * @param event The ENet event that was received. +@@ -247,6 +253,9 @@ void Room::RoomImpl::ServerLoop() { + case IdProxyPacket: + HandleProxyPacket(&event); + break; ++ case IdLdnPacket: ++ HandleLdnPacket(&event); ++ break; + case IdChatMessage: + HandleChatPacket(&event); + break; +@@ -861,6 +870,60 @@ void Room::RoomImpl::HandleProxyPacket(const ENetEvent* event) { + enet_host_flush(server); + } + ++void Room::RoomImpl::HandleLdnPacket(const ENetEvent* event) { ++ Packet in_packet; ++ in_packet.Append(event->packet->data, event->packet->dataLength); ++ ++ in_packet.IgnoreBytes(sizeof(u8)); // Message type ++ ++ in_packet.IgnoreBytes(sizeof(u8)); // LAN packet type ++ in_packet.IgnoreBytes(sizeof(IPv4Address)); // Local IP ++ ++ IPv4Address remote_ip; ++ in_packet.Read(remote_ip); // Remote IP ++ ++ bool broadcast; ++ in_packet.Read(broadcast); // Broadcast ++ ++ Packet out_packet; ++ out_packet.Append(event->packet->data, event->packet->dataLength); ++ ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(), ++ ENET_PACKET_FLAG_RELIABLE); ++ ++ const auto& destination_address = remote_ip; ++ if (broadcast) { // Send the data to everyone except the sender ++ std::lock_guard lock(member_mutex); ++ bool sent_packet = false; ++ for (const auto& member : members) { ++ if (member.peer != event->peer) { ++ sent_packet = true; ++ enet_peer_send(member.peer, 0, enet_packet); ++ } ++ } ++ ++ if (!sent_packet) { ++ enet_packet_destroy(enet_packet); ++ } ++ } else { ++ std::lock_guard lock(member_mutex); ++ auto member = std::find_if(members.begin(), members.end(), ++ [destination_address](const Member& member_entry) -> bool { ++ return member_entry.fake_ip == destination_address; ++ }); ++ if (member != members.end()) { ++ enet_peer_send(member->peer, 0, enet_packet); ++ } else { ++ LOG_ERROR(Network, ++ "Attempting to send to unknown IP address: " ++ "{}.{}.{}.{}", ++ destination_address[0], destination_address[1], destination_address[2], ++ destination_address[3]); ++ enet_packet_destroy(enet_packet); ++ } ++ } ++ enet_host_flush(server); ++} ++ + void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) { + Packet in_packet; + in_packet.Append(event->packet->data, event->packet->dataLength); +diff --git a/src/network/room.h b/src/network/room.h +index c2a4b1a70240..edbd3ecfb23f 100644 +--- a/src/network/room.h ++++ b/src/network/room.h +@@ -40,6 +40,7 @@ enum RoomMessageTypes : u8 { + IdRoomInformation, + IdSetGameInfo, + IdProxyPacket, ++ IdLdnPacket, + IdChatMessage, + IdNameCollision, + IdIpCollision, +diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp +index 06818af783c7..572e55a5b83c 100644 +--- a/src/network/room_member.cpp ++++ b/src/network/room_member.cpp +@@ -58,6 +58,7 @@ class RoomMember::RoomMemberImpl { + + private: + CallbackSet callback_set_proxy_packet; ++ CallbackSet callback_set_ldn_packet; + CallbackSet callback_set_chat_messages; + CallbackSet callback_set_status_messages; + CallbackSet callback_set_room_information; +@@ -107,6 +108,12 @@ class RoomMember::RoomMemberImpl { + */ + 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(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(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(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& RoomMember::RoomMemberImpl + return callback_set_proxy_packet; + } + ++template <> ++RoomMember::RoomMemberImpl::CallbackSet& RoomMember::RoomMemberImpl::Callbacks::Get() { ++ return callback_set_ldn_packet; ++} ++ + template <> + RoomMember::RoomMemberImpl::CallbackSet& + 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(IdLdnPacket)); ++ ++ packet.Write(static_cast(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(IdChatMessage)); +@@ -663,6 +714,11 @@ RoomMember::CallbackHandle RoomMember::BindOnProxyPacketReceived( + return room_member_impl->Bind(callback); + } + ++RoomMember::CallbackHandle RoomMember::BindOnLdnPacketReceived( ++ std::function callback) { ++ return room_member_impl->Bind(callback); ++} ++ + RoomMember::CallbackHandle RoomMember::BindOnRoomInformationChanged( + std::function callback) { + return room_member_impl->Bind(callback); +@@ -699,6 +755,7 @@ void RoomMember::Leave() { + } + + template void RoomMember::Unbind(CallbackHandle); ++template void RoomMember::Unbind(CallbackHandle); + template void RoomMember::Unbind(CallbackHandle); + template void RoomMember::Unbind(CallbackHandle); + template void RoomMember::Unbind(CallbackHandle); +diff --git a/src/network/room_member.h b/src/network/room_member.h +index f578f7f6a31a..0d6417294579 100644 +--- a/src/network/room_member.h ++++ b/src/network/room_member.h +@@ -17,7 +17,24 @@ namespace Network { + using AnnounceMultiplayerRoom::GameInfo; + using AnnounceMultiplayerRoom::RoomInformation; + +-/// Information about the received WiFi packets. ++enum class LDNPacketType : u8 { ++ Scan, ++ ScanResp, ++ Connect, ++ SyncNetwork, ++ Disconnect, ++ DestroyNetwork, ++}; ++ ++struct LDNPacket { ++ LDNPacketType type; ++ IPv4Address local_ip; ++ IPv4Address remote_ip; ++ bool broadcast; ++ std::vector data; ++}; ++ ++/// Information about the received proxy packets. + struct ProxyPacket { + SockAddrIn local_endpoint; + SockAddrIn remote_endpoint; +@@ -151,6 +168,12 @@ class RoomMember final { + */ + 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 @@ class RoomMember final { + CallbackHandle BindOnProxyPacketReceived( + std::function 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 BindOnLdnPacketReceived( ++ std::function 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. +diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp +index a85adc072429..9dfa8d639c26 100644 +--- a/src/yuzu/main.cpp ++++ b/src/yuzu/main.cpp +@@ -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")); +diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui +index cdf31b417d75..60a8deab1c9a 100644 +--- a/src/yuzu/main.ui ++++ b/src/yuzu/main.ui +@@ -120,6 +120,20 @@ + + + ++ ++ ++ true ++ ++ ++ Multiplayer ++ ++ ++ ++ ++ ++ ++ ++ + + + &Tools +diff --git a/src/yuzu/multiplayer/chat_room.cpp b/src/yuzu/multiplayer/chat_room.cpp +index 9e672f82e98d..dec9696c186e 100644 +--- a/src/yuzu/multiplayer/chat_room.cpp ++++ b/src/yuzu/multiplayer/chat_room.cpp +@@ -61,7 +61,10 @@ class ChatMessage { + + /// 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 @@ class ChatMessage { + } + + private: +- static constexpr std::array player_color = { ++ static constexpr std::array 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 player_color_dark = { ++ {"#559AD1", "#4EC9A8", "#D69D85", "#C6C923", "#B975B5", "#D81F1F", "#7EAE39", "#4F8733", ++ "#F7CD8A", "#6FCACF", "#CE4897", "#8A2BE2", "#D2691E", "#9ACD32", "#FF7F50", "#152ccd"}}; + static constexpr char ping_color[] = "#FFFF00"; + + QString timestamp; +diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp +index 66e098296d02..3ad8460281db 100644 +--- a/src/yuzu/multiplayer/state.cpp ++++ b/src/yuzu/multiplayer/state.cpp +@@ -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")); + } + +From 8f207bd93ddb9778f0242fca0dab6ef155bd2a97 Mon Sep 17 00:00:00 2001 +From: german77 +Date: Fri, 9 Sep 2022 15:29:22 -0500 +Subject: [PATCH 2/5] yuzu: Multiple room UI improvements + +--- + src/core/hle/service/hid/hid.cpp | 3 +- + .../internal_network/network_interface.cpp | 10 +++ + src/core/internal_network/network_interface.h | 1 + + src/yuzu/main.cpp | 8 ++ + src/yuzu/main.h | 1 + + src/yuzu/main.ui | 10 +-- + src/yuzu/multiplayer/client_room.cpp | 3 +- + src/yuzu/multiplayer/direct_connect.cpp | 2 + + src/yuzu/multiplayer/direct_connect.h | 1 + + src/yuzu/multiplayer/host_room.cpp | 1 + + src/yuzu/multiplayer/host_room.h | 3 + + src/yuzu/multiplayer/lobby.cpp | 67 +++++++++++----- + src/yuzu/multiplayer/lobby.h | 8 ++ + src/yuzu/multiplayer/lobby_p.h | 16 ++-- + src/yuzu/multiplayer/message.cpp | 6 +- + src/yuzu/multiplayer/state.cpp | 79 +++++++++++++------ + src/yuzu/multiplayer/state.h | 14 ++++ + src/yuzu/uisettings.h | 2 +- + 18 files changed, 176 insertions(+), 59 deletions(-) + +diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp +index 3d3457160030..7e923462baf5 100644 +--- a/src/core/hle/service/hid/hid.cpp ++++ b/src/core/hle/service/hid/hid.cpp +@@ -35,7 +35,8 @@ namespace Service::HID { + + // Updating period for each HID device. + // Period time is obtained by measuring the number of samples in a second on HW using a homebrew +-constexpr auto pad_update_ns = std::chrono::nanoseconds{4 * 1000 * 1000}; // (4ms, 250Hz) ++// Correct pad_update_ns is 4ms this is overclocked to lower input lag ++constexpr auto pad_update_ns = std::chrono::nanoseconds{1 * 1000 * 1000}; // (1ms, 1000Hz) + constexpr auto mouse_keyboard_update_ns = std::chrono::nanoseconds{8 * 1000 * 1000}; // (8ms, 125Hz) + constexpr auto motion_update_ns = std::chrono::nanoseconds{5 * 1000 * 1000}; // (5ms, 200Hz) + +diff --git a/src/core/internal_network/network_interface.cpp b/src/core/internal_network/network_interface.cpp +index 0f0a661606c8..858ae1cfbbb2 100644 +--- a/src/core/internal_network/network_interface.cpp ++++ b/src/core/internal_network/network_interface.cpp +@@ -206,4 +206,14 @@ std::optional GetSelectedNetworkInterface() { + return *res; + } + ++void SelectFirstNetworkInterface() { ++ const auto network_interfaces = Network::GetAvailableNetworkInterfaces(); ++ ++ if (network_interfaces.size() == 0) { ++ return; ++ } ++ ++ Settings::values.network_interface.SetValue(network_interfaces[0].name); ++} ++ + } // namespace Network +diff --git a/src/core/internal_network/network_interface.h b/src/core/internal_network/network_interface.h +index 9b98b6b4204d..175e61b1f9f6 100644 +--- a/src/core/internal_network/network_interface.h ++++ b/src/core/internal_network/network_interface.h +@@ -24,5 +24,6 @@ struct NetworkInterface { + + std::vector GetAvailableNetworkInterfaces(); + std::optional GetSelectedNetworkInterface(); ++void SelectFirstNetworkInterface(); + + } // namespace Network +diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp +index 9dfa8d639c26..deee1c370fb9 100644 +--- a/src/yuzu/main.cpp ++++ b/src/yuzu/main.cpp +@@ -1296,6 +1296,7 @@ void GMainWindow::ConnectMenuEvents() { + &MultiplayerState::OnDirectConnectToRoom); + connect(ui->action_Show_Room, &QAction::triggered, multiplayer_state, + &MultiplayerState::OnOpenNetworkRoom); ++ connect(multiplayer_state, &MultiplayerState::SaveConfig, this, &GMainWindow::OnSaveConfig); + + // Tools + connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this, +@@ -1336,6 +1337,8 @@ void GMainWindow::UpdateMenuState() { + } else { + ui->action_Pause->setText(tr("&Pause")); + } ++ ++ multiplayer_state->UpdateNotificationStatus(); + } + + void GMainWindow::OnDisplayTitleBars(bool show) { +@@ -2766,6 +2769,11 @@ void GMainWindow::OnExit() { + OnStopGame(); + } + ++void GMainWindow::OnSaveConfig() { ++ system->ApplySettings(); ++ config->Save(); ++} ++ + void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_text) { + OverlayDialog dialog(render_window, *system, error_code, error_text, QString{}, tr("OK"), + Qt::AlignLeft | Qt::AlignVCenter); +diff --git a/src/yuzu/main.h b/src/yuzu/main.h +index 1ae2b93d9b23..1a756b171e33 100644 +--- a/src/yuzu/main.h ++++ b/src/yuzu/main.h +@@ -169,6 +169,7 @@ public slots: + void OnLoadComplete(); + void OnExecuteProgram(std::size_t program_index); + void OnExit(); ++ void OnSaveConfig(); + void ControllerSelectorReconfigureControllers( + const Core::Frontend::ControllerParameters& parameters); + void SoftwareKeyboardInitialize( +diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui +index 60a8deab1c9a..de1545216a81 100644 +--- a/src/yuzu/main.ui ++++ b/src/yuzu/main.ui +@@ -265,7 +265,7 @@ + true + + +- Browse Public Game Lobby ++ &Browse Public Game Lobby + + + +@@ -273,7 +273,7 @@ + true + + +- Create Room ++ &Create Room + + + +@@ -281,12 +281,12 @@ + false + + +- Leave Room ++ &Leave Room + + + + +- Direct Connect to Room ++ &Direct Connect to Room + + + +@@ -294,7 +294,7 @@ + false + + +- Show Current Room ++ &Show Current Room + + + +diff --git a/src/yuzu/multiplayer/client_room.cpp b/src/yuzu/multiplayer/client_room.cpp +index b34a8d004abf..caf34a414fc6 100644 +--- a/src/yuzu/multiplayer/client_room.cpp ++++ b/src/yuzu/multiplayer/client_room.cpp +@@ -97,8 +97,9 @@ void ClientRoomWindow::UpdateView() { + auto memberlist = member->GetMemberInformation(); + ui->chat->SetPlayerList(memberlist); + const auto information = member->GetRoomInformation(); +- setWindowTitle(QString(tr("%1 (%2/%3 members) - connected")) ++ setWindowTitle(QString(tr("%1 - %2 (%3/%4 members) - connected")) + .arg(QString::fromStdString(information.name)) ++ .arg(QString::fromStdString(information.preferred_game.name)) + .arg(memberlist.size()) + .arg(information.member_slots)); + ui->description->setText(QString::fromStdString(information.description)); +diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp +index 017063074479..10bf0a4fb3d3 100644 +--- a/src/yuzu/multiplayer/direct_connect.cpp ++++ b/src/yuzu/multiplayer/direct_connect.cpp +@@ -106,6 +106,8 @@ void DirectConnectWindow::Connect() { + UISettings::values.multiplayer_port = UISettings::values.multiplayer_port.GetDefault(); + } + ++ emit SaveConfig(); ++ + // attempt to connect in a different thread + QFuture f = QtConcurrent::run([&] { + if (auto room_member = room_network.GetRoomMember().lock()) { +diff --git a/src/yuzu/multiplayer/direct_connect.h b/src/yuzu/multiplayer/direct_connect.h +index e39dd1e0d6df..b8f66cfb2b1f 100644 +--- a/src/yuzu/multiplayer/direct_connect.h ++++ b/src/yuzu/multiplayer/direct_connect.h +@@ -31,6 +31,7 @@ class DirectConnectWindow : public QDialog { + * connections that it might have. + */ + void Closed(); ++ void SaveConfig(); + + private slots: + void OnConnection(); +diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp +index 0c6adfd04007..a8faa5b248ba 100644 +--- a/src/yuzu/multiplayer/host_room.cpp ++++ b/src/yuzu/multiplayer/host_room.cpp +@@ -232,6 +232,7 @@ void HostRoomWindow::Host() { + } + UISettings::values.multiplayer_room_description = ui->room_description->toPlainText(); + ui->host->setEnabled(true); ++ emit SaveConfig(); + close(); + } + } +diff --git a/src/yuzu/multiplayer/host_room.h b/src/yuzu/multiplayer/host_room.h +index 034cb2eefd31..ae816e2e03c1 100644 +--- a/src/yuzu/multiplayer/host_room.h ++++ b/src/yuzu/multiplayer/host_room.h +@@ -46,6 +46,9 @@ class HostRoomWindow : public QDialog { + void UpdateGameList(QStandardItemModel* list); + void RetranslateUi(); + ++signals: ++ void SaveConfig(); ++ + private: + void Host(); + std::unique_ptr CreateVerifyBackend(bool use_validation) const; +diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp +index 107d40547679..08c275696463 100644 +--- a/src/yuzu/multiplayer/lobby.cpp ++++ b/src/yuzu/multiplayer/lobby.cpp +@@ -7,6 +7,7 @@ + #include "common/logging/log.h" + #include "common/settings.h" + #include "core/core.h" ++#include "core/hle/service/acc/profile_manager.h" + #include "core/internal_network/network_interface.h" + #include "network/network.h" + #include "ui_lobby.h" +@@ -26,9 +27,9 @@ + Lobby::Lobby(QWidget* parent, QStandardItemModel* list, + std::shared_ptr session, Core::System& system_) + : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), +- ui(std::make_unique()), +- announce_multiplayer_session(session), system{system_}, room_network{ +- system.GetRoomNetwork()} { ++ ui(std::make_unique()), announce_multiplayer_session(session), ++ profile_manager(std::make_unique()), system{system_}, ++ room_network{system.GetRoomNetwork()} { + ui->setupUi(this); + + // setup the watcher for background connections +@@ -60,9 +61,17 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list, + + ui->nickname->setValidator(validation.GetNickname()); + ui->nickname->setText(UISettings::values.multiplayer_nickname.GetValue()); +- if (ui->nickname->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) { +- // Use yuzu Web Service user name as nickname by default +- ui->nickname->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue())); ++ ++ // Try find the best nickname by default ++ if (ui->nickname->text().isEmpty() || ui->nickname->text() == QStringLiteral("yuzu")) { ++ if (!Settings::values.yuzu_username.GetValue().empty()) { ++ ui->nickname->setText( ++ QString::fromStdString(Settings::values.yuzu_username.GetValue())); ++ } else if (!GetProfileUsername().empty()) { ++ ui->nickname->setText(QString::fromStdString(GetProfileUsername())); ++ } else { ++ ui->nickname->setText(QStringLiteral("yuzu")); ++ } + } + + // UI Buttons +@@ -76,12 +85,6 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list, + // Actions + connect(&room_list_watcher, &QFutureWatcher::finished, this, + &Lobby::OnRefreshLobby); +- +- // manually start a refresh when the window is opening +- // TODO(jroweboy): if this refresh is slow for people with bad internet, then don't do it as +- // part of the constructor, but offload the refresh until after the window shown. perhaps emit a +- // refreshroomlist signal from places that open the lobby +- RefreshLobby(); + } + + Lobby::~Lobby() = default; +@@ -96,6 +99,7 @@ void Lobby::UpdateGameList(QStandardItemModel* list) { + } + if (proxy) + proxy->UpdateGameList(game_list); ++ ui->room_list->sortByColumn(Column::GAME_NAME, Qt::AscendingOrder); + } + + void Lobby::RetranslateUi() { +@@ -116,6 +120,11 @@ void Lobby::OnExpandRoom(const QModelIndex& index) { + } + + void Lobby::OnJoinRoom(const QModelIndex& source) { ++ if (!Network::GetSelectedNetworkInterface()) { ++ LOG_INFO(WebService, "Automatically selected network interface for room network."); ++ Network::SelectFirstNetworkInterface(); ++ } ++ + if (!Network::GetSelectedNetworkInterface()) { + NetworkMessage::ErrorManager::ShowError( + NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED); +@@ -197,16 +206,16 @@ void Lobby::OnJoinRoom(const QModelIndex& source) { + proxy->data(connection_index, LobbyItemHost::HostIPRole).toString(); + UISettings::values.multiplayer_port = + proxy->data(connection_index, LobbyItemHost::HostPortRole).toInt(); ++ emit SaveConfig(); + } + + void Lobby::ResetModel() { + model->clear(); + model->insertColumns(0, Column::TOTAL); +- model->setHeaderData(Column::EXPAND, Qt::Horizontal, QString(), Qt::DisplayRole); ++ model->setHeaderData(Column::MEMBER, Qt::Horizontal, tr("Players"), Qt::DisplayRole); + model->setHeaderData(Column::ROOM_NAME, Qt::Horizontal, tr("Room Name"), Qt::DisplayRole); + model->setHeaderData(Column::GAME_NAME, Qt::Horizontal, tr("Preferred Game"), Qt::DisplayRole); + model->setHeaderData(Column::HOST, Qt::Horizontal, tr("Host"), Qt::DisplayRole); +- model->setHeaderData(Column::MEMBER, Qt::Horizontal, tr("Players"), Qt::DisplayRole); + } + + void Lobby::RefreshLobby() { +@@ -229,6 +238,7 @@ void Lobby::OnRefreshLobby() { + for (int r = 0; r < game_list->rowCount(); ++r) { + auto index = game_list->index(r, 0); + auto game_id = game_list->data(index, GameListItemPath::ProgramIdRole).toULongLong(); ++ + if (game_id != 0 && room.information.preferred_game.id == game_id) { + smdh_icon = game_list->data(index, Qt::DecorationRole).value(); + } +@@ -243,17 +253,16 @@ void Lobby::OnRefreshLobby() { + members.append(var); + } + +- auto first_item = new LobbyItem(); ++ auto first_item = new LobbyItemGame( ++ room.information.preferred_game.id, ++ QString::fromStdString(room.information.preferred_game.name), smdh_icon); + auto row = QList({ + first_item, + new LobbyItemName(room.has_password, QString::fromStdString(room.information.name)), +- new LobbyItemGame(room.information.preferred_game.id, +- QString::fromStdString(room.information.preferred_game.name), +- smdh_icon), ++ new LobbyItemMemberList(members, room.information.member_slots), + new LobbyItemHost(QString::fromStdString(room.information.host_username), + QString::fromStdString(room.ip), room.information.port, + QString::fromStdString(room.verify_uid)), +- new LobbyItemMemberList(members, room.information.member_slots), + }); + model->appendRow(row); + // To make the rows expandable, add the member data as a child of the first column of the +@@ -283,6 +292,26 @@ void Lobby::OnRefreshLobby() { + ui->room_list->setFirstColumnSpanned(j, proxy->index(i, 0), true); + } + } ++ ++ ui->room_list->sortByColumn(Column::GAME_NAME, Qt::AscendingOrder); ++} ++ ++std::string Lobby::GetProfileUsername() { ++ const auto& current_user = profile_manager->GetUser(Settings::values.current_user.GetValue()); ++ Service::Account::ProfileBase profile{}; ++ ++ if (!current_user.has_value()) { ++ return ""; ++ } ++ ++ if (!profile_manager->GetProfileBase(*current_user, profile)) { ++ return ""; ++ } ++ ++ const auto text = Common::StringFromFixedZeroTerminatedBuffer( ++ reinterpret_cast(profile.username.data()), profile.username.size()); ++ ++ return text; + } + + LobbyFilterProxyModel::LobbyFilterProxyModel(QWidget* parent, QStandardItemModel* list) +diff --git a/src/yuzu/multiplayer/lobby.h b/src/yuzu/multiplayer/lobby.h +index 2696aec21a52..300dad13e11a 100644 +--- a/src/yuzu/multiplayer/lobby.h ++++ b/src/yuzu/multiplayer/lobby.h +@@ -24,6 +24,10 @@ namespace Core { + class System; + } + ++namespace Service::Account { ++class ProfileManager; ++} ++ + /** + * Listing of all public games pulled from services. The lobby should be simple enough for users to + * find the game they want to play, and join it. +@@ -75,8 +79,11 @@ private slots: + + signals: + void StateChanged(const Network::RoomMember::State&); ++ void SaveConfig(); + + private: ++ std::string GetProfileUsername(); ++ + /** + * Removes all entries in the Lobby before refreshing. + */ +@@ -96,6 +103,7 @@ private slots: + + QFutureWatcher room_list_watcher; + std::weak_ptr announce_multiplayer_session; ++ std::unique_ptr profile_manager; + QFutureWatcher* watcher; + Validation validation; + Core::System& system; +diff --git a/src/yuzu/multiplayer/lobby_p.h b/src/yuzu/multiplayer/lobby_p.h +index 8071cede4d49..8b17075062b3 100644 +--- a/src/yuzu/multiplayer/lobby_p.h ++++ b/src/yuzu/multiplayer/lobby_p.h +@@ -11,11 +11,10 @@ + + namespace Column { + enum List { +- EXPAND, +- ROOM_NAME, + GAME_NAME, +- HOST, ++ ROOM_NAME, + MEMBER, ++ HOST, + TOTAL, + }; + } +@@ -98,7 +97,12 @@ class LobbyItemGame : public LobbyItem { + if (role == Qt::DecorationRole) { + auto val = data(GameIconRole); + if (val.isValid()) { +- val = val.value().scaled(16, 16, Qt::KeepAspectRatio); ++ val = val.value().scaled(32, 32, Qt::KeepAspectRatio, ++ Qt::TransformationMode::SmoothTransformation); ++ } else { ++ auto blank_image = QPixmap(32, 32); ++ blank_image.fill(Qt::black); ++ val = blank_image; + } + return val; + } else if (role != Qt::DisplayRole) { +@@ -191,8 +195,8 @@ class LobbyItemMemberList : public LobbyItem { + return LobbyItem::data(role); + } + auto members = data(MemberListRole).toList(); +- return QStringLiteral("%1 / %2").arg(QString::number(members.size()), +- data(MaxPlayerRole).toString()); ++ return QStringLiteral("%1 / %2 ") ++ .arg(QString::number(members.size()), data(MaxPlayerRole).toString()); + } + + bool operator<(const QStandardItem& other) const override { +diff --git a/src/yuzu/multiplayer/message.cpp b/src/yuzu/multiplayer/message.cpp +index 758b5b731d9a..6d8f18274f56 100644 +--- a/src/yuzu/multiplayer/message.cpp ++++ b/src/yuzu/multiplayer/message.cpp +@@ -49,9 +49,9 @@ const ConnectionError ErrorManager::PERMISSION_DENIED( + QT_TR_NOOP("You do not have enough permission to perform this action.")); + const ConnectionError ErrorManager::NO_SUCH_USER(QT_TR_NOOP( + "The user you are trying to kick/ban could not be found.\nThey may have left the room.")); +-const ConnectionError ErrorManager::NO_INTERFACE_SELECTED( +- QT_TR_NOOP("No network interface is selected.\nPlease go to Configure -> System -> Network and " +- "make a selection.")); ++const ConnectionError ErrorManager::NO_INTERFACE_SELECTED(QT_TR_NOOP( ++ "No valid network interface is selected.\nPlease go to Configure -> System -> Network and " ++ "make a selection.")); + + static bool WarnMessage(const std::string& title, const std::string& text) { + return QMessageBox::Ok == QMessageBox::warning(nullptr, QObject::tr(title.c_str()), +diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp +index 3ad8460281db..ae2738ad4c17 100644 +--- a/src/yuzu/multiplayer/state.cpp ++++ b/src/yuzu/multiplayer/state.cpp +@@ -44,9 +44,6 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis + + status_text = new ClickableLabel(this); + status_icon = new ClickableLabel(this); +- status_text->setToolTip(tr("Current connection status")); +- status_text->setText(tr("Not Connected. Click here to find a room!")); +- status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16)); + + connect(status_text, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom); + connect(status_icon, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom); +@@ -57,6 +54,8 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis + HideNotification(); + } + }); ++ ++ retranslateUi(); + } + + MultiplayerState::~MultiplayerState() = default; +@@ -90,14 +89,7 @@ void MultiplayerState::Close() { + void MultiplayerState::retranslateUi() { + status_text->setToolTip(tr("Current connection status")); + +- if (current_state == Network::RoomMember::State::Uninitialized) { +- status_text->setText(tr("Not Connected. Click here to find a room!")); +- } else if (current_state == Network::RoomMember::State::Joined || +- current_state == Network::RoomMember::State::Moderator) { +- status_text->setText(tr("Connected")); +- } else { +- status_text->setText(tr("Not Connected")); +- } ++ UpdateNotificationStatus(); + + if (lobby) { + lobby->RetranslateUi(); +@@ -113,21 +105,55 @@ void MultiplayerState::retranslateUi() { + } + } + ++void MultiplayerState::SetNotificationStatus(NotificationStatus status) { ++ notification_status = status; ++ UpdateNotificationStatus(); ++} ++ ++void MultiplayerState::UpdateNotificationStatus() { ++ switch (notification_status) { ++ case NotificationStatus::Unitialized: ++ status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16)); ++ status_text->setText(tr("Not Connected. Click here to find a room!")); ++ leave_room->setEnabled(false); ++ show_room->setEnabled(false); ++ break; ++ case NotificationStatus::Disconnected: ++ status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16)); ++ status_text->setText(tr("Not Connected")); ++ leave_room->setEnabled(false); ++ show_room->setEnabled(false); ++ break; ++ case NotificationStatus::Connected: ++ status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16)); ++ status_text->setText(tr("Connected")); ++ leave_room->setEnabled(true); ++ show_room->setEnabled(true); ++ break; ++ case NotificationStatus::Notification: ++ status_icon->setPixmap( ++ QIcon::fromTheme(QStringLiteral("connected_notification")).pixmap(16)); ++ status_text->setText(tr("New Messages Received")); ++ leave_room->setEnabled(true); ++ show_room->setEnabled(true); ++ break; ++ } ++ ++ // Clean up status bar if game is running ++ if (system.IsPoweredOn()) { ++ status_text->clear(); ++ } ++} ++ + void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& state) { + LOG_DEBUG(Frontend, "Network State: {}", Network::GetStateStr(state)); + if (state == Network::RoomMember::State::Joined || + state == Network::RoomMember::State::Moderator) { + + OnOpenNetworkRoom(); +- status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16)); +- status_text->setText(tr("Connected")); +- leave_room->setEnabled(true); +- show_room->setEnabled(true); ++ SetNotificationStatus(NotificationStatus::Connected); + } else { +- status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16)); +- status_text->setText(tr("Not Connected")); +- leave_room->setEnabled(false); +- show_room->setEnabled(false); ++ SetNotificationStatus(NotificationStatus::Disconnected); + } + + current_state = state; +@@ -185,6 +211,10 @@ void MultiplayerState::OnAnnounceFailed(const WebService::WebResult& result) { + QMessageBox::Ok); + } + ++void MultiplayerState::OnSaveConfig() { ++ emit SaveConfig(); ++} ++ + void MultiplayerState::UpdateThemedIcons() { + if (show_notification) { + status_icon->setPixmap( +@@ -209,13 +239,16 @@ static void BringWidgetToFront(QWidget* widget) { + void MultiplayerState::OnViewLobby() { + if (lobby == nullptr) { + lobby = new Lobby(this, game_list_model, announce_multiplayer_session, system); ++ connect(lobby, &Lobby::SaveConfig, this, &MultiplayerState::OnSaveConfig); + } ++ lobby->RefreshLobby(); + BringWidgetToFront(lobby); + } + + void MultiplayerState::OnCreateRoom() { + if (host_room == nullptr) { + host_room = new HostRoomWindow(this, game_list_model, announce_multiplayer_session, system); ++ connect(host_room, &HostRoomWindow::SaveConfig, this, &MultiplayerState::OnSaveConfig); + } + BringWidgetToFront(host_room); + } +@@ -250,14 +283,12 @@ void MultiplayerState::ShowNotification() { + 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")); ++ SetNotificationStatus(NotificationStatus::Notification); + } + + void MultiplayerState::HideNotification() { + show_notification = false; +- status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16)); +- status_text->setText(tr("Connected")); ++ SetNotificationStatus(NotificationStatus::Connected); + } + + void MultiplayerState::OnOpenNetworkRoom() { +@@ -280,6 +311,8 @@ void MultiplayerState::OnOpenNetworkRoom() { + void MultiplayerState::OnDirectConnectToRoom() { + if (direct_connect == nullptr) { + direct_connect = new DirectConnectWindow(system, this); ++ connect(direct_connect, &DirectConnectWindow::SaveConfig, this, ++ &MultiplayerState::OnSaveConfig); + } + BringWidgetToFront(direct_connect); + } +diff --git a/src/yuzu/multiplayer/state.h b/src/yuzu/multiplayer/state.h +index c92496413ca5..5d681c5c6131 100644 +--- a/src/yuzu/multiplayer/state.h ++++ b/src/yuzu/multiplayer/state.h +@@ -22,6 +22,13 @@ class MultiplayerState : public QWidget { + Q_OBJECT; + + public: ++ enum class NotificationStatus { ++ Unitialized, ++ Disconnected, ++ Connected, ++ Notification, ++ }; ++ + explicit MultiplayerState(QWidget* parent, QStandardItemModel* game_list, QAction* leave_room, + QAction* show_room, Core::System& system_); + ~MultiplayerState(); +@@ -31,6 +38,10 @@ class MultiplayerState : public QWidget { + */ + void Close(); + ++ void SetNotificationStatus(NotificationStatus state); ++ ++ void UpdateNotificationStatus(); ++ + ClickableLabel* GetStatusText() const { + return status_text; + } +@@ -64,6 +75,7 @@ public slots: + void OnOpenNetworkRoom(); + void OnDirectConnectToRoom(); + void OnAnnounceFailed(const WebService::WebResult&); ++ void OnSaveConfig(); + void UpdateThemedIcons(); + void ShowNotification(); + void HideNotification(); +@@ -72,6 +84,7 @@ public slots: + void NetworkStateChanged(const Network::RoomMember::State&); + void NetworkError(const Network::RoomMember::Error&); + void AnnounceFailed(const WebService::WebResult&); ++ void SaveConfig(); + + private: + Lobby* lobby = nullptr; +@@ -85,6 +98,7 @@ public slots: + QAction* show_room; + std::shared_ptr announce_multiplayer_session; + Network::RoomMember::State current_state = Network::RoomMember::State::Uninitialized; ++ NotificationStatus notification_status = NotificationStatus::Unitialized; + bool has_mod_perms = false; + Network::RoomMember::CallbackHandle state_callback_handle; + Network::RoomMember::CallbackHandle error_callback_handle; +diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h +index e12d414d96b7..753797efc4d9 100644 +--- a/src/yuzu/uisettings.h ++++ b/src/yuzu/uisettings.h +@@ -102,7 +102,7 @@ struct Values { + Settings::Setting callout_flags{0, "calloutFlags"}; + + // multiplayer settings +- Settings::Setting multiplayer_nickname{QStringLiteral("yuzu"), "nickname"}; ++ Settings::Setting multiplayer_nickname{{}, "nickname"}; + Settings::Setting multiplayer_ip{{}, "ip"}; + Settings::SwitchableSetting multiplayer_port{24872, 0, UINT16_MAX, "port"}; + Settings::Setting multiplayer_room_nickname{{}, "room_nickname"}; + +From 1694c55d62d44730d760371d1bf2559de1b191dd Mon Sep 17 00:00:00 2001 +From: Narr the Reg +Date: Sat, 10 Sep 2022 12:57:19 -0500 +Subject: [PATCH 3/5] fix black icon + +--- + src/yuzu/multiplayer/lobby_p.h | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/yuzu/multiplayer/lobby_p.h b/src/yuzu/multiplayer/lobby_p.h +index 8b17075062b3..068c95acad75 100644 +--- a/src/yuzu/multiplayer/lobby_p.h ++++ b/src/yuzu/multiplayer/lobby_p.h +@@ -90,6 +90,8 @@ class LobbyItemGame : public LobbyItem { + setData(game_name, GameNameRole); + if (!smdh_icon.isNull()) { + setData(smdh_icon, GameIconRole); ++ } else { ++ setData(QIcon::fromTheme(QStringLiteral("chip")).pixmap(32), GameIconRole); + } + } + + +From aa11d73bba386076973010ba4c60d5b04ba828a3 Mon Sep 17 00:00:00 2001 +From: liushuyu +Date: Sat, 10 Sep 2022 17:38:36 -0600 +Subject: [PATCH 4/5] dedicated_room: fix token padding ... + +... mebedtls' base64 routine has a strange behavioral issue where if the +input is invalid, it will not report it as invalid, but rather returning +a bunch of garbage data. This new round-tripping padding method should +eliminate such issue. +--- + src/dedicated_room/yuzu_room.cpp | 14 ++++++++++++-- + 1 file changed, 12 insertions(+), 2 deletions(-) + +diff --git a/src/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp +index 8d8ac1ed74f1..359891883c4a 100644 +--- a/src/dedicated_room/yuzu_room.cpp ++++ b/src/dedicated_room/yuzu_room.cpp +@@ -76,8 +76,18 @@ static constexpr char BanListMagic[] = "YuzuRoom-BanList-1"; + static constexpr char token_delimiter{':'}; + + static void PadToken(std::string& token) { +- const auto remainder = token.size() % 3; +- for (size_t i = 0; i < (3 - remainder); i++) { ++ std::size_t outlen = 0; ++ ++ std::array output{}; ++ std::array roundtrip{}; ++ for (size_t i = 0; i < 3; i++) { ++ mbedtls_base64_decode(output.data(), output.size(), &outlen, ++ reinterpret_cast(token.c_str()), ++ token.length()); ++ mbedtls_base64_encode(roundtrip.data(), roundtrip.size(), &outlen, output.data(), outlen); ++ if (memcmp(roundtrip.data(), token.data(), token.size()) == 0) { ++ break; ++ } + token.push_back('='); + } + } + +From 6b9e6953e8bb91df1ae7c1a6a6076f57ad628c44 Mon Sep 17 00:00:00 2001 +From: FearlessTobi +Date: Mon, 12 Sep 2022 22:39:18 +0200 +Subject: [PATCH 5/5] Address some review comments + +--- + src/core/hle/service/ldn/lan_discovery.cpp | 41 +++++++------------ + src/core/hle/service/ldn/lan_discovery.h | 13 +++--- + src/core/hle/service/ldn/ldn.cpp | 2 - + src/core/hle/service/ldn/ldn_types.h | 8 +--- + .../internal_network/network_interface.cpp | 4 +- + src/network/room_member.cpp | 2 +- + 6 files changed, 26 insertions(+), 44 deletions(-) + +diff --git a/src/core/hle/service/ldn/lan_discovery.cpp b/src/core/hle/service/ldn/lan_discovery.cpp +index b04c990771ad..5f3222217f00 100644 +--- a/src/core/hle/service/ldn/lan_discovery.cpp ++++ b/src/core/hle/service/ldn/lan_discovery.cpp +@@ -35,12 +35,9 @@ void LanStation::OverrideInfo() { + + 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"); +-} ++ room_network{room_network_} {} + + LANDiscovery::~LANDiscovery() { +- LOG_INFO(Service_LDN, "~LANDiscovery"); + if (inited) { + Result rc = Finalize(); + LOG_INFO(Service_LDN, "Finalize: {}", rc.raw); +@@ -62,7 +59,7 @@ void LANDiscovery::InitNetworkInfo() { + } + + void LANDiscovery::InitNodeStateChange() { +- for (auto& node_update : nodeChanges) { ++ for (auto& node_update : node_changes) { + node_update.state_change = NodeStateChange::None; + } + for (auto& node_state : node_last_states) { +@@ -97,8 +94,8 @@ Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network, + 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; ++ out_updates[i].state_change = node_changes[i].state_change; ++ node_changes[i].state_change = NodeStateChange::None; + } + return ResultSuccess; + } +@@ -168,9 +165,9 @@ Result LANDiscovery::Scan(std::vector& networks, u16& count, + return ResultSuccess; + } + +-Result LANDiscovery::SetAdvertiseData(std::vector& data) { ++Result LANDiscovery::SetAdvertiseData(std::span data) { + std::scoped_lock lock{packet_mutex}; +- std::size_t size = data.size(); ++ const std::size_t size = data.size(); + if (size > AdvertiseDataSizeMax) { + return ResultAdvertiseDataTooLarge; + } +@@ -328,7 +325,7 @@ Result LANDiscovery::Disconnect() { + return ResultSuccess; + } + +-Result LANDiscovery::Initialize(LanEventFunc lan_event, bool listening) { ++Result LANDiscovery::Initialize(LanEventFunc lan_event_, bool listening) { + std::scoped_lock lock{packet_mutex}; + if (inited) { + return ResultSuccess; +@@ -341,7 +338,7 @@ Result LANDiscovery::Initialize(LanEventFunc lan_event, bool listening) { + } + + connected_clients.clear(); +- LanEvent = lan_event; ++ LanEvent = lan_event_; + + SetState(State::Initialized); + +@@ -439,7 +436,6 @@ void LANDiscovery::SendPacket(Network::LDNPacketType type, const Data& data, + 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); +@@ -453,7 +449,6 @@ void LANDiscovery::SendPacket(Network::LDNPacketType type, Ipv4Address remote_ip + packet.local_ip = GetLocalIp(); + packet.remote_ip = remote_ip; + +- packet.data.clear(); + SendPacket(packet); + } + +@@ -465,7 +460,6 @@ void LANDiscovery::SendBroadcast(Network::LDNPacketType type, const Data& data) + 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); +@@ -478,7 +472,6 @@ void LANDiscovery::SendBroadcast(Network::LDNPacketType type) { + packet.broadcast = true; + packet.local_ip = GetLocalIp(); + +- packet.data.clear(); + SendPacket(packet); + } + +@@ -581,9 +574,9 @@ bool LANDiscovery::IsNodeStateChanged() { + 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; ++ node_changes[i].state_change |= NodeStateChange::Connect; + } else { +- nodeChanges[i].state_change |= NodeStateChange::Disconnect; ++ node_changes[i].state_change |= NodeStateChange::Disconnect; + } + node_last_states[i] = nodes[i].is_connected; + changed = true; +@@ -598,15 +591,11 @@ bool LANDiscovery::IsFlagSet(ScanFilterFlag flag, ScanFilterFlag search_flag) co + 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; ++int LANDiscovery::GetStationCount() const { ++ return static_cast( ++ std::count_if(stations.begin(), stations.end(), [](const auto& station) { ++ return station.GetStatus() != NodeStatus::Disconnected; ++ })); + } + + MacAddress LANDiscovery::GetFakeMac() const { +diff --git a/src/core/hle/service/ldn/lan_discovery.h b/src/core/hle/service/ldn/lan_discovery.h +index 255342456eba..e64e89424542 100644 +--- a/src/core/hle/service/ldn/lan_discovery.h ++++ b/src/core/hle/service/ldn/lan_discovery.h +@@ -10,6 +10,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -44,7 +45,7 @@ class LanStation { + + class LANDiscovery { + public: +- typedef std::function LanEventFunc; ++ using LanEventFunc = std::function; + + LANDiscovery(Network::RoomNetwork& room_network_); + ~LANDiscovery(); +@@ -58,7 +59,7 @@ class LANDiscovery { + + DisconnectReason GetDisconnectReason() const; + Result Scan(std::vector& networks, u16& count, const ScanFilter& filter); +- Result SetAdvertiseData(std::vector& data); ++ Result SetAdvertiseData(std::span data); + + Result OpenAccessPoint(); + Result CloseAccessPoint(); +@@ -74,7 +75,7 @@ class LANDiscovery { + u16 local_communication_version); + Result Disconnect(); + +- Result Initialize(LanEventFunc lan_event = empty_func, bool listening = true); ++ Result Initialize(LanEventFunc lan_event_ = empty_func, bool listening = true); + Result Finalize(); + + void ReceivePacket(const Network::LDNPacket& packet); +@@ -94,7 +95,7 @@ class LANDiscovery { + + bool IsNodeStateChanged(); + bool IsFlagSet(ScanFilterFlag flag, ScanFilterFlag search_flag) const; +- int GetStationCount(); ++ int GetStationCount() const; + MacAddress GetFakeMac() const; + Result GetNodeInfo(NodeInfo& node, const UserConfig& user_config, + u16 local_communication_version); +@@ -114,7 +115,7 @@ class LANDiscovery { + bool inited{}; + std::mutex packet_mutex; + std::array stations; +- std::array nodeChanges{}; ++ std::array node_changes{}; + std::array node_last_states{}; + std::unordered_map scan_results{}; + NodeInfo node_info{}; +@@ -124,7 +125,7 @@ class LANDiscovery { + + // TODO (flTobi): Should this be an std::set? + std::vector connected_clients; +- std::optional host_ip = std::nullopt; ++ std::optional host_ip; + + LanEventFunc LanEvent; + +diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp +index 6537f49cf2e9..ea3e7e55af62 100644 +--- a/src/core/hle/service/ldn/ldn.cpp ++++ b/src/core/hle/service/ldn/ldn.cpp +@@ -205,8 +205,6 @@ class IUserLocalCommunicationService final + } + + void GetIpv4Address(Kernel::HLERequestContext& ctx) { +- LOG_CRITICAL(Service_LDN, "called"); +- + const auto network_interface = Network::GetSelectedNetworkInterface(); + + if (!network_interface) { +diff --git a/src/core/hle/service/ldn/ldn_types.h b/src/core/hle/service/ldn/ldn_types.h +index d6609fff55fd..de3cd6b762fd 100644 +--- a/src/core/hle/service/ldn/ldn_types.h ++++ b/src/core/hle/service/ldn/ldn_types.h +@@ -31,13 +31,7 @@ enum class NodeStateChange : u8 { + DisconnectAndConnect, + }; + +-inline NodeStateChange operator|(NodeStateChange a, NodeStateChange b) { +- return static_cast(static_cast(a) | static_cast(b)); +-} +- +-inline NodeStateChange operator|=(NodeStateChange& a, NodeStateChange b) { +- return a = a | b; +-} ++DECLARE_ENUM_FLAG_OPERATORS(NodeStateChange) + + enum class ScanFilterFlag : u32 { + None = 0, +diff --git a/src/core/internal_network/network_interface.cpp b/src/core/internal_network/network_interface.cpp +index 858ae1cfbbb2..057fd3661748 100644 +--- a/src/core/internal_network/network_interface.cpp ++++ b/src/core/internal_network/network_interface.cpp +@@ -188,7 +188,7 @@ std::vector GetAvailableNetworkInterfaces() { + std::optional GetSelectedNetworkInterface() { + const auto& selected_network_interface = Settings::values.network_interface.GetValue(); + const auto network_interfaces = Network::GetAvailableNetworkInterfaces(); +- if (network_interfaces.size() == 0) { ++ if (network_interfaces.empty()) { + LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces"); + return std::nullopt; + } +@@ -209,7 +209,7 @@ std::optional GetSelectedNetworkInterface() { + void SelectFirstNetworkInterface() { + const auto network_interfaces = Network::GetAvailableNetworkInterfaces(); + +- if (network_interfaces.size() == 0) { ++ if (network_interfaces.empty()) { + return; + } + +diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp +index 572e55a5b83c..b94cb24ad74c 100644 +--- a/src/network/room_member.cpp ++++ b/src/network/room_member.cpp +@@ -716,7 +716,7 @@ RoomMember::CallbackHandle RoomMember::BindOnProxyPacketReceived( + + RoomMember::CallbackHandle RoomMember::BindOnLdnPacketReceived( + std::function callback) { +- return room_member_impl->Bind(callback); ++ return room_member_impl->Bind(std::move(callback)); + } + + RoomMember::CallbackHandle RoomMember::BindOnRoomInformationChanged(