From 404b95cf8c189dc9d1ff2fcd9885347797e7ce1e Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-7SFAKTP\\Ron" Date: Sat, 13 Sep 2025 03:02:29 -0700 Subject: [PATCH] Added NWM spectator mode (DLP now partially working), fixed debug assert, added applet utility cmd fallback --- src/core/hle/service/am/am.cpp | 1 + src/core/hle/service/apt/apt.cpp | 19 +++-- src/core/hle/service/nwm/nwm_uds.cpp | 111 +++++++++++++++++--------- src/core/hle/service/nwm/nwm_uds.h | 11 ++- src/core/hle/service/nwm/uds_common.h | 16 ++++ src/core/hle/service/nwm/uds_data.cpp | 18 +++-- src/core/hle/service/nwm/uds_data.h | 10 ++- 7 files changed, 129 insertions(+), 57 deletions(-) create mode 100644 src/core/hle/service/nwm/uds_common.h diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 1b8c97bae..b69d22acc 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -3451,6 +3451,7 @@ void Module::Interface::GetProgramInfoFromCia(Kernel::HLERequestContext& ctx) { IPC::RequestBuilder rb = rp.MakeBuilder(8, 0); rb.Push(ResultSuccess); rb.PushRaw(title_info); + rb.Push(0x0); // make num words pushed match header so no assert occurrs } void Module::Interface::GetSystemMenuDataFromCia(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 002bd3728..d776ea1cf 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -698,11 +698,20 @@ void Module::APTInterface::AppletUtility(Kernel::HLERequestContext& ctx) { utility_command, input_size, output_size); std::vector out(output_size); - if (utility_command == 0x6 && output_size > 0) { - // Command 0x6 (TryLockTransition) expects a boolean return value indicating - // whether the attempt succeeded. Since we don't implement any of the transition - // locking stuff yet, fake a success result to avoid app crashes. - out[0] = true; + switch (utility_command) { + case 0x6: { + // Command 0x6 (TryLockTransition) expects a boolean return value indicating + // whether the attempt succeeded. Since we don't implement any of the transition + // locking stuff yet, fake a success result to avoid app crashes. + if (output_size > 0) + out[0] = true; + break; + } + case 0x4: { // unknown: luigi's mansion 1 multiplayer requires this. works when byte 0 of output is set to true. + if (output_size > 0) + out[0] = true; + break; + } } IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp index 3a08ffbee..857e489af 100644 --- a/src/core/hle/service/nwm/nwm_uds.cpp +++ b/src/core/hle/service/nwm/nwm_uds.cpp @@ -106,15 +106,16 @@ void NWM_UDS::BroadcastNodeMap() { packet.channel = network_channel; packet.type = Network::WifiPacket::PacketType::NodeMap; packet.destination_address = Network::BroadcastMac; + auto node_can_broad = [](auto& node) -> bool {return node.second.connected && !node.second.spec;}; std::size_t num_entries = std::count_if(node_map.begin(), node_map.end(), - [](const auto& node) { return node.second.connected; }); + [&node_can_broad](const auto& node) { return node_can_broad(node); }); using node_t = decltype(node_map)::value_type; packet.data.resize(sizeof(num_entries) + (sizeof(node_t::first) + sizeof(node_t::second.node_id)) * num_entries); std::memcpy(packet.data.data(), &num_entries, sizeof(num_entries)); std::size_t offset = sizeof(num_entries); for (const auto& node : node_map) { - if (node.second.connected) { + if (node_can_broad(node)) { std::memcpy(packet.data.data() + offset, node.first.data(), sizeof(node.first)); std::memcpy(packet.data.data() + offset + sizeof(node.first), &node.second.node_id, sizeof(node.second.node_id)); @@ -185,7 +186,7 @@ void NWM_UDS::HandleAssociationResponseFrame(const Network::WifiPacket& packet) using Network::WifiPacket; WifiPacket eapol_start; eapol_start.channel = network_channel; - eapol_start.data = GenerateEAPoLStartFrame(std::get(assoc_result), current_node); + eapol_start.data = GenerateEAPoLStartFrame(std::get(assoc_result), conn_type, current_node); // TODO(B3N30): Encrypt the packet. eapol_start.destination_address = packet.transmitter_address; eapol_start.type = WifiPacket::PacketType::Data; @@ -217,24 +218,35 @@ void NWM_UDS::HandleEAPoLPacket(const Network::WifiPacket& packet) { ASSERT(connection_status.max_nodes != connection_status.total_nodes); - auto node = DeserializeNodeInfoFromFrame(packet.data); + auto eapol_start = DeserializeEAPolStartPacket(packet.data); - // Get an unused network node id - u16 node_id = GetNextAvailableNodeId(); - node.network_node_id = node_id; + auto node = DeserializeNodeInfo(eapol_start.node); - connection_status.node_bitmask |= 1 << (node_id - 1); - connection_status.changed_nodes |= 1 << (node_id - 1); - connection_status.nodes[node_id - 1] = node.network_node_id; - connection_status.total_nodes++; + if (eapol_start.conn_type == ConnectionType::Client) { + // Get an unused network node id + u16 node_id = GetNextAvailableNodeId(); + node.network_node_id = node_id; - node_info[node_id - 1] = node; - network_info.total_nodes++; + connection_status.node_bitmask |= 1 << (node_id - 1); + connection_status.changed_nodes |= 1 << (node_id - 1); + connection_status.nodes[node_id - 1] = node.network_node_id; + connection_status.total_nodes++; - node_map[packet.transmitter_address].node_id = node.network_node_id; - node_map[packet.transmitter_address].connected = true; + node_info[node_id - 1] = node; + network_info.total_nodes++; - BroadcastNodeMap(); + node_map[packet.transmitter_address].node_id = node.network_node_id; + node_map[packet.transmitter_address].connected = true; + node_map[packet.transmitter_address].spec = false; + + BroadcastNodeMap(); + } else if (eapol_start.conn_type == ConnectionType::Spectator) { + node_map[packet.transmitter_address].node_id = NodeIDSpec; + node_map[packet.transmitter_address].connected = true; + node_map[packet.transmitter_address].spec = true; + } else { + LOG_ERROR(Service_NWM, "Client tried connecting with unknown connection type: 0x{:x}", static_cast(eapol_start.conn_type)); + } // Send the EAPoL-Logoff packet. using Network::WifiPacket; @@ -282,15 +294,23 @@ void NWM_UDS::HandleEAPoLPacket(const Network::WifiPacket& packet) { node_info[index - 1] = DeserializeNodeInfo(node); } + if (conn_type == ConnectionType::Client) { + connection_status.status = NetworkStatus::ConnectedAsClient; + } else if (conn_type == ConnectionType::Spectator) { + connection_status.status = NetworkStatus::ConnectedAsSpectator; + } else { + LOG_ERROR(Service_NWM, "Unknown connection type: 0x{:x}", static_cast(conn_type)); + } + // We're now connected, signal the application - connection_status.status = NetworkStatus::ConnectedAsClient; connection_status.status_change_reason = NetworkStatusChangeReason::ConnectionEstablished; // Some games require ConnectToNetwork to block, for now it doesn't // If blocking is implemented this lock needs to be changed, // otherwise it might cause deadlocks connection_status_event->Signal(); connection_event->Signal(); - } else if (connection_status.status == NetworkStatus::ConnectedAsClient) { + } else if (connection_status.status == NetworkStatus::ConnectedAsClient || + connection_status.status == NetworkStatus::ConnectedAsSpectator) { // TODO(B3N30): Remove that section and send/receive a proper connection_status packet // On a 3ds this packet wouldn't be addressed to already connected clients // We use this information because in the current implementation the host @@ -325,12 +345,13 @@ void NWM_UDS::HandleEAPoLPacket(const Network::WifiPacket& packet) { void NWM_UDS::HandleSecureDataPacket(const Network::WifiPacket& packet) { const auto secure_data = ParseSecureDataHeader(packet.data); - std::scoped_lock lock{connection_status_mutex, system.Kernel().GetHLELock()}; + std::scoped_lock lock{connection_status_mutex, + system.Kernel().GetHLELock()}; if (connection_status.status != NetworkStatus::ConnectedAsHost && - connection_status.status != NetworkStatus::ConnectedAsClient) { - // TODO(B3N30): Handle spectators - LOG_DEBUG(Service_NWM, "Ignored SecureDataPacket, because connection status is {}", + connection_status.status != NetworkStatus::ConnectedAsClient && + connection_status.status != NetworkStatus::ConnectedAsSpectator) { + LOG_ERROR(Service_NWM, "Ignored SecureDataPacket because connection status is {}", static_cast(connection_status.status)); return; } @@ -370,12 +391,14 @@ void NWM_UDS::HandleSecureDataPacket(const Network::WifiPacket& packet) { // TODO(B3N30): Allow more than one bind node per channel. auto channel_info = channel_data.find(secure_data.data_channel); // Ignore packets from channels we're not interested in. - if (channel_info == channel_data.end()) + if (channel_info == channel_data.end()) { return; + } if (channel_info->second.network_node_id != BroadcastNetworkNodeId && - channel_info->second.network_node_id != secure_data.src_node_id) + channel_info->second.network_node_id != secure_data.src_node_id) { return; + } // Add the received packet to the data queue. channel_info->second.received_packets.emplace_back(packet.data); @@ -432,7 +455,9 @@ void NWM_UDS::HandleAuthenticationFrame(const Network::WifiPacket& packet) { // Only the SEQ1 auth frame is handled here, the SEQ2 frame doesn't need any special behavior if (GetAuthenticationSeqNumber(packet.data) == AuthenticationSeq::SEQ1) { using Network::WifiPacket; - WifiPacket auth_request; + AuthenticationFrame auth_request; + memcpy(&auth_request, packet.data.data(), sizeof(auth_request)); + WifiPacket auth_response; { std::scoped_lock lock(connection_status_mutex); if (connection_status.status != NetworkStatus::ConnectedAsHost) { @@ -454,13 +479,13 @@ void NWM_UDS::HandleAuthenticationFrame(const Network::WifiPacket& packet) { return; } // Respond with an authentication response frame with SEQ2 - auth_request.channel = network_channel; - auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ2); - auth_request.destination_address = packet.transmitter_address; - auth_request.type = WifiPacket::PacketType::Authentication; + auth_response.channel = network_channel; + auth_response.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ2); + auth_response.destination_address = packet.transmitter_address; + auth_response.type = WifiPacket::PacketType::Authentication; node_map[packet.transmitter_address].connected = false; } - SendPacket(auth_request); + SendPacket(auth_response); SendAssociationResponseFrame(packet.transmitter_address); } @@ -495,16 +520,16 @@ void NWM_UDS::HandleDeauthenticationFrame(const Network::WifiPacket& packet) { return; } - connection_status.node_bitmask &= ~(1 << (node.node_id - 1)); - connection_status.changed_nodes |= 1 << (node.node_id - 1); - connection_status.total_nodes--; - connection_status.nodes[node.node_id - 1] = 0; - - network_info.total_nodes--; - // TODO(B3N30): broadcast new connection_status to clients + if (!node.spec) { + connection_status.node_bitmask &= ~(1 << (node.node_id - 1)); + connection_status.changed_nodes |= 1 << (node.node_id - 1); + connection_status.total_nodes--; + connection_status.nodes[node.node_id - 1] = 0; + network_info.total_nodes--; + // TODO(B3N30): broadcast new connection_status to clients + } node_it->Reset(); - connection_status_event->Signal(); } @@ -588,14 +613,20 @@ void NWM_UDS::RecvBeaconBroadcastData(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 out_buffer_size = rp.Pop(); + + // scan input struct u32 unk1 = rp.Pop(); u32 unk2 = rp.Pop(); MacAddress mac_address; rp.PopRaw(mac_address); + // uninitialized data in scan input struct rp.Skip(9, false); + // end scan input struct + + u32 wlan_comm_id = rp.Pop(); u32 id = rp.Pop(); // From 3dbrew: @@ -765,7 +796,7 @@ void NWM_UDS::Bind(Kernel::HLERequestContext& ctx) { u8 data_channel = rp.Pop(); u16 network_node_id = rp.Pop(); - LOG_DEBUG(Service_NWM, "called"); + LOG_INFO(Service_NWM, "called"); if (data_channel == 0 || bind_node_id == 0) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); @@ -1042,6 +1073,7 @@ void NWM_UDS::DestroyNetwork(Kernel::HLERequestContext& ctx) { } void NWM_UDS::DisconnectNetwork(Kernel::HLERequestContext& ctx) { + LOG_INFO(Service_NWM, "disconnecting from network"); IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); @@ -1277,6 +1309,7 @@ void NWM_UDS::ConnectToNetwork(Kernel::HLERequestContext& ctx, u16 command_id, std::vector passphrase) { network_info = {}; std::memcpy(&network_info, network_info_buffer.data(), network_info_buffer.size()); + conn_type = static_cast(connection_type); // Start the connection sequence StartConnectionSequence(network_info.host_mac_address); diff --git a/src/core/hle/service/nwm/nwm_uds.h b/src/core/hle/service/nwm/nwm_uds.h index cf59e7bcd..b0f2a2797 100644 --- a/src/core/hle/service/nwm/nwm_uds.h +++ b/src/core/hle/service/nwm/nwm_uds.h @@ -20,6 +20,7 @@ #include "common/swap.h" #include "core/hle/service/service.h" #include "network/network.h" +#include "core/hle/service/nwm/uds_common.h" namespace Core { class System; @@ -47,6 +48,8 @@ const u16 DefaultBeaconInterval = 100; /// The maximum number of nodes that can exist in an UDS session. constexpr u32 UDSMaxNodes = 16; +constexpr u16 NodeIDSpec = 0; + struct NodeInfo { u64_le friend_code_seed; std::array username; @@ -95,7 +98,7 @@ static_assert(sizeof(ConnectionStatus) == 0x30, "ConnectionStatus has incorrect struct NetworkInfo { std::array host_mac_address; u8 channel; - INSERT_PADDING_BYTES(1); + u8 unk1; u8 initialized; INSERT_PADDING_BYTES(3); std::array oui_value; @@ -477,7 +480,7 @@ private: void HandleSecureDataPacket(const Network::WifiPacket& packet); /* - * Start a connection sequence with an UDS server. The sequence starts by sending an 802.11 + * Start a connection sequence with a UDS server. The sequence starts by sending an 802.11 * authentication frame with SEQ1. */ void StartConnectionSequence(const MacAddress& server); @@ -526,6 +529,9 @@ private: // Node information about our own system. NodeInfo current_node; + // whether you are connecting as a client or a spec + ConnectionType conn_type; + struct BindNodeData { u32 bind_node_id; ///< Id of the bind node associated with this data. u8 channel; ///< Channel that this bind node was bound to. @@ -548,6 +554,7 @@ private: // Mapping of mac addresses to their respective node_ids. struct Node { bool connected; + bool spec; u16 node_id; private: diff --git a/src/core/hle/service/nwm/uds_common.h b/src/core/hle/service/nwm/uds_common.h new file mode 100644 index 000000000..359fd5c51 --- /dev/null +++ b/src/core/hle/service/nwm/uds_common.h @@ -0,0 +1,16 @@ +// Copyright 2025 Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::NWM { + +enum class ConnectionType : u8 { + Client = 0x1, + Spectator = 0x2, +}; + +}; // Service::NWM \ No newline at end of file diff --git a/src/core/hle/service/nwm/uds_data.cpp b/src/core/hle/service/nwm/uds_data.cpp index abf2b36f9..e2fd21d5c 100644 --- a/src/core/hle/service/nwm/uds_data.cpp +++ b/src/core/hle/service/nwm/uds_data.cpp @@ -286,9 +286,10 @@ SecureDataHeader ParseSecureDataHeader(std::span data) { return header; } -std::vector GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node_info) { +std::vector GenerateEAPoLStartFrame(u16 association_id, ConnectionType conn_type, const NodeInfo& node_info) { EAPoLStartPacket eapol_start{}; eapol_start.association_id = association_id; + eapol_start.conn_type = conn_type; eapol_start.node.friend_code_seed = node_info.friend_code_seed; std::copy(node_info.username.begin(), node_info.username.end(), @@ -328,12 +329,7 @@ NodeInfo DeserializeNodeInfoFromFrame(std::span frame) { std::memcpy(&eapol_start, frame.data() + sizeof(LLCHeader), sizeof(eapol_start)); NodeInfo node{}; - node.friend_code_seed = eapol_start.node.friend_code_seed; - - std::copy(eapol_start.node.username.begin(), eapol_start.node.username.end(), - node.username.begin()); - - return node; + return DeserializeNodeInfo(eapol_start.node); } NodeInfo DeserializeNodeInfo(const EAPoLNodeInfo& node) { @@ -380,4 +376,12 @@ EAPoLLogoffPacket ParseEAPoLLogoffFrame(std::span frame) { return eapol_logoff; } + +EAPoLStartPacket DeserializeEAPolStartPacket(std::span frame) { + EAPoLStartPacket eapol_start; + + std::memcpy(&eapol_start, frame.data() + sizeof(LLCHeader), sizeof(eapol_start)); + return eapol_start; +} + } // namespace Service::NWM diff --git a/src/core/hle/service/nwm/uds_data.h b/src/core/hle/service/nwm/uds_data.h index 291cca0f5..5dc62d33a 100644 --- a/src/core/hle/service/nwm/uds_data.h +++ b/src/core/hle/service/nwm/uds_data.h @@ -11,6 +11,7 @@ #include "common/swap.h" #include "core/hle/service/nwm/uds_beacon.h" #include "core/hle/service/service.h" +#include "core/hle/service/nwm/uds_common.h" namespace Service::NWM { @@ -90,9 +91,8 @@ constexpr u16 EAPoLStartMagic = 0x201; struct EAPoLStartPacket { u16_be magic = EAPoLStartMagic; u16_be association_id; - // This value is hardcoded to 1 in the NWM module. - u16_be unknown = 1; - INSERT_PADDING_BYTES(2); + enum_le conn_type; + INSERT_PADDING_BYTES(3); EAPoLNodeInfo node; }; @@ -132,7 +132,7 @@ SecureDataHeader ParseSecureDataHeader(std::span data); * communication. * @returns The generated frame body. */ -std::vector GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node_info); +std::vector GenerateEAPoLStartFrame(u16 association_id, ConnectionType conn_type, const NodeInfo& node_info); /* * Returns the EtherType of the specified 802.11 frame. @@ -151,6 +151,8 @@ u16 GetEAPoLFrameType(std::span frame); */ NodeInfo DeserializeNodeInfoFromFrame(std::span frame); +EAPoLStartPacket DeserializeEAPolStartPacket(std::span frame); + /* * Returns a NodeInfo constructed from the data in the specified EAPoLNodeInfo. */