Added NWM spectator mode (DLP now partially working), fixed debug assert, added applet utility cmd fallback

This commit is contained in:
U-DESKTOP-7SFAKTP\Ron 2025-09-13 03:02:29 -07:00 committed by PabloMK7
parent eb1197a65c
commit 404b95cf8c
7 changed files with 129 additions and 57 deletions

View file

@ -3451,6 +3451,7 @@ void Module::Interface::GetProgramInfoFromCia(Kernel::HLERequestContext& ctx) {
IPC::RequestBuilder rb = rp.MakeBuilder(8, 0);
rb.Push(ResultSuccess);
rb.PushRaw<TitleInfo>(title_info);
rb.Push<u32>(0x0); // make num words pushed match header so no assert occurrs
}
void Module::Interface::GetSystemMenuDataFromCia(Kernel::HLERequestContext& ctx) {

View file

@ -698,11 +698,20 @@ void Module::APTInterface::AppletUtility(Kernel::HLERequestContext& ctx) {
utility_command, input_size, output_size);
std::vector<u8> out(output_size);
if (utility_command == 0x6 && output_size > 0) {
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);

View file

@ -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<u16>(assoc_result), current_node);
eapol_start.data = GenerateEAPoLStartFrame(std::get<u16>(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,8 +218,11 @@ 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);
auto node = DeserializeNodeInfo(eapol_start.node);
if (eapol_start.conn_type == ConnectionType::Client) {
// Get an unused network node id
u16 node_id = GetNextAvailableNodeId();
node.network_node_id = node_id;
@ -233,8 +237,16 @@ void NWM_UDS::HandleEAPoLPacket(const Network::WifiPacket& packet) {
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<u32>(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);
}
// We're now connected, signal the application
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<u32>(conn_type));
}
// We're now connected, signal the application
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<u32>(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,6 +520,7 @@ void NWM_UDS::HandleDeauthenticationFrame(const Network::WifiPacket& packet) {
return;
}
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--;
@ -502,9 +528,8 @@ void NWM_UDS::HandleDeauthenticationFrame(const Network::WifiPacket& packet) {
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<u32>();
// scan input struct
u32 unk1 = rp.Pop<u32>();
u32 unk2 = rp.Pop<u32>();
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>();
u32 id = rp.Pop<u32>();
// From 3dbrew:
@ -765,7 +796,7 @@ void NWM_UDS::Bind(Kernel::HLERequestContext& ctx) {
u8 data_channel = rp.Pop<u8>();
u16 network_node_id = rp.Pop<u16>();
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<u8> passphrase) {
network_info = {};
std::memcpy(&network_info, network_info_buffer.data(), network_info_buffer.size());
conn_type = static_cast<ConnectionType>(connection_type);
// Start the connection sequence
StartConnectionSequence(network_info.host_mac_address);

View file

@ -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<u16_le, 10> username;
@ -95,7 +98,7 @@ static_assert(sizeof(ConnectionStatus) == 0x30, "ConnectionStatus has incorrect
struct NetworkInfo {
std::array<u8, 6> host_mac_address;
u8 channel;
INSERT_PADDING_BYTES(1);
u8 unk1;
u8 initialized;
INSERT_PADDING_BYTES(3);
std::array<u8, 3> 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:

View file

@ -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

View file

@ -286,9 +286,10 @@ SecureDataHeader ParseSecureDataHeader(std::span<const u8> data) {
return header;
}
std::vector<u8> GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node_info) {
std::vector<u8> 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<const u8> 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<const u8> frame) {
return eapol_logoff;
}
EAPoLStartPacket DeserializeEAPolStartPacket(std::span<const u8> frame) {
EAPoLStartPacket eapol_start;
std::memcpy(&eapol_start, frame.data() + sizeof(LLCHeader), sizeof(eapol_start));
return eapol_start;
}
} // namespace Service::NWM

View file

@ -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<ConnectionType> conn_type;
INSERT_PADDING_BYTES(3);
EAPoLNodeInfo node;
};
@ -132,7 +132,7 @@ SecureDataHeader ParseSecureDataHeader(std::span<const u8> data);
* communication.
* @returns The generated frame body.
*/
std::vector<u8> GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node_info);
std::vector<u8> 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<const u8> frame);
*/
NodeInfo DeserializeNodeInfoFromFrame(std::span<const u8> frame);
EAPoLStartPacket DeserializeEAPolStartPacket(std::span<const u8> frame);
/*
* Returns a NodeInfo constructed from the data in the specified EAPoLNodeInfo.
*/