diff options
Diffstat (limited to 'src')
30 files changed, 1307 insertions, 184 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c17662323..81391f513 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -496,6 +496,8 @@ add_library(core STATIC hle/service/jit/jit.h hle/service/lbl/lbl.cpp hle/service/lbl/lbl.h + hle/service/ldn/lan_discovery.cpp + hle/service/ldn/lan_discovery.h hle/service/ldn/ldn_results.h hle/service/ldn/ldn.cpp hle/service/ldn/ldn.h diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index de3fae2cb..46bad7871 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -36,7 +36,8 @@ namespace Service::HID { // Updating period for each HID device. // Period time is obtained by measuring the number of samples in a second on HW using a homebrew -constexpr auto pad_update_ns = std::chrono::nanoseconds{4 * 1000 * 1000}; // (4ms, 250Hz) +// Correct pad_update_ns is 4ms this is overclocked to lower input lag +constexpr auto pad_update_ns = std::chrono::nanoseconds{1 * 1000 * 1000}; // (1ms, 1000Hz) constexpr auto mouse_keyboard_update_ns = std::chrono::nanoseconds{8 * 1000 * 1000}; // (8ms, 125Hz) constexpr auto motion_update_ns = std::chrono::nanoseconds{5 * 1000 * 1000}; // (5ms, 200Hz) diff --git a/src/core/hle/service/ldn/lan_discovery.cpp b/src/core/hle/service/ldn/lan_discovery.cpp new file mode 100644 index 000000000..8f3c04550 --- /dev/null +++ b/src/core/hle/service/ldn/lan_discovery.cpp @@ -0,0 +1,633 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/ldn/lan_discovery.h" +#include "core/internal_network/network.h" +#include "core/internal_network/network_interface.h" + +namespace Service::LDN { + +LanStation::LanStation(s8 node_id_, LANDiscovery* discovery_) + : node_info(nullptr), status(NodeStatus::Disconnected), node_id(node_id_), + discovery(discovery_) {} + +LanStation::~LanStation() = default; + +NodeStatus LanStation::GetStatus() const { + return status; +} + +void LanStation::OnClose() { + LOG_INFO(Service_LDN, "OnClose {}", node_id); + Reset(); + discovery->UpdateNodes(); +} + +void LanStation::Reset() { + status = NodeStatus::Disconnected; +}; + +void LanStation::OverrideInfo() { + bool connected = GetStatus() == NodeStatus::Connected; + node_info->node_id = node_id; + node_info->is_connected = connected ? 1 : 0; +} + +LANDiscovery::LANDiscovery(Network::RoomNetwork& room_network_) + : stations({{{1, this}, {2, this}, {3, this}, {4, this}, {5, this}, {6, this}, {7, this}}}), + room_network{room_network_} {} + +LANDiscovery::~LANDiscovery() { + if (inited) { + Result rc = Finalize(); + LOG_INFO(Service_LDN, "Finalize: {}", rc.raw); + } +} + +void LANDiscovery::InitNetworkInfo() { + network_info.common.bssid = GetFakeMac(); + network_info.common.channel = WifiChannel::Wifi24_6; + network_info.common.link_level = LinkLevel::Good; + network_info.common.network_type = PackedNetworkType::Ldn; + network_info.common.ssid = fake_ssid; + + auto& nodes = network_info.ldn.nodes; + for (std::size_t i = 0; i < NodeCountMax; i++) { + nodes[i].node_id = static_cast<s8>(i); + nodes[i].is_connected = 0; + } +} + +void LANDiscovery::InitNodeStateChange() { + for (auto& node_update : node_changes) { + node_update.state_change = NodeStateChange::None; + } + for (auto& node_state : node_last_states) { + node_state = 0; + } +} + +State LANDiscovery::GetState() const { + return state; +} + +void LANDiscovery::SetState(State new_state) { + state = new_state; +} + +Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network) const { + if (state == State::AccessPointCreated || state == State::StationConnected) { + std::memcpy(&out_network, &network_info, sizeof(network_info)); + return ResultSuccess; + } + + return ResultBadState; +} + +Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network, + std::vector<NodeLatestUpdate>& out_updates, + std::size_t buffer_count) { + if (buffer_count > NodeCountMax) { + return ResultInvalidBufferCount; + } + + if (state == State::AccessPointCreated || state == State::StationConnected) { + std::memcpy(&out_network, &network_info, sizeof(network_info)); + for (std::size_t i = 0; i < buffer_count; i++) { + out_updates[i].state_change = node_changes[i].state_change; + node_changes[i].state_change = NodeStateChange::None; + } + return ResultSuccess; + } + + return ResultBadState; +} + +DisconnectReason LANDiscovery::GetDisconnectReason() const { + return disconnect_reason; +} + +Result LANDiscovery::Scan(std::vector<NetworkInfo>& networks, u16& count, + const ScanFilter& filter) { + if (!IsFlagSet(filter.flag, ScanFilterFlag::NetworkType) || + filter.network_type <= NetworkType::All) { + if (!IsFlagSet(filter.flag, ScanFilterFlag::Ssid) && filter.ssid.length >= SsidLengthMax) { + return ResultBadInput; + } + } + + { + std::scoped_lock lock{packet_mutex}; + scan_results.clear(); + + SendBroadcast(Network::LDNPacketType::Scan); + } + + LOG_INFO(Service_LDN, "Waiting for scan replies"); + std::this_thread::sleep_for(std::chrono::seconds(1)); + + std::scoped_lock lock{packet_mutex}; + for (const auto& [key, info] : scan_results) { + if (count >= networks.size()) { + break; + } + + if (IsFlagSet(filter.flag, ScanFilterFlag::LocalCommunicationId)) { + if (filter.network_id.intent_id.local_communication_id != + info.network_id.intent_id.local_communication_id) { + continue; + } + } + if (IsFlagSet(filter.flag, ScanFilterFlag::SessionId)) { + if (filter.network_id.session_id != info.network_id.session_id) { + continue; + } + } + if (IsFlagSet(filter.flag, ScanFilterFlag::NetworkType)) { + if (filter.network_type != static_cast<NetworkType>(info.common.network_type)) { + continue; + } + } + if (IsFlagSet(filter.flag, ScanFilterFlag::Ssid)) { + if (filter.ssid != info.common.ssid) { + continue; + } + } + if (IsFlagSet(filter.flag, ScanFilterFlag::SceneId)) { + if (filter.network_id.intent_id.scene_id != info.network_id.intent_id.scene_id) { + continue; + } + } + + networks[count++] = info; + } + + return ResultSuccess; +} + +Result LANDiscovery::SetAdvertiseData(std::span<const u8> data) { + std::scoped_lock lock{packet_mutex}; + const std::size_t size = data.size(); + if (size > AdvertiseDataSizeMax) { + return ResultAdvertiseDataTooLarge; + } + + std::memcpy(network_info.ldn.advertise_data.data(), data.data(), size); + network_info.ldn.advertise_data_size = static_cast<u16>(size); + + UpdateNodes(); + + return ResultSuccess; +} + +Result LANDiscovery::OpenAccessPoint() { + std::scoped_lock lock{packet_mutex}; + disconnect_reason = DisconnectReason::None; + if (state == State::None) { + return ResultBadState; + } + + ResetStations(); + SetState(State::AccessPointOpened); + + return ResultSuccess; +} + +Result LANDiscovery::CloseAccessPoint() { + std::scoped_lock lock{packet_mutex}; + if (state == State::None) { + return ResultBadState; + } + + if (state == State::AccessPointCreated) { + DestroyNetwork(); + } + + ResetStations(); + SetState(State::Initialized); + + return ResultSuccess; +} + +Result LANDiscovery::OpenStation() { + std::scoped_lock lock{packet_mutex}; + disconnect_reason = DisconnectReason::None; + if (state == State::None) { + return ResultBadState; + } + + ResetStations(); + SetState(State::StationOpened); + + return ResultSuccess; +} + +Result LANDiscovery::CloseStation() { + std::scoped_lock lock{packet_mutex}; + if (state == State::None) { + return ResultBadState; + } + + if (state == State::StationConnected) { + Disconnect(); + } + + ResetStations(); + SetState(State::Initialized); + + return ResultSuccess; +} + +Result LANDiscovery::CreateNetwork(const SecurityConfig& security_config, + const UserConfig& user_config, + const NetworkConfig& network_config) { + std::scoped_lock lock{packet_mutex}; + + if (state != State::AccessPointOpened) { + return ResultBadState; + } + + InitNetworkInfo(); + network_info.ldn.node_count_max = network_config.node_count_max; + network_info.ldn.security_mode = security_config.security_mode; + + if (network_config.channel == WifiChannel::Default) { + network_info.common.channel = WifiChannel::Wifi24_6; + } else { + network_info.common.channel = network_config.channel; + } + + std::independent_bits_engine<std::mt19937, 64, u64> bits_engine; + network_info.network_id.session_id.high = bits_engine(); + network_info.network_id.session_id.low = bits_engine(); + network_info.network_id.intent_id = network_config.intent_id; + + NodeInfo& node0 = network_info.ldn.nodes[0]; + const Result rc2 = GetNodeInfo(node0, user_config, network_config.local_communication_version); + if (rc2.IsError()) { + return ResultAccessPointConnectionFailed; + } + + SetState(State::AccessPointCreated); + + InitNodeStateChange(); + node0.is_connected = 1; + UpdateNodes(); + + return rc2; +} + +Result LANDiscovery::DestroyNetwork() { + for (auto local_ip : connected_clients) { + SendPacket(Network::LDNPacketType::DestroyNetwork, local_ip); + } + + ResetStations(); + + SetState(State::AccessPointOpened); + lan_event(); + + return ResultSuccess; +} + +Result LANDiscovery::Connect(const NetworkInfo& network_info_, const UserConfig& user_config, + u16 local_communication_version) { + std::scoped_lock lock{packet_mutex}; + if (network_info_.ldn.node_count == 0) { + return ResultInvalidNodeCount; + } + + Result rc = GetNodeInfo(node_info, user_config, local_communication_version); + if (rc.IsError()) { + return ResultConnectionFailed; + } + + Ipv4Address node_host = network_info_.ldn.nodes[0].ipv4_address; + std::reverse(std::begin(node_host), std::end(node_host)); // htonl + host_ip = node_host; + SendPacket(Network::LDNPacketType::Connect, node_info, *host_ip); + + InitNodeStateChange(); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + return ResultSuccess; +} + +Result LANDiscovery::Disconnect() { + if (host_ip) { + SendPacket(Network::LDNPacketType::Disconnect, node_info, *host_ip); + } + + SetState(State::StationOpened); + lan_event(); + + return ResultSuccess; +} + +Result LANDiscovery::Initialize(LanEventFunc lan_event_, bool listening) { + std::scoped_lock lock{packet_mutex}; + if (inited) { + return ResultSuccess; + } + + for (auto& station : stations) { + station.discovery = this; + station.node_info = &network_info.ldn.nodes[station.node_id]; + station.Reset(); + } + + connected_clients.clear(); + lan_event = lan_event_; + + SetState(State::Initialized); + + inited = true; + return ResultSuccess; +} + +Result LANDiscovery::Finalize() { + std::scoped_lock lock{packet_mutex}; + Result rc = ResultSuccess; + + if (inited) { + if (state == State::AccessPointCreated) { + DestroyNetwork(); + } + if (state == State::StationConnected) { + Disconnect(); + } + + ResetStations(); + inited = false; + } + + SetState(State::None); + + return rc; +} + +void LANDiscovery::ResetStations() { + for (auto& station : stations) { + station.Reset(); + } + connected_clients.clear(); +} + +void LANDiscovery::UpdateNodes() { + u8 count = 0; + for (auto& station : stations) { + bool connected = station.GetStatus() == NodeStatus::Connected; + if (connected) { + count++; + } + station.OverrideInfo(); + } + network_info.ldn.node_count = count + 1; + + for (auto local_ip : connected_clients) { + SendPacket(Network::LDNPacketType::SyncNetwork, network_info, local_ip); + } + + OnNetworkInfoChanged(); +} + +void LANDiscovery::OnSyncNetwork(const NetworkInfo& info) { + network_info = info; + if (state == State::StationOpened) { + SetState(State::StationConnected); + } + OnNetworkInfoChanged(); +} + +void LANDiscovery::OnDisconnectFromHost() { + LOG_INFO(Service_LDN, "OnDisconnectFromHost state: {}", static_cast<int>(state)); + host_ip = std::nullopt; + if (state == State::StationConnected) { + SetState(State::StationOpened); + lan_event(); + } +} + +void LANDiscovery::OnNetworkInfoChanged() { + if (IsNodeStateChanged()) { + lan_event(); + } + return; +} + +Network::IPv4Address LANDiscovery::GetLocalIp() const { + Network::IPv4Address local_ip{0xFF, 0xFF, 0xFF, 0xFF}; + if (auto room_member = room_network.GetRoomMember().lock()) { + if (room_member->IsConnected()) { + local_ip = room_member->GetFakeIpAddress(); + } + } + return local_ip; +} + +template <typename Data> +void LANDiscovery::SendPacket(Network::LDNPacketType type, const Data& data, + Ipv4Address remote_ip) { + Network::LDNPacket packet; + packet.type = type; + + packet.broadcast = false; + packet.local_ip = GetLocalIp(); + packet.remote_ip = remote_ip; + + packet.data.resize(sizeof(data)); + std::memcpy(packet.data.data(), &data, sizeof(data)); + SendPacket(packet); +} + +void LANDiscovery::SendPacket(Network::LDNPacketType type, Ipv4Address remote_ip) { + Network::LDNPacket packet; + packet.type = type; + + packet.broadcast = false; + packet.local_ip = GetLocalIp(); + packet.remote_ip = remote_ip; + + SendPacket(packet); +} + +template <typename Data> +void LANDiscovery::SendBroadcast(Network::LDNPacketType type, const Data& data) { + Network::LDNPacket packet; + packet.type = type; + + packet.broadcast = true; + packet.local_ip = GetLocalIp(); + + packet.data.resize(sizeof(data)); + std::memcpy(packet.data.data(), &data, sizeof(data)); + SendPacket(packet); +} + +void LANDiscovery::SendBroadcast(Network::LDNPacketType type) { + Network::LDNPacket packet; + packet.type = type; + + packet.broadcast = true; + packet.local_ip = GetLocalIp(); + + SendPacket(packet); +} + +void LANDiscovery::SendPacket(const Network::LDNPacket& packet) { + if (auto room_member = room_network.GetRoomMember().lock()) { + if (room_member->IsConnected()) { + room_member->SendLdnPacket(packet); + } + } +} + +void LANDiscovery::ReceivePacket(const Network::LDNPacket& packet) { + std::scoped_lock lock{packet_mutex}; + switch (packet.type) { + case Network::LDNPacketType::Scan: { + LOG_INFO(Frontend, "Scan packet received!"); + if (state == State::AccessPointCreated) { + // Reply to the sender + SendPacket(Network::LDNPacketType::ScanResp, network_info, packet.local_ip); + } + break; + } + case Network::LDNPacketType::ScanResp: { + LOG_INFO(Frontend, "ScanResp packet received!"); + + NetworkInfo info{}; + std::memcpy(&info, packet.data.data(), sizeof(NetworkInfo)); + scan_results.insert({info.common.bssid, info}); + + break; + } + case Network::LDNPacketType::Connect: { + LOG_INFO(Frontend, "Connect packet received!"); + + NodeInfo info{}; + std::memcpy(&info, packet.data.data(), sizeof(NodeInfo)); + + connected_clients.push_back(packet.local_ip); + + for (LanStation& station : stations) { + if (station.status != NodeStatus::Connected) { + *station.node_info = info; + station.status = NodeStatus::Connected; + break; + } + } + + UpdateNodes(); + + break; + } + case Network::LDNPacketType::Disconnect: { + LOG_INFO(Frontend, "Disconnect packet received!"); + + connected_clients.erase( + std::remove(connected_clients.begin(), connected_clients.end(), packet.local_ip), + connected_clients.end()); + + NodeInfo info{}; + std::memcpy(&info, packet.data.data(), sizeof(NodeInfo)); + + for (LanStation& station : stations) { + if (station.status == NodeStatus::Connected && + station.node_info->mac_address == info.mac_address) { + station.OnClose(); + break; + } + } + + break; + } + case Network::LDNPacketType::DestroyNetwork: { + ResetStations(); + OnDisconnectFromHost(); + break; + } + case Network::LDNPacketType::SyncNetwork: { + if (state == State::StationOpened || state == State::StationConnected) { + LOG_INFO(Frontend, "SyncNetwork packet received!"); + NetworkInfo info{}; + std::memcpy(&info, packet.data.data(), sizeof(NetworkInfo)); + + OnSyncNetwork(info); + } else { + LOG_INFO(Frontend, "SyncNetwork packet received but in wrong State!"); + } + + break; + } + default: { + LOG_INFO(Frontend, "ReceivePacket unhandled type {}", static_cast<int>(packet.type)); + break; + } + } +} + +bool LANDiscovery::IsNodeStateChanged() { + bool changed = false; + const auto& nodes = network_info.ldn.nodes; + for (int i = 0; i < NodeCountMax; i++) { + if (nodes[i].is_connected != node_last_states[i]) { + if (nodes[i].is_connected) { + node_changes[i].state_change |= NodeStateChange::Connect; + } else { + node_changes[i].state_change |= NodeStateChange::Disconnect; + } + node_last_states[i] = nodes[i].is_connected; + changed = true; + } + } + return changed; +} + +bool LANDiscovery::IsFlagSet(ScanFilterFlag flag, ScanFilterFlag search_flag) const { + const auto flag_value = static_cast<u32>(flag); + const auto search_flag_value = static_cast<u32>(search_flag); + return (flag_value & search_flag_value) == search_flag_value; +} + +int LANDiscovery::GetStationCount() const { + return static_cast<int>( + std::count_if(stations.begin(), stations.end(), [](const auto& station) { + return station.GetStatus() != NodeStatus::Disconnected; + })); +} + +MacAddress LANDiscovery::GetFakeMac() const { + MacAddress mac{}; + mac.raw[0] = 0x02; + mac.raw[1] = 0x00; + + const auto ip = GetLocalIp(); + memcpy(mac.raw.data() + 2, &ip, sizeof(ip)); + + return mac; +} + +Result LANDiscovery::GetNodeInfo(NodeInfo& node, const UserConfig& userConfig, + u16 localCommunicationVersion) { + const auto network_interface = Network::GetSelectedNetworkInterface(); + + if (!network_interface) { + LOG_ERROR(Service_LDN, "No network interface available"); + return ResultNoIpAddress; + } + + node.mac_address = GetFakeMac(); + node.is_connected = 1; + std::memcpy(node.user_name.data(), userConfig.user_name.data(), UserNameBytesMax + 1); + node.local_communication_version = localCommunicationVersion; + + Ipv4Address current_address = GetLocalIp(); + std::reverse(std::begin(current_address), std::end(current_address)); // ntohl + node.ipv4_address = current_address; + + return ResultSuccess; +} + +} // namespace Service::LDN diff --git a/src/core/hle/service/ldn/lan_discovery.h b/src/core/hle/service/ldn/lan_discovery.h new file mode 100644 index 000000000..3833cd764 --- /dev/null +++ b/src/core/hle/service/ldn/lan_discovery.h @@ -0,0 +1,134 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <cstring> +#include <functional> +#include <memory> +#include <mutex> +#include <optional> +#include <random> +#include <span> +#include <thread> +#include <unordered_map> + +#include "common/logging/log.h" +#include "common/socket_types.h" +#include "core/hle/result.h" +#include "core/hle/service/ldn/ldn_results.h" +#include "core/hle/service/ldn/ldn_types.h" +#include "network/network.h" + +namespace Service::LDN { + +class LANDiscovery; + +class LanStation { +public: + LanStation(s8 node_id_, LANDiscovery* discovery_); + ~LanStation(); + + void OnClose(); + NodeStatus GetStatus() const; + void Reset(); + void OverrideInfo(); + +protected: + friend class LANDiscovery; + NodeInfo* node_info; + NodeStatus status; + s8 node_id; + LANDiscovery* discovery; +}; + +class LANDiscovery { +public: + using LanEventFunc = std::function<void()>; + + LANDiscovery(Network::RoomNetwork& room_network_); + ~LANDiscovery(); + + State GetState() const; + void SetState(State new_state); + + Result GetNetworkInfo(NetworkInfo& out_network) const; + Result GetNetworkInfo(NetworkInfo& out_network, std::vector<NodeLatestUpdate>& out_updates, + std::size_t buffer_count); + + DisconnectReason GetDisconnectReason() const; + Result Scan(std::vector<NetworkInfo>& networks, u16& count, const ScanFilter& filter); + Result SetAdvertiseData(std::span<const u8> data); + + Result OpenAccessPoint(); + Result CloseAccessPoint(); + + Result OpenStation(); + Result CloseStation(); + + Result CreateNetwork(const SecurityConfig& security_config, const UserConfig& user_config, + const NetworkConfig& network_config); + Result DestroyNetwork(); + + Result Connect(const NetworkInfo& network_info_, const UserConfig& user_config, + u16 local_communication_version); + Result Disconnect(); + + Result Initialize(LanEventFunc lan_event_ = empty_func, bool listening = true); + Result Finalize(); + + void ReceivePacket(const Network::LDNPacket& packet); + +protected: + friend class LanStation; + + void InitNetworkInfo(); + void InitNodeStateChange(); + + void ResetStations(); + void UpdateNodes(); + + void OnSyncNetwork(const NetworkInfo& info); + void OnDisconnectFromHost(); + void OnNetworkInfoChanged(); + + bool IsNodeStateChanged(); + bool IsFlagSet(ScanFilterFlag flag, ScanFilterFlag search_flag) const; + int GetStationCount() const; + MacAddress GetFakeMac() const; + Result GetNodeInfo(NodeInfo& node, const UserConfig& user_config, + u16 local_communication_version); + + Network::IPv4Address GetLocalIp() const; + template <typename Data> + void SendPacket(Network::LDNPacketType type, const Data& data, Ipv4Address remote_ip); + void SendPacket(Network::LDNPacketType type, Ipv4Address remote_ip); + template <typename Data> + void SendBroadcast(Network::LDNPacketType type, const Data& data); + void SendBroadcast(Network::LDNPacketType type); + void SendPacket(const Network::LDNPacket& packet); + + static const LanEventFunc empty_func; + static constexpr Ssid fake_ssid{"YuzuFakeSsidForLdn"}; + + bool inited{}; + std::mutex packet_mutex; + std::array<LanStation, StationCountMax> stations; + std::array<NodeLatestUpdate, NodeCountMax> node_changes{}; + std::array<u8, NodeCountMax> node_last_states{}; + std::unordered_map<MacAddress, NetworkInfo, MACAddressHash> scan_results{}; + NodeInfo node_info{}; + NetworkInfo network_info{}; + State state{State::None}; + DisconnectReason disconnect_reason{DisconnectReason::None}; + + // TODO (flTobi): Should this be an std::set? + std::vector<Ipv4Address> connected_clients; + std::optional<Ipv4Address> host_ip; + + LanEventFunc lan_event; + + Network::RoomNetwork& room_network; +}; +} // namespace Service::LDN diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp index c11daff54..ea3e7e55a 100644 --- a/src/core/hle/service/ldn/ldn.cpp +++ b/src/core/hle/service/ldn/ldn.cpp @@ -4,11 +4,13 @@ #include <memory> #include "core/core.h" +#include "core/hle/service/ldn/lan_discovery.h" #include "core/hle/service/ldn/ldn.h" #include "core/hle/service/ldn/ldn_results.h" #include "core/hle/service/ldn/ldn_types.h" #include "core/internal_network/network.h" #include "core/internal_network/network_interface.h" +#include "network/network.h" // This is defined by synchapi.h and conflicts with ServiceContext::CreateEvent #undef CreateEvent @@ -105,13 +107,13 @@ class IUserLocalCommunicationService final public: explicit IUserLocalCommunicationService(Core::System& system_) : ServiceFramework{system_, "IUserLocalCommunicationService", ServiceThreadType::CreateNew}, - service_context{system, "IUserLocalCommunicationService"}, room_network{ - system_.GetRoomNetwork()} { + service_context{system, "IUserLocalCommunicationService"}, + room_network{system_.GetRoomNetwork()}, lan_discovery{room_network} { // clang-format off static const FunctionInfo functions[] = { {0, &IUserLocalCommunicationService::GetState, "GetState"}, {1, &IUserLocalCommunicationService::GetNetworkInfo, "GetNetworkInfo"}, - {2, nullptr, "GetIpv4Address"}, + {2, &IUserLocalCommunicationService::GetIpv4Address, "GetIpv4Address"}, {3, &IUserLocalCommunicationService::GetDisconnectReason, "GetDisconnectReason"}, {4, &IUserLocalCommunicationService::GetSecurityParameter, "GetSecurityParameter"}, {5, &IUserLocalCommunicationService::GetNetworkConfig, "GetNetworkConfig"}, @@ -119,7 +121,7 @@ public: {101, &IUserLocalCommunicationService::GetNetworkInfoLatestUpdate, "GetNetworkInfoLatestUpdate"}, {102, &IUserLocalCommunicationService::Scan, "Scan"}, {103, &IUserLocalCommunicationService::ScanPrivate, "ScanPrivate"}, - {104, nullptr, "SetWirelessControllerRestriction"}, + {104, &IUserLocalCommunicationService::SetWirelessControllerRestriction, "SetWirelessControllerRestriction"}, {200, &IUserLocalCommunicationService::OpenAccessPoint, "OpenAccessPoint"}, {201, &IUserLocalCommunicationService::CloseAccessPoint, "CloseAccessPoint"}, {202, &IUserLocalCommunicationService::CreateNetwork, "CreateNetwork"}, @@ -148,16 +150,30 @@ public: } ~IUserLocalCommunicationService() { + if (is_initialized) { + if (auto room_member = room_network.GetRoomMember().lock()) { + room_member->Unbind(ldn_packet_received); + } + } + service_context.CloseEvent(state_change_event); } + /// Callback to parse and handle a received LDN packet. + void OnLDNPacketReceived(const Network::LDNPacket& packet) { + lan_discovery.ReceivePacket(packet); + } + void OnEventFired() { state_change_event->GetWritableEvent().Signal(); } void GetState(Kernel::HLERequestContext& ctx) { State state = State::Error; - LOG_WARNING(Service_LDN, "(STUBBED) called, state = {}", state); + + if (is_initialized) { + state = lan_discovery.GetState(); + } IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); @@ -175,7 +191,7 @@ public: } NetworkInfo network_info{}; - const auto rc = ResultSuccess; + const auto rc = lan_discovery.GetNetworkInfo(network_info); if (rc.IsError()) { LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw); IPC::ResponseBuilder rb{ctx, 2}; @@ -183,28 +199,50 @@ public: return; } - LOG_WARNING(Service_LDN, "(STUBBED) called, ssid='{}', nodes={}", - network_info.common.ssid.GetStringValue(), network_info.ldn.node_count); - ctx.WriteBuffer<NetworkInfo>(network_info); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(rc); + rb.Push(ResultSuccess); } - void GetDisconnectReason(Kernel::HLERequestContext& ctx) { - const auto disconnect_reason = DisconnectReason::None; + void GetIpv4Address(Kernel::HLERequestContext& ctx) { + const auto network_interface = Network::GetSelectedNetworkInterface(); + + if (!network_interface) { + LOG_ERROR(Service_LDN, "No network interface available"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultNoIpAddress); + return; + } - LOG_WARNING(Service_LDN, "(STUBBED) called, disconnect_reason={}", disconnect_reason); + Ipv4Address current_address{Network::TranslateIPv4(network_interface->ip_address)}; + Ipv4Address subnet_mask{Network::TranslateIPv4(network_interface->subnet_mask)}; + + // When we're connected to a room, spoof the hosts IP address + if (auto room_member = room_network.GetRoomMember().lock()) { + if (room_member->IsConnected()) { + current_address = room_member->GetFakeIpAddress(); + } + } + + std::reverse(std::begin(current_address), std::end(current_address)); // ntohl + std::reverse(std::begin(subnet_mask), std::end(subnet_mask)); // ntohl + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.PushRaw(current_address); + rb.PushRaw(subnet_mask); + } + void GetDisconnectReason(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.PushEnum(disconnect_reason); + rb.PushEnum(lan_discovery.GetDisconnectReason()); } void GetSecurityParameter(Kernel::HLERequestContext& ctx) { SecurityParameter security_parameter{}; NetworkInfo info{}; - const Result rc = ResultSuccess; + const Result rc = lan_discovery.GetNetworkInfo(info); if (rc.IsError()) { LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw); @@ -217,8 +255,6 @@ public: std::memcpy(security_parameter.data.data(), info.ldn.security_parameter.data(), sizeof(SecurityParameter::data)); - LOG_WARNING(Service_LDN, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 10}; rb.Push(rc); rb.PushRaw<SecurityParameter>(security_parameter); @@ -227,7 +263,7 @@ public: void GetNetworkConfig(Kernel::HLERequestContext& ctx) { NetworkConfig config{}; NetworkInfo info{}; - const Result rc = ResultSuccess; + const Result rc = lan_discovery.GetNetworkInfo(info); if (rc.IsError()) { LOG_ERROR(Service_LDN, "NetworkConfig is not valid {}", rc.raw); @@ -241,12 +277,6 @@ public: config.node_count_max = info.ldn.node_count_max; config.local_communication_version = info.ldn.nodes[0].local_communication_version; - LOG_WARNING(Service_LDN, - "(STUBBED) called, intent_id={}/{}, channel={}, node_count_max={}, " - "local_communication_version={}", - config.intent_id.local_communication_id, config.intent_id.scene_id, - config.channel, config.node_count_max, config.local_communication_version); - IPC::ResponseBuilder rb{ctx, 10}; rb.Push(rc); rb.PushRaw<NetworkConfig>(config); @@ -265,17 +295,17 @@ public: const std::size_t node_buffer_count = ctx.GetWriteBufferSize(1) / sizeof(NodeLatestUpdate); if (node_buffer_count == 0 || network_buffer_size != sizeof(NetworkInfo)) { - LOG_ERROR(Service_LDN, "Invalid buffer size {}, {}", network_buffer_size, + LOG_ERROR(Service_LDN, "Invalid buffer, size = {}, count = {}", network_buffer_size, node_buffer_count); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultBadInput); return; } - NetworkInfo info; + NetworkInfo info{}; std::vector<NodeLatestUpdate> latest_update(node_buffer_count); - const auto rc = ResultSuccess; + const auto rc = lan_discovery.GetNetworkInfo(info, latest_update, latest_update.size()); if (rc.IsError()) { LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw); IPC::ResponseBuilder rb{ctx, 2}; @@ -283,9 +313,6 @@ public: return; } - LOG_WARNING(Service_LDN, "(STUBBED) called, ssid='{}', nodes={}", - info.common.ssid.GetStringValue(), info.ldn.node_count); - ctx.WriteBuffer(info, 0); ctx.WriteBuffer(latest_update, 1); @@ -317,92 +344,78 @@ public: u16 count = 0; std::vector<NetworkInfo> network_infos(network_info_size); + Result rc = lan_discovery.Scan(network_infos, count, scan_filter); - LOG_WARNING(Service_LDN, - "(STUBBED) called, channel={}, filter_scan_flag={}, filter_network_type={}", - channel, scan_filter.flag, scan_filter.network_type); + LOG_INFO(Service_LDN, + "called, channel={}, filter_scan_flag={}, filter_network_type={}, is_private={}", + channel, scan_filter.flag, scan_filter.network_type, is_private); ctx.WriteBuffer(network_infos); IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); + rb.Push(rc); rb.Push<u32>(count); } - void OpenAccessPoint(Kernel::HLERequestContext& ctx) { + void SetWirelessControllerRestriction(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_LDN, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } + void OpenAccessPoint(Kernel::HLERequestContext& ctx) { + LOG_INFO(Service_LDN, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(lan_discovery.OpenAccessPoint()); + } + void CloseAccessPoint(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_LDN, "(STUBBED) called"); + LOG_INFO(Service_LDN, "called"); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(lan_discovery.CloseAccessPoint()); } void CreateNetwork(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - struct Parameters { - SecurityConfig security_config; - UserConfig user_config; - INSERT_PADDING_WORDS_NOINIT(1); - NetworkConfig network_config; - }; - static_assert(sizeof(Parameters) == 0x98, "Parameters has incorrect size."); + LOG_INFO(Service_LDN, "called"); - const auto parameters{rp.PopRaw<Parameters>()}; + CreateNetworkImpl(ctx); + } - LOG_WARNING(Service_LDN, - "(STUBBED) called, passphrase_size={}, security_mode={}, " - "local_communication_version={}", - parameters.security_config.passphrase_size, - parameters.security_config.security_mode, - parameters.network_config.local_communication_version); + void CreateNetworkPrivate(Kernel::HLERequestContext& ctx) { + LOG_INFO(Service_LDN, "called"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + CreateNetworkImpl(ctx, true); } - void CreateNetworkPrivate(Kernel::HLERequestContext& ctx) { + void CreateNetworkImpl(Kernel::HLERequestContext& ctx, bool is_private = false) { IPC::RequestParser rp{ctx}; - struct Parameters { - SecurityConfig security_config; - SecurityParameter security_parameter; - UserConfig user_config; - NetworkConfig network_config; - }; - static_assert(sizeof(Parameters) == 0xB8, "Parameters has incorrect size."); - - const auto parameters{rp.PopRaw<Parameters>()}; - LOG_WARNING(Service_LDN, - "(STUBBED) called, passphrase_size={}, security_mode={}, " - "local_communication_version={}", - parameters.security_config.passphrase_size, - parameters.security_config.security_mode, - parameters.network_config.local_communication_version); + const auto security_config{rp.PopRaw<SecurityConfig>()}; + [[maybe_unused]] const auto security_parameter{is_private ? rp.PopRaw<SecurityParameter>() + : SecurityParameter{}}; + const auto user_config{rp.PopRaw<UserConfig>()}; + rp.Pop<u32>(); // Padding + const auto network_Config{rp.PopRaw<NetworkConfig>()}; IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(lan_discovery.CreateNetwork(security_config, user_config, network_Config)); } void DestroyNetwork(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_LDN, "(STUBBED) called"); + LOG_INFO(Service_LDN, "called"); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(lan_discovery.DestroyNetwork()); } void SetAdvertiseData(Kernel::HLERequestContext& ctx) { std::vector<u8> read_buffer = ctx.ReadBuffer(); - LOG_WARNING(Service_LDN, "(STUBBED) called, size {}", read_buffer.size()); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(lan_discovery.SetAdvertiseData(read_buffer)); } void SetStationAcceptPolicy(Kernel::HLERequestContext& ctx) { @@ -420,17 +433,17 @@ public: } void OpenStation(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_LDN, "(STUBBED) called"); + LOG_INFO(Service_LDN, "called"); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(lan_discovery.OpenStation()); } void CloseStation(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_LDN, "(STUBBED) called"); + LOG_INFO(Service_LDN, "called"); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(lan_discovery.CloseStation()); } void Connect(Kernel::HLERequestContext& ctx) { @@ -445,16 +458,13 @@ public: const auto parameters{rp.PopRaw<Parameters>()}; - LOG_WARNING(Service_LDN, - "(STUBBED) called, passphrase_size={}, security_mode={}, " - "local_communication_version={}", - parameters.security_config.passphrase_size, - parameters.security_config.security_mode, - parameters.local_communication_version); + LOG_INFO(Service_LDN, + "called, passphrase_size={}, security_mode={}, " + "local_communication_version={}", + parameters.security_config.passphrase_size, + parameters.security_config.security_mode, parameters.local_communication_version); const std::vector<u8> read_buffer = ctx.ReadBuffer(); - NetworkInfo network_info{}; - if (read_buffer.size() != sizeof(NetworkInfo)) { LOG_ERROR(Frontend, "NetworkInfo doesn't match read_buffer size!"); IPC::ResponseBuilder rb{ctx, 2}; @@ -462,40 +472,47 @@ public: return; } + NetworkInfo network_info{}; std::memcpy(&network_info, read_buffer.data(), read_buffer.size()); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(lan_discovery.Connect(network_info, parameters.user_config, + static_cast<u16>(parameters.local_communication_version))); } void Disconnect(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_LDN, "(STUBBED) called"); + LOG_INFO(Service_LDN, "called"); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(lan_discovery.Disconnect()); } - void Initialize(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_LDN, "(STUBBED) called"); + void Initialize(Kernel::HLERequestContext& ctx) { const auto rc = InitializeImpl(ctx); + if (rc.IsError()) { + LOG_ERROR(Service_LDN, "Network isn't initialized, rc={}", rc.raw); + } IPC::ResponseBuilder rb{ctx, 2}; rb.Push(rc); } void Finalize(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_LDN, "(STUBBED) called"); + if (auto room_member = room_network.GetRoomMember().lock()) { + room_member->Unbind(ldn_packet_received); + } is_initialized = false; IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(lan_discovery.Finalize()); } void Initialize2(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_LDN, "(STUBBED) called"); - const auto rc = InitializeImpl(ctx); + if (rc.IsError()) { + LOG_ERROR(Service_LDN, "Network isn't initialized, rc={}", rc.raw); + } IPC::ResponseBuilder rb{ctx, 2}; rb.Push(rc); @@ -508,14 +525,26 @@ public: return ResultAirplaneModeEnabled; } + if (auto room_member = room_network.GetRoomMember().lock()) { + ldn_packet_received = room_member->BindOnLdnPacketReceived( + [this](const Network::LDNPacket& packet) { OnLDNPacketReceived(packet); }); + } else { + LOG_ERROR(Service_LDN, "Couldn't bind callback!"); + return ResultAirplaneModeEnabled; + } + + lan_discovery.Initialize([&]() { OnEventFired(); }); is_initialized = true; - // TODO (flTobi): Change this to ResultSuccess when LDN is fully implemented - return ResultAirplaneModeEnabled; + return ResultSuccess; } KernelHelpers::ServiceContext service_context; Kernel::KEvent* state_change_event; Network::RoomNetwork& room_network; + LANDiscovery lan_discovery; + + // Callback identifier for the OnLDNPacketReceived event. + Network::RoomMember::CallbackHandle<Network::LDNPacket> ldn_packet_received; bool is_initialized{}; }; diff --git a/src/core/hle/service/ldn/ldn_types.h b/src/core/hle/service/ldn/ldn_types.h index 6231e936d..44c2c773b 100644 --- a/src/core/hle/service/ldn/ldn_types.h +++ b/src/core/hle/service/ldn/ldn_types.h @@ -31,6 +31,8 @@ enum class NodeStateChange : u8 { DisconnectAndConnect, }; +DECLARE_ENUM_FLAG_OPERATORS(NodeStateChange) + enum class ScanFilterFlag : u32 { None = 0, LocalCommunicationId = 1 << 0, @@ -100,13 +102,13 @@ enum class AcceptPolicy : u8 { enum class WifiChannel : s16 { Default = 0, - wifi24_1 = 1, - wifi24_6 = 6, - wifi24_11 = 11, - wifi50_36 = 36, - wifi50_40 = 40, - wifi50_44 = 44, - wifi50_48 = 48, + Wifi24_1 = 1, + Wifi24_6 = 6, + Wifi24_11 = 11, + Wifi50_36 = 36, + Wifi50_40 = 40, + Wifi50_44 = 44, + Wifi50_48 = 48, }; enum class LinkLevel : s8 { @@ -116,6 +118,11 @@ enum class LinkLevel : s8 { Excellent, }; +enum class NodeStatus : u8 { + Disconnected, + Connected, +}; + struct NodeLatestUpdate { NodeStateChange state_change; INSERT_PADDING_BYTES(0x7); // Unknown @@ -150,7 +157,7 @@ struct Ssid { Ssid() = default; - explicit Ssid(std::string_view data) { + constexpr explicit Ssid(std::string_view data) { length = static_cast<u8>(std::min(data.size(), SsidLengthMax)); data.copy(raw.data(), length); raw[length] = 0; @@ -159,19 +166,18 @@ struct Ssid { std::string GetStringValue() const { return std::string(raw.data()); } -}; -static_assert(sizeof(Ssid) == 0x22, "Ssid is an invalid size"); -struct Ipv4Address { - union { - u32 raw{}; - std::array<u8, 4> bytes; - }; + bool operator==(const Ssid& b) const { + return (length == b.length) && (std::memcmp(raw.data(), b.raw.data(), length) == 0); + } - std::string GetStringValue() const { - return fmt::format("{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]); + bool operator!=(const Ssid& b) const { + return !operator==(b); } }; +static_assert(sizeof(Ssid) == 0x22, "Ssid is an invalid size"); + +using Ipv4Address = std::array<u8, 4>; static_assert(sizeof(Ipv4Address) == 0x4, "Ipv4Address is an invalid size"); struct MacAddress { @@ -181,6 +187,14 @@ struct MacAddress { }; static_assert(sizeof(MacAddress) == 0x6, "MacAddress is an invalid size"); +struct MACAddressHash { + size_t operator()(const MacAddress& address) const { + u64 value{}; + std::memcpy(&value, address.raw.data(), sizeof(address.raw)); + return value; + } +}; + struct ScanFilter { NetworkId network_id; NetworkType network_type; diff --git a/src/core/internal_network/network_interface.cpp b/src/core/internal_network/network_interface.cpp index 0f0a66160..057fd3661 100644 --- a/src/core/internal_network/network_interface.cpp +++ b/src/core/internal_network/network_interface.cpp @@ -188,7 +188,7 @@ std::vector<NetworkInterface> GetAvailableNetworkInterfaces() { std::optional<NetworkInterface> GetSelectedNetworkInterface() { const auto& selected_network_interface = Settings::values.network_interface.GetValue(); const auto network_interfaces = Network::GetAvailableNetworkInterfaces(); - if (network_interfaces.size() == 0) { + if (network_interfaces.empty()) { LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces"); return std::nullopt; } @@ -206,4 +206,14 @@ std::optional<NetworkInterface> GetSelectedNetworkInterface() { return *res; } +void SelectFirstNetworkInterface() { + const auto network_interfaces = Network::GetAvailableNetworkInterfaces(); + + if (network_interfaces.empty()) { + return; + } + + Settings::values.network_interface.SetValue(network_interfaces[0].name); +} + } // namespace Network diff --git a/src/core/internal_network/network_interface.h b/src/core/internal_network/network_interface.h index 9b98b6b42..175e61b1f 100644 --- a/src/core/internal_network/network_interface.h +++ b/src/core/internal_network/network_interface.h @@ -24,5 +24,6 @@ struct NetworkInterface { std::vector<NetworkInterface> GetAvailableNetworkInterfaces(); std::optional<NetworkInterface> GetSelectedNetworkInterface(); +void SelectFirstNetworkInterface(); } // namespace Network diff --git a/src/core/internal_network/socket_proxy.cpp b/src/core/internal_network/socket_proxy.cpp index 0c746bd82..7d5d37bbc 100644 --- a/src/core/internal_network/socket_proxy.cpp +++ b/src/core/internal_network/socket_proxy.cpp @@ -6,6 +6,7 @@ #include "common/assert.h" #include "common/logging/log.h" +#include "common/zstd_compression.h" #include "core/internal_network/network.h" #include "core/internal_network/network_interface.h" #include "core/internal_network/socket_proxy.h" @@ -32,8 +33,11 @@ void ProxySocket::HandleProxyPacket(const ProxyPacket& packet) { return; } + auto decompressed = packet; + decompressed.data = Common::Compression::DecompressDataZSTD(packet.data); + std::lock_guard guard(packets_mutex); - received_packets.push(packet); + received_packets.push(decompressed); } template <typename T> @@ -185,6 +189,8 @@ std::pair<s32, Errno> ProxySocket::Send(const std::vector<u8>& message, int flag void ProxySocket::SendPacket(ProxyPacket& packet) { if (auto room_member = room_network.GetRoomMember().lock()) { if (room_member->IsConnected()) { + packet.data = Common::Compression::CompressDataZSTDDefault(packet.data.data(), + packet.data.size()); room_member->SendProxyPacket(packet); } } diff --git a/src/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp index 7b6deba41..359891883 100644 --- a/src/dedicated_room/yuzu_room.cpp +++ b/src/dedicated_room/yuzu_room.cpp @@ -76,7 +76,18 @@ static constexpr char BanListMagic[] = "YuzuRoom-BanList-1"; static constexpr char token_delimiter{':'}; static void PadToken(std::string& token) { - while (token.size() % 4 != 0) { + std::size_t outlen = 0; + + std::array<unsigned char, 512> output{}; + std::array<unsigned char, 2048> roundtrip{}; + for (size_t i = 0; i < 3; i++) { + mbedtls_base64_decode(output.data(), output.size(), &outlen, + reinterpret_cast<const unsigned char*>(token.c_str()), + token.length()); + mbedtls_base64_encode(roundtrip.data(), roundtrip.size(), &outlen, output.data(), outlen); + if (memcmp(roundtrip.data(), token.data(), token.size()) == 0) { + break; + } token.push_back('='); } } diff --git a/src/network/room.cpp b/src/network/room.cpp index 8c63b255b..dc5dbce7f 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -212,6 +212,12 @@ public: void HandleProxyPacket(const ENetEvent* event); /** + * Broadcasts this packet to all members except the sender. + * @param event The ENet event containing the data + */ + void HandleLdnPacket(const ENetEvent* event); + + /** * Extracts a chat entry from a received ENet packet and adds it to the chat queue. * @param event The ENet event that was received. */ @@ -247,6 +253,9 @@ void Room::RoomImpl::ServerLoop() { case IdProxyPacket: HandleProxyPacket(&event); break; + case IdLdnPacket: + HandleLdnPacket(&event); + break; case IdChatMessage: HandleChatPacket(&event); break; @@ -861,6 +870,60 @@ void Room::RoomImpl::HandleProxyPacket(const ENetEvent* event) { enet_host_flush(server); } +void Room::RoomImpl::HandleLdnPacket(const ENetEvent* event) { + Packet in_packet; + in_packet.Append(event->packet->data, event->packet->dataLength); + + in_packet.IgnoreBytes(sizeof(u8)); // Message type + + in_packet.IgnoreBytes(sizeof(u8)); // LAN packet type + in_packet.IgnoreBytes(sizeof(IPv4Address)); // Local IP + + IPv4Address remote_ip; + in_packet.Read(remote_ip); // Remote IP + + bool broadcast; + in_packet.Read(broadcast); // Broadcast + + Packet out_packet; + out_packet.Append(event->packet->data, event->packet->dataLength); + ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(), + ENET_PACKET_FLAG_RELIABLE); + + const auto& destination_address = remote_ip; + if (broadcast) { // Send the data to everyone except the sender + std::lock_guard lock(member_mutex); + bool sent_packet = false; + for (const auto& member : members) { + if (member.peer != event->peer) { + sent_packet = true; + enet_peer_send(member.peer, 0, enet_packet); + } + } + + if (!sent_packet) { + enet_packet_destroy(enet_packet); + } + } else { + std::lock_guard lock(member_mutex); + auto member = std::find_if(members.begin(), members.end(), + [destination_address](const Member& member_entry) -> bool { + return member_entry.fake_ip == destination_address; + }); + if (member != members.end()) { + enet_peer_send(member->peer, 0, enet_packet); + } else { + LOG_ERROR(Network, + "Attempting to send to unknown IP address: " + "{}.{}.{}.{}", + destination_address[0], destination_address[1], destination_address[2], + destination_address[3]); + enet_packet_destroy(enet_packet); + } + } + enet_host_flush(server); +} + void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) { Packet in_packet; in_packet.Append(event->packet->data, event->packet->dataLength); diff --git a/src/network/room.h b/src/network/room.h index c2a4b1a70..edbd3ecfb 100644 --- a/src/network/room.h +++ b/src/network/room.h @@ -40,6 +40,7 @@ enum RoomMessageTypes : u8 { IdRoomInformation, IdSetGameInfo, IdProxyPacket, + IdLdnPacket, IdChatMessage, IdNameCollision, IdIpCollision, diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index 06818af78..b94cb24ad 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -58,6 +58,7 @@ public: private: CallbackSet<ProxyPacket> callback_set_proxy_packet; + CallbackSet<LDNPacket> callback_set_ldn_packet; CallbackSet<ChatEntry> callback_set_chat_messages; CallbackSet<StatusMessageEntry> callback_set_status_messages; CallbackSet<RoomInformation> callback_set_room_information; @@ -108,6 +109,12 @@ public: void HandleProxyPackets(const ENetEvent* event); /** + * Extracts an LdnPacket from a received ENet packet. + * @param event The ENet event that was received. + */ + void HandleLdnPackets(const ENetEvent* event); + + /** * Extracts a chat entry from a received ENet packet and adds it to the chat queue. * @param event The ENet event that was received. */ @@ -166,6 +173,9 @@ void RoomMember::RoomMemberImpl::MemberLoop() { case IdProxyPacket: HandleProxyPackets(&event); break; + case IdLdnPacket: + HandleLdnPackets(&event); + break; case IdChatMessage: HandleChatPacket(&event); break; @@ -372,6 +382,27 @@ void RoomMember::RoomMemberImpl::HandleProxyPackets(const ENetEvent* event) { Invoke<ProxyPacket>(proxy_packet); } +void RoomMember::RoomMemberImpl::HandleLdnPackets(const ENetEvent* event) { + LDNPacket ldn_packet{}; + Packet packet; + packet.Append(event->packet->data, event->packet->dataLength); + + // Ignore the first byte, which is the message id. + packet.IgnoreBytes(sizeof(u8)); // Ignore the message type + + u8 packet_type; + packet.Read(packet_type); + ldn_packet.type = static_cast<LDNPacketType>(packet_type); + + packet.Read(ldn_packet.local_ip); + packet.Read(ldn_packet.remote_ip); + packet.Read(ldn_packet.broadcast); + + packet.Read(ldn_packet.data); + + Invoke<LDNPacket>(ldn_packet); +} + void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) { Packet packet; packet.Append(event->packet->data, event->packet->dataLength); @@ -450,6 +481,11 @@ RoomMember::RoomMemberImpl::CallbackSet<ProxyPacket>& RoomMember::RoomMemberImpl } template <> +RoomMember::RoomMemberImpl::CallbackSet<LDNPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() { + return callback_set_ldn_packet; +} + +template <> RoomMember::RoomMemberImpl::CallbackSet<RoomMember::State>& RoomMember::RoomMemberImpl::Callbacks::Get() { return callback_set_state; @@ -607,6 +643,21 @@ void RoomMember::SendProxyPacket(const ProxyPacket& proxy_packet) { room_member_impl->Send(std::move(packet)); } +void RoomMember::SendLdnPacket(const LDNPacket& ldn_packet) { + Packet packet; + packet.Write(static_cast<u8>(IdLdnPacket)); + + packet.Write(static_cast<u8>(ldn_packet.type)); + + packet.Write(ldn_packet.local_ip); + packet.Write(ldn_packet.remote_ip); + packet.Write(ldn_packet.broadcast); + + packet.Write(ldn_packet.data); + + room_member_impl->Send(std::move(packet)); +} + void RoomMember::SendChatMessage(const std::string& message) { Packet packet; packet.Write(static_cast<u8>(IdChatMessage)); @@ -663,6 +714,11 @@ RoomMember::CallbackHandle<ProxyPacket> RoomMember::BindOnProxyPacketReceived( return room_member_impl->Bind(callback); } +RoomMember::CallbackHandle<LDNPacket> RoomMember::BindOnLdnPacketReceived( + std::function<void(const LDNPacket&)> callback) { + return room_member_impl->Bind(std::move(callback)); +} + RoomMember::CallbackHandle<RoomInformation> RoomMember::BindOnRoomInformationChanged( std::function<void(const RoomInformation&)> callback) { return room_member_impl->Bind(callback); @@ -699,6 +755,7 @@ void RoomMember::Leave() { } template void RoomMember::Unbind(CallbackHandle<ProxyPacket>); +template void RoomMember::Unbind(CallbackHandle<LDNPacket>); template void RoomMember::Unbind(CallbackHandle<RoomMember::State>); template void RoomMember::Unbind(CallbackHandle<RoomMember::Error>); template void RoomMember::Unbind(CallbackHandle<RoomInformation>); diff --git a/src/network/room_member.h b/src/network/room_member.h index f578f7f6a..0d6417294 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -17,7 +17,24 @@ namespace Network { using AnnounceMultiplayerRoom::GameInfo; using AnnounceMultiplayerRoom::RoomInformation; -/// Information about the received WiFi packets. +enum class LDNPacketType : u8 { + Scan, + ScanResp, + Connect, + SyncNetwork, + Disconnect, + DestroyNetwork, +}; + +struct LDNPacket { + LDNPacketType type; + IPv4Address local_ip; + IPv4Address remote_ip; + bool broadcast; + std::vector<u8> data; +}; + +/// Information about the received proxy packets. struct ProxyPacket { SockAddrIn local_endpoint; SockAddrIn remote_endpoint; @@ -152,6 +169,12 @@ public: void SendProxyPacket(const ProxyPacket& packet); /** + * Sends an LDN packet to the room. + * @param packet The WiFi packet to send. + */ + void SendLdnPacket(const LDNPacket& packet); + + /** * Sends a chat message to the room. * @param message The contents of the message. */ @@ -205,6 +228,16 @@ public: std::function<void(const ProxyPacket&)> callback); /** + * Binds a function to an event that will be triggered every time an LDNPacket is received. + * The function wil be called everytime the event is triggered. + * The callback function must not bind or unbind a function. Doing so will cause a deadlock + * @param callback The function to call + * @return A handle used for removing the function from the registered list + */ + CallbackHandle<LDNPacket> BindOnLdnPacketReceived( + std::function<void(const LDNPacket&)> callback); + + /** * Binds a function to an event that will be triggered every time the RoomInformation changes. * The function wil be called every time the event is triggered. * The callback function must not bind or unbind a function. Doing so will cause a deadlock diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index e2c2b9292..612b8dbb8 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -899,8 +899,8 @@ void GMainWindow::InitializeWidgets() { } // TODO (flTobi): Add the widget when multiplayer is fully implemented - // statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0); - // statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0); + statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0); + statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0); tas_label = new QLabel(); tas_label->setObjectName(QStringLiteral("TASlabel")); @@ -1299,6 +1299,7 @@ void GMainWindow::ConnectMenuEvents() { &MultiplayerState::OnDirectConnectToRoom); connect(ui->action_Show_Room, &QAction::triggered, multiplayer_state, &MultiplayerState::OnOpenNetworkRoom); + connect(multiplayer_state, &MultiplayerState::SaveConfig, this, &GMainWindow::OnSaveConfig); // Tools connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this, @@ -1339,6 +1340,8 @@ void GMainWindow::UpdateMenuState() { } else { ui->action_Pause->setText(tr("&Pause")); } + + multiplayer_state->UpdateNotificationStatus(); } void GMainWindow::OnDisplayTitleBars(bool show) { @@ -2770,6 +2773,11 @@ void GMainWindow::OnExit() { OnStopGame(); } +void GMainWindow::OnSaveConfig() { + system->ApplySettings(); + config->Save(); +} + void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_text) { OverlayDialog dialog(render_window, *system, error_code, error_text, QString{}, tr("OK"), Qt::AlignLeft | Qt::AlignVCenter); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 716aef063..f7aa8e417 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -169,6 +169,7 @@ public slots: void OnLoadComplete(); void OnExecuteProgram(std::size_t program_index); void OnExit(); + void OnSaveConfig(); void ControllerSelectorReconfigureControllers( const Core::Frontend::ControllerParameters& parameters); void SoftwareKeyboardInitialize( diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index cdf31b417..74d49dbd4 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -120,6 +120,20 @@ <addaction name="menu_Reset_Window_Size"/> <addaction name="menu_View_Debugging"/> </widget> + <widget class="QMenu" name="menu_Multiplayer"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="title"> + <string>&Multiplayer</string> + </property> + <addaction name="action_View_Lobby"/> + <addaction name="action_Start_Room"/> + <addaction name="action_Connect_To_Room"/> + <addaction name="separator"/> + <addaction name="action_Show_Room"/> + <addaction name="action_Leave_Room"/> + </widget> <widget class="QMenu" name="menu_Tools"> <property name="title"> <string>&Tools</string> @@ -251,7 +265,7 @@ <bool>true</bool> </property> <property name="text"> - <string>Browse Public Game Lobby</string> + <string>&Browse Public Game Lobby</string> </property> </action> <action name="action_Start_Room"> @@ -259,7 +273,7 @@ <bool>true</bool> </property> <property name="text"> - <string>Create Room</string> + <string>&Create Room</string> </property> </action> <action name="action_Leave_Room"> @@ -267,12 +281,12 @@ <bool>false</bool> </property> <property name="text"> - <string>Leave Room</string> + <string>&Leave Room</string> </property> </action> <action name="action_Connect_To_Room"> <property name="text"> - <string>Direct Connect to Room</string> + <string>&Direct Connect to Room</string> </property> </action> <action name="action_Show_Room"> @@ -280,7 +294,7 @@ <bool>false</bool> </property> <property name="text"> - <string>Show Current Room</string> + <string>&Show Current Room</string> </property> </action> <action name="action_Fullscreen"> diff --git a/src/yuzu/multiplayer/chat_room.cpp b/src/yuzu/multiplayer/chat_room.cpp index 9e672f82e..dec9696c1 100644 --- a/src/yuzu/multiplayer/chat_room.cpp +++ b/src/yuzu/multiplayer/chat_room.cpp @@ -61,7 +61,10 @@ public: /// Format the message using the players color QString GetPlayerChatMessage(u16 player) const { - auto color = player_color[player % 16]; + const bool is_dark_theme = QIcon::themeName().contains(QStringLiteral("dark")) || + QIcon::themeName().contains(QStringLiteral("midnight")); + auto color = + is_dark_theme ? player_color_dark[player % 16] : player_color_default[player % 16]; QString name; if (username.isEmpty() || username == nickname) { name = nickname; @@ -84,9 +87,12 @@ public: } private: - static constexpr std::array<const char*, 16> player_color = { + static constexpr std::array<const char*, 16> player_color_default = { {"#0000FF", "#FF0000", "#8A2BE2", "#FF69B4", "#1E90FF", "#008000", "#00FF7F", "#B22222", - "#DAA520", "#FF4500", "#2E8B57", "#5F9EA0", "#D2691E", "#9ACD32", "#FF7F50", "FFFF00"}}; + "#DAA520", "#FF4500", "#2E8B57", "#5F9EA0", "#D2691E", "#9ACD32", "#FF7F50", "#FFFF00"}}; + static constexpr std::array<const char*, 16> player_color_dark = { + {"#559AD1", "#4EC9A8", "#D69D85", "#C6C923", "#B975B5", "#D81F1F", "#7EAE39", "#4F8733", + "#F7CD8A", "#6FCACF", "#CE4897", "#8A2BE2", "#D2691E", "#9ACD32", "#FF7F50", "#152ccd"}}; static constexpr char ping_color[] = "#FFFF00"; QString timestamp; diff --git a/src/yuzu/multiplayer/client_room.cpp b/src/yuzu/multiplayer/client_room.cpp index b34a8d004..caf34a414 100644 --- a/src/yuzu/multiplayer/client_room.cpp +++ b/src/yuzu/multiplayer/client_room.cpp @@ -97,8 +97,9 @@ void ClientRoomWindow::UpdateView() { auto memberlist = member->GetMemberInformation(); ui->chat->SetPlayerList(memberlist); const auto information = member->GetRoomInformation(); - setWindowTitle(QString(tr("%1 (%2/%3 members) - connected")) + setWindowTitle(QString(tr("%1 - %2 (%3/%4 members) - connected")) .arg(QString::fromStdString(information.name)) + .arg(QString::fromStdString(information.preferred_game.name)) .arg(memberlist.size()) .arg(information.member_slots)); ui->description->setText(QString::fromStdString(information.description)); diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp index 017063074..10bf0a4fb 100644 --- a/src/yuzu/multiplayer/direct_connect.cpp +++ b/src/yuzu/multiplayer/direct_connect.cpp @@ -106,6 +106,8 @@ void DirectConnectWindow::Connect() { UISettings::values.multiplayer_port = UISettings::values.multiplayer_port.GetDefault(); } + emit SaveConfig(); + // attempt to connect in a different thread QFuture<void> f = QtConcurrent::run([&] { if (auto room_member = room_network.GetRoomMember().lock()) { diff --git a/src/yuzu/multiplayer/direct_connect.h b/src/yuzu/multiplayer/direct_connect.h index e39dd1e0d..b8f66cfb2 100644 --- a/src/yuzu/multiplayer/direct_connect.h +++ b/src/yuzu/multiplayer/direct_connect.h @@ -31,6 +31,7 @@ signals: * connections that it might have. */ void Closed(); + void SaveConfig(); private slots: void OnConnection(); diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp index 0c6adfd04..a8faa5b24 100644 --- a/src/yuzu/multiplayer/host_room.cpp +++ b/src/yuzu/multiplayer/host_room.cpp @@ -232,6 +232,7 @@ void HostRoomWindow::Host() { } UISettings::values.multiplayer_room_description = ui->room_description->toPlainText(); ui->host->setEnabled(true); + emit SaveConfig(); close(); } } diff --git a/src/yuzu/multiplayer/host_room.h b/src/yuzu/multiplayer/host_room.h index 034cb2eef..ae816e2e0 100644 --- a/src/yuzu/multiplayer/host_room.h +++ b/src/yuzu/multiplayer/host_room.h @@ -46,6 +46,9 @@ public: void UpdateGameList(QStandardItemModel* list); void RetranslateUi(); +signals: + void SaveConfig(); + private: void Host(); std::unique_ptr<Network::VerifyUser::Backend> CreateVerifyBackend(bool use_validation) const; diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp index 107d40547..08c275696 100644 --- a/src/yuzu/multiplayer/lobby.cpp +++ b/src/yuzu/multiplayer/lobby.cpp @@ -7,6 +7,7 @@ #include "common/logging/log.h" #include "common/settings.h" #include "core/core.h" +#include "core/hle/service/acc/profile_manager.h" #include "core/internal_network/network_interface.h" #include "network/network.h" #include "ui_lobby.h" @@ -26,9 +27,9 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list, std::shared_ptr<Core::AnnounceMultiplayerSession> session, Core::System& system_) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), - ui(std::make_unique<Ui::Lobby>()), - announce_multiplayer_session(session), system{system_}, room_network{ - system.GetRoomNetwork()} { + ui(std::make_unique<Ui::Lobby>()), announce_multiplayer_session(session), + profile_manager(std::make_unique<Service::Account::ProfileManager>()), system{system_}, + room_network{system.GetRoomNetwork()} { ui->setupUi(this); // setup the watcher for background connections @@ -60,9 +61,17 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list, ui->nickname->setValidator(validation.GetNickname()); ui->nickname->setText(UISettings::values.multiplayer_nickname.GetValue()); - if (ui->nickname->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) { - // Use yuzu Web Service user name as nickname by default - ui->nickname->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue())); + + // Try find the best nickname by default + if (ui->nickname->text().isEmpty() || ui->nickname->text() == QStringLiteral("yuzu")) { + if (!Settings::values.yuzu_username.GetValue().empty()) { + ui->nickname->setText( + QString::fromStdString(Settings::values.yuzu_username.GetValue())); + } else if (!GetProfileUsername().empty()) { + ui->nickname->setText(QString::fromStdString(GetProfileUsername())); + } else { + ui->nickname->setText(QStringLiteral("yuzu")); + } } // UI Buttons @@ -76,12 +85,6 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list, // Actions connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this, &Lobby::OnRefreshLobby); - - // manually start a refresh when the window is opening - // TODO(jroweboy): if this refresh is slow for people with bad internet, then don't do it as - // part of the constructor, but offload the refresh until after the window shown. perhaps emit a - // refreshroomlist signal from places that open the lobby - RefreshLobby(); } Lobby::~Lobby() = default; @@ -96,6 +99,7 @@ void Lobby::UpdateGameList(QStandardItemModel* list) { } if (proxy) proxy->UpdateGameList(game_list); + ui->room_list->sortByColumn(Column::GAME_NAME, Qt::AscendingOrder); } void Lobby::RetranslateUi() { @@ -117,6 +121,11 @@ void Lobby::OnExpandRoom(const QModelIndex& index) { void Lobby::OnJoinRoom(const QModelIndex& source) { if (!Network::GetSelectedNetworkInterface()) { + LOG_INFO(WebService, "Automatically selected network interface for room network."); + Network::SelectFirstNetworkInterface(); + } + + if (!Network::GetSelectedNetworkInterface()) { NetworkMessage::ErrorManager::ShowError( NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED); return; @@ -197,16 +206,16 @@ void Lobby::OnJoinRoom(const QModelIndex& source) { proxy->data(connection_index, LobbyItemHost::HostIPRole).toString(); UISettings::values.multiplayer_port = proxy->data(connection_index, LobbyItemHost::HostPortRole).toInt(); + emit SaveConfig(); } void Lobby::ResetModel() { model->clear(); model->insertColumns(0, Column::TOTAL); - model->setHeaderData(Column::EXPAND, Qt::Horizontal, QString(), Qt::DisplayRole); + model->setHeaderData(Column::MEMBER, Qt::Horizontal, tr("Players"), Qt::DisplayRole); model->setHeaderData(Column::ROOM_NAME, Qt::Horizontal, tr("Room Name"), Qt::DisplayRole); model->setHeaderData(Column::GAME_NAME, Qt::Horizontal, tr("Preferred Game"), Qt::DisplayRole); model->setHeaderData(Column::HOST, Qt::Horizontal, tr("Host"), Qt::DisplayRole); - model->setHeaderData(Column::MEMBER, Qt::Horizontal, tr("Players"), Qt::DisplayRole); } void Lobby::RefreshLobby() { @@ -229,6 +238,7 @@ void Lobby::OnRefreshLobby() { for (int r = 0; r < game_list->rowCount(); ++r) { auto index = game_list->index(r, 0); auto game_id = game_list->data(index, GameListItemPath::ProgramIdRole).toULongLong(); + if (game_id != 0 && room.information.preferred_game.id == game_id) { smdh_icon = game_list->data(index, Qt::DecorationRole).value<QPixmap>(); } @@ -243,17 +253,16 @@ void Lobby::OnRefreshLobby() { members.append(var); } - auto first_item = new LobbyItem(); + auto first_item = new LobbyItemGame( + room.information.preferred_game.id, + QString::fromStdString(room.information.preferred_game.name), smdh_icon); auto row = QList<QStandardItem*>({ first_item, new LobbyItemName(room.has_password, QString::fromStdString(room.information.name)), - new LobbyItemGame(room.information.preferred_game.id, - QString::fromStdString(room.information.preferred_game.name), - smdh_icon), + new LobbyItemMemberList(members, room.information.member_slots), new LobbyItemHost(QString::fromStdString(room.information.host_username), QString::fromStdString(room.ip), room.information.port, QString::fromStdString(room.verify_uid)), - new LobbyItemMemberList(members, room.information.member_slots), }); model->appendRow(row); // To make the rows expandable, add the member data as a child of the first column of the @@ -283,6 +292,26 @@ void Lobby::OnRefreshLobby() { ui->room_list->setFirstColumnSpanned(j, proxy->index(i, 0), true); } } + + ui->room_list->sortByColumn(Column::GAME_NAME, Qt::AscendingOrder); +} + +std::string Lobby::GetProfileUsername() { + const auto& current_user = profile_manager->GetUser(Settings::values.current_user.GetValue()); + Service::Account::ProfileBase profile{}; + + if (!current_user.has_value()) { + return ""; + } + + if (!profile_manager->GetProfileBase(*current_user, profile)) { + return ""; + } + + const auto text = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(profile.username.data()), profile.username.size()); + + return text; } LobbyFilterProxyModel::LobbyFilterProxyModel(QWidget* parent, QStandardItemModel* list) diff --git a/src/yuzu/multiplayer/lobby.h b/src/yuzu/multiplayer/lobby.h index 2696aec21..300dad13e 100644 --- a/src/yuzu/multiplayer/lobby.h +++ b/src/yuzu/multiplayer/lobby.h @@ -24,6 +24,10 @@ namespace Core { class System; } +namespace Service::Account { +class ProfileManager; +} + /** * Listing of all public games pulled from services. The lobby should be simple enough for users to * find the game they want to play, and join it. @@ -75,8 +79,11 @@ private slots: signals: void StateChanged(const Network::RoomMember::State&); + void SaveConfig(); private: + std::string GetProfileUsername(); + /** * Removes all entries in the Lobby before refreshing. */ @@ -96,6 +103,7 @@ private: QFutureWatcher<AnnounceMultiplayerRoom::RoomList> room_list_watcher; std::weak_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session; + std::unique_ptr<Service::Account::ProfileManager> profile_manager; QFutureWatcher<void>* watcher; Validation validation; Core::System& system; diff --git a/src/yuzu/multiplayer/lobby_p.h b/src/yuzu/multiplayer/lobby_p.h index 8071cede4..068c95aca 100644 --- a/src/yuzu/multiplayer/lobby_p.h +++ b/src/yuzu/multiplayer/lobby_p.h @@ -11,11 +11,10 @@ namespace Column { enum List { - EXPAND, - ROOM_NAME, GAME_NAME, - HOST, + ROOM_NAME, MEMBER, + HOST, TOTAL, }; } @@ -91,6 +90,8 @@ public: setData(game_name, GameNameRole); if (!smdh_icon.isNull()) { setData(smdh_icon, GameIconRole); + } else { + setData(QIcon::fromTheme(QStringLiteral("chip")).pixmap(32), GameIconRole); } } @@ -98,7 +99,12 @@ public: if (role == Qt::DecorationRole) { auto val = data(GameIconRole); if (val.isValid()) { - val = val.value<QPixmap>().scaled(16, 16, Qt::KeepAspectRatio); + val = val.value<QPixmap>().scaled(32, 32, Qt::KeepAspectRatio, + Qt::TransformationMode::SmoothTransformation); + } else { + auto blank_image = QPixmap(32, 32); + blank_image.fill(Qt::black); + val = blank_image; } return val; } else if (role != Qt::DisplayRole) { @@ -191,8 +197,8 @@ public: return LobbyItem::data(role); } auto members = data(MemberListRole).toList(); - return QStringLiteral("%1 / %2").arg(QString::number(members.size()), - data(MaxPlayerRole).toString()); + return QStringLiteral("%1 / %2 ") + .arg(QString::number(members.size()), data(MaxPlayerRole).toString()); } bool operator<(const QStandardItem& other) const override { diff --git a/src/yuzu/multiplayer/message.cpp b/src/yuzu/multiplayer/message.cpp index 758b5b731..6d8f18274 100644 --- a/src/yuzu/multiplayer/message.cpp +++ b/src/yuzu/multiplayer/message.cpp @@ -49,9 +49,9 @@ const ConnectionError ErrorManager::PERMISSION_DENIED( QT_TR_NOOP("You do not have enough permission to perform this action.")); const ConnectionError ErrorManager::NO_SUCH_USER(QT_TR_NOOP( "The user you are trying to kick/ban could not be found.\nThey may have left the room.")); -const ConnectionError ErrorManager::NO_INTERFACE_SELECTED( - QT_TR_NOOP("No network interface is selected.\nPlease go to Configure -> System -> Network and " - "make a selection.")); +const ConnectionError ErrorManager::NO_INTERFACE_SELECTED(QT_TR_NOOP( + "No valid network interface is selected.\nPlease go to Configure -> System -> Network and " + "make a selection.")); static bool WarnMessage(const std::string& title, const std::string& text) { return QMessageBox::Ok == QMessageBox::warning(nullptr, QObject::tr(title.c_str()), diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp index 66e098296..ae2738ad4 100644 --- a/src/yuzu/multiplayer/state.cpp +++ b/src/yuzu/multiplayer/state.cpp @@ -44,9 +44,6 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis status_text = new ClickableLabel(this); status_icon = new ClickableLabel(this); - status_text->setToolTip(tr("Current connection status")); - status_text->setText(tr("Not Connected. Click here to find a room!")); - status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16)); connect(status_text, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom); connect(status_icon, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom); @@ -57,6 +54,8 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis HideNotification(); } }); + + retranslateUi(); } MultiplayerState::~MultiplayerState() = default; @@ -90,14 +89,7 @@ void MultiplayerState::Close() { void MultiplayerState::retranslateUi() { status_text->setToolTip(tr("Current connection status")); - if (current_state == Network::RoomMember::State::Uninitialized) { - status_text->setText(tr("Not Connected. Click here to find a room!")); - } else if (current_state == Network::RoomMember::State::Joined || - current_state == Network::RoomMember::State::Moderator) { - status_text->setText(tr("Connected")); - } else { - status_text->setText(tr("Not Connected")); - } + UpdateNotificationStatus(); if (lobby) { lobby->RetranslateUi(); @@ -113,21 +105,55 @@ void MultiplayerState::retranslateUi() { } } +void MultiplayerState::SetNotificationStatus(NotificationStatus status) { + notification_status = status; + UpdateNotificationStatus(); +} + +void MultiplayerState::UpdateNotificationStatus() { + switch (notification_status) { + case NotificationStatus::Unitialized: + status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16)); + status_text->setText(tr("Not Connected. Click here to find a room!")); + leave_room->setEnabled(false); + show_room->setEnabled(false); + break; + case NotificationStatus::Disconnected: + status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16)); + status_text->setText(tr("Not Connected")); + leave_room->setEnabled(false); + show_room->setEnabled(false); + break; + case NotificationStatus::Connected: + status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16)); + status_text->setText(tr("Connected")); + leave_room->setEnabled(true); + show_room->setEnabled(true); + break; + case NotificationStatus::Notification: + status_icon->setPixmap( + QIcon::fromTheme(QStringLiteral("connected_notification")).pixmap(16)); + status_text->setText(tr("New Messages Received")); + leave_room->setEnabled(true); + show_room->setEnabled(true); + break; + } + + // Clean up status bar if game is running + if (system.IsPoweredOn()) { + status_text->clear(); + } +} + void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& state) { LOG_DEBUG(Frontend, "Network State: {}", Network::GetStateStr(state)); if (state == Network::RoomMember::State::Joined || state == Network::RoomMember::State::Moderator) { OnOpenNetworkRoom(); - status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16)); - status_text->setText(tr("Connected")); - leave_room->setEnabled(true); - show_room->setEnabled(true); + SetNotificationStatus(NotificationStatus::Connected); } else { - status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16)); - status_text->setText(tr("Not Connected")); - leave_room->setEnabled(false); - show_room->setEnabled(false); + SetNotificationStatus(NotificationStatus::Disconnected); } current_state = state; @@ -185,6 +211,10 @@ void MultiplayerState::OnAnnounceFailed(const WebService::WebResult& result) { QMessageBox::Ok); } +void MultiplayerState::OnSaveConfig() { + emit SaveConfig(); +} + void MultiplayerState::UpdateThemedIcons() { if (show_notification) { status_icon->setPixmap( @@ -209,13 +239,16 @@ static void BringWidgetToFront(QWidget* widget) { void MultiplayerState::OnViewLobby() { if (lobby == nullptr) { lobby = new Lobby(this, game_list_model, announce_multiplayer_session, system); + connect(lobby, &Lobby::SaveConfig, this, &MultiplayerState::OnSaveConfig); } + lobby->RefreshLobby(); BringWidgetToFront(lobby); } void MultiplayerState::OnCreateRoom() { if (host_room == nullptr) { host_room = new HostRoomWindow(this, game_list_model, announce_multiplayer_session, system); + connect(host_room, &HostRoomWindow::SaveConfig, this, &MultiplayerState::OnSaveConfig); } BringWidgetToFront(host_room); } @@ -249,14 +282,13 @@ void MultiplayerState::ShowNotification() { return; // Do not show notification if the chat window currently has focus show_notification = true; QApplication::alert(nullptr); - status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected_notification")).pixmap(16)); - status_text->setText(tr("New Messages Received")); + QApplication::beep(); + SetNotificationStatus(NotificationStatus::Notification); } void MultiplayerState::HideNotification() { show_notification = false; - status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16)); - status_text->setText(tr("Connected")); + SetNotificationStatus(NotificationStatus::Connected); } void MultiplayerState::OnOpenNetworkRoom() { @@ -279,6 +311,8 @@ void MultiplayerState::OnOpenNetworkRoom() { void MultiplayerState::OnDirectConnectToRoom() { if (direct_connect == nullptr) { direct_connect = new DirectConnectWindow(system, this); + connect(direct_connect, &DirectConnectWindow::SaveConfig, this, + &MultiplayerState::OnSaveConfig); } BringWidgetToFront(direct_connect); } diff --git a/src/yuzu/multiplayer/state.h b/src/yuzu/multiplayer/state.h index c92496413..5d681c5c6 100644 --- a/src/yuzu/multiplayer/state.h +++ b/src/yuzu/multiplayer/state.h @@ -22,6 +22,13 @@ class MultiplayerState : public QWidget { Q_OBJECT; public: + enum class NotificationStatus { + Unitialized, + Disconnected, + Connected, + Notification, + }; + explicit MultiplayerState(QWidget* parent, QStandardItemModel* game_list, QAction* leave_room, QAction* show_room, Core::System& system_); ~MultiplayerState(); @@ -31,6 +38,10 @@ public: */ void Close(); + void SetNotificationStatus(NotificationStatus state); + + void UpdateNotificationStatus(); + ClickableLabel* GetStatusText() const { return status_text; } @@ -64,6 +75,7 @@ public slots: void OnOpenNetworkRoom(); void OnDirectConnectToRoom(); void OnAnnounceFailed(const WebService::WebResult&); + void OnSaveConfig(); void UpdateThemedIcons(); void ShowNotification(); void HideNotification(); @@ -72,6 +84,7 @@ signals: void NetworkStateChanged(const Network::RoomMember::State&); void NetworkError(const Network::RoomMember::Error&); void AnnounceFailed(const WebService::WebResult&); + void SaveConfig(); private: Lobby* lobby = nullptr; @@ -85,6 +98,7 @@ private: QAction* show_room; std::shared_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session; Network::RoomMember::State current_state = Network::RoomMember::State::Uninitialized; + NotificationStatus notification_status = NotificationStatus::Unitialized; bool has_mod_perms = false; Network::RoomMember::CallbackHandle<Network::RoomMember::State> state_callback_handle; Network::RoomMember::CallbackHandle<Network::RoomMember::Error> error_callback_handle; diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index e12d414d9..753797efc 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -102,7 +102,7 @@ struct Values { Settings::Setting<uint32_t> callout_flags{0, "calloutFlags"}; // multiplayer settings - Settings::Setting<QString> multiplayer_nickname{QStringLiteral("yuzu"), "nickname"}; + Settings::Setting<QString> multiplayer_nickname{{}, "nickname"}; Settings::Setting<QString> multiplayer_ip{{}, "ip"}; Settings::SwitchableSetting<uint, true> multiplayer_port{24872, 0, UINT16_MAX, "port"}; Settings::Setting<QString> multiplayer_room_nickname{{}, "room_nickname"}; |