From 0be78e9550885f2d81044540a36f3e19e9572d4e Mon Sep 17 00:00:00 2001 From: lannoene <77375172+lannoene@users.noreply.github.com> Date: Sat, 20 Sep 2025 10:59:54 -0700 Subject: [PATCH] Improve DLP and multiplayer compatability (#1375) * Added NWM spectator mode (DLP now partially working), fixed debug assert, added applet utility cmd fallback * Reverted AppletUtility command change * Fixed inconsistent mac address * Enabled DLP Child authorization * Added the DLP module to recommended online modules * Clean up * Changed the returned number of words on GetProgramInfoFromCia to 7 insteead of 8, futher reverted AppletUtility function to match its original form --- src/citra_qt/multiplayer/direct_connect.cpp | 5 +- src/citra_qt/multiplayer/host_room.cpp | 4 +- src/citra_qt/multiplayer/lobby.cpp | 3 +- src/common/hacks/hack_list.cpp | 3 + src/core/hle/service/am/am.cpp | 12 ++- src/core/hle/service/cfg/cfg.cpp | 4 + src/core/hle/service/cfg/cfg.h | 2 + src/core/hle/service/nwm/nwm_uds.cpp | 112 +++++++++++++------- src/core/hle/service/nwm/nwm_uds.h | 13 ++- src/core/hle/service/nwm/uds_common.h | 16 +++ src/core/hle/service/nwm/uds_data.cpp | 21 ++-- src/core/hle/service/nwm/uds_data.h | 13 ++- src/core/hle/service/service.cpp | 2 +- 13 files changed, 146 insertions(+), 64 deletions(-) create mode 100644 src/core/hle/service/nwm/uds_common.h diff --git a/src/citra_qt/multiplayer/direct_connect.cpp b/src/citra_qt/multiplayer/direct_connect.cpp index c77aac36f..f1e29b145 100644 --- a/src/citra_qt/multiplayer/direct_connect.cpp +++ b/src/citra_qt/multiplayer/direct_connect.cpp @@ -1,4 +1,4 @@ -// Copyright 2017 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -90,7 +90,8 @@ void DirectConnectWindow::Connect() { room_member->Join(ui->nickname->text().toStdString(), Service::CFG::GetConsoleIdHash(system), ui->ip->text().toStdString().c_str(), port, 0, - Network::NoPreferredMac, ui->password->text().toStdString().c_str()); + Service::CFG::GetConsoleMacAddress(system), + ui->password->text().toStdString().c_str()); } }); watcher->setFuture(f); diff --git a/src/citra_qt/multiplayer/host_room.cpp b/src/citra_qt/multiplayer/host_room.cpp index 5f29eed2d..53de0071c 100644 --- a/src/citra_qt/multiplayer/host_room.cpp +++ b/src/citra_qt/multiplayer/host_room.cpp @@ -193,8 +193,8 @@ void HostRoomWindow::Host() { } #endif member->Join(ui->username->text().toStdString(), Service::CFG::GetConsoleIdHash(system), - "127.0.0.1", static_cast(port), 0, Network::NoPreferredMac, password, - token); + "127.0.0.1", static_cast(port), 0, + Service::CFG::GetConsoleMacAddress(system), password, token); // Store settings UISettings::values.room_nickname = ui->username->text(); diff --git a/src/citra_qt/multiplayer/lobby.cpp b/src/citra_qt/multiplayer/lobby.cpp index 7a7f032c1..c18c8eefd 100644 --- a/src/citra_qt/multiplayer/lobby.cpp +++ b/src/citra_qt/multiplayer/lobby.cpp @@ -175,7 +175,8 @@ void Lobby::OnJoinRoom(const QModelIndex& source) { #endif if (auto room_member = Network::GetRoomMember().lock()) { room_member->Join(nickname, Service::CFG::GetConsoleIdHash(system), ip.c_str(), - static_cast(port), 0, Network::NoPreferredMac, password, token); + static_cast(port), 0, Service::CFG::GetConsoleMacAddress(system), + password, token); } }); watcher->setFuture(f); diff --git a/src/common/hacks/hack_list.cpp b/src/common/hacks/hack_list.cpp index c5702df5e..ce93ad2ef 100644 --- a/src/common/hacks/hack_list.cpp +++ b/src/common/hacks/hack_list.cpp @@ -67,6 +67,9 @@ HackManager hack_manager = { 0x0004013000002C02, // Normal 0x0004013000002C03, // Safe mode 0x0004013020002C03, // New 3DS safe mode + + // DLP + 0x0004013000002802, }, }}, diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 1b8c97bae..9b0d5a709 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -3255,9 +3255,15 @@ void Module::Interface::BeginImportProgramTemporarily(Kernel::HLERequestContext& // Create our CIAFile handle for the app to write to, and while the app writes Citra will store // contents out to sdmc/nand const FileSys::Path cia_path = {}; - auto file = std::make_shared( - am->system.Kernel(), std::make_unique(am->system, FS::MediaType::NAND), cia_path); + std::shared_ptr file; + { + auto cia_file = std::make_unique(am->system, FS::MediaType::NAND); + AuthorizeCIAFileDecryption(cia_file.get(), ctx); + + file = + std::make_shared(am->system.Kernel(), std::move(cia_file), cia_path); + } am->cia_installing = true; IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); @@ -3448,7 +3454,7 @@ void Module::Interface::GetProgramInfoFromCia(Kernel::HLERequestContext& ctx) { title_info.version = tmd.GetTitleVersion(); title_info.type = tmd.GetTitleType(); - IPC::RequestBuilder rb = rp.MakeBuilder(8, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(7, 0); rb.Push(ResultSuccess); rb.PushRaw(title_info); } diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 4d04af017..070612250 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -1264,6 +1264,10 @@ std::string GetConsoleIdHash(Core::System& system) { return fmt::format("{:02x}", fmt::join(hash.begin(), hash.end(), "")); } +std::array GetConsoleMacAddress(Core::System& system) { + return MacToArray(GetModule(system)->GetMacAddress()); +} + std::array MacToArray(const std::string& mac) { std::array ret; int last = -1; diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h index 7536680c0..8b02d6f8e 100644 --- a/src/core/hle/service/cfg/cfg.h +++ b/src/core/hle/service/cfg/cfg.h @@ -672,6 +672,8 @@ u64 MacToU64(const std::string& mac); std::string GenerateRandomMAC(); +std::array GetConsoleMacAddress(Core::System& system); + } // namespace Service::CFG SERVICE_CONSTRUCT(Service::CFG::Module) diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp index 3a08ffbee..1672feee3 100644 --- a/src/core/hle/service/nwm/nwm_uds.cpp +++ b/src/core/hle/service/nwm/nwm_uds.cpp @@ -106,15 +106,19 @@ void NWM_UDS::BroadcastNodeMap() { packet.channel = network_channel; packet.type = Network::WifiPacket::PacketType::NodeMap; packet.destination_address = Network::BroadcastMac; - std::size_t num_entries = std::count_if(node_map.begin(), node_map.end(), - [](const auto& node) { return node.second.connected; }); + 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(), + [&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 +189,8 @@ 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 +222,36 @@ 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 +299,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 @@ -328,9 +353,9 @@ void NWM_UDS::HandleSecureDataPacket(const Network::WifiPacket& packet) { 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_DEBUG(Service_NWM, "Ignored SecureDataPacket because connection status is {}", static_cast(connection_status.status)); return; } @@ -370,12 +395,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 +459,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 +483,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 +524,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 +617,19 @@ 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: @@ -1042,6 +1076,7 @@ void NWM_UDS::DestroyNetwork(Kernel::HLERequestContext& ctx) { } void NWM_UDS::DisconnectNetwork(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NWM, "disconnecting from network"); IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); @@ -1277,6 +1312,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..045d4ba96 100644 --- a/src/core/hle/service/nwm/nwm_uds.h +++ b/src/core/hle/service/nwm/nwm_uds.h @@ -1,4 +1,4 @@ -// Copyright 2014 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -18,6 +18,7 @@ #include #include "common/common_types.h" #include "common/swap.h" +#include "core/hle/service/nwm/uds_common.h" #include "core/hle/service/service.h" #include "network/network.h" @@ -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..fb2e118fa --- /dev/null +++ b/src/core/hle/service/nwm/uds_common.h @@ -0,0 +1,16 @@ +// Copyright Citra Emulator Project / 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, +}; + +}; // namespace Service::NWM diff --git a/src/core/hle/service/nwm/uds_data.cpp b/src/core/hle/service/nwm/uds_data.cpp index abf2b36f9..305aec843 100644 --- a/src/core/hle/service/nwm/uds_data.cpp +++ b/src/core/hle/service/nwm/uds_data.cpp @@ -1,4 +1,4 @@ -// Copyright 2017 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -286,9 +286,11 @@ 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(), @@ -327,13 +329,7 @@ NodeInfo DeserializeNodeInfoFromFrame(std::span frame) { // Skip the LLC header 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,11 @@ 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..faaf53448 100644 --- a/src/core/hle/service/nwm/uds_data.h +++ b/src/core/hle/service/nwm/uds_data.h @@ -1,4 +1,4 @@ -// Copyright 2017 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -10,6 +10,7 @@ #include "common/common_types.h" #include "common/swap.h" #include "core/hle/service/nwm/uds_beacon.h" +#include "core/hle/service/nwm/uds_common.h" #include "core/hle/service/service.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,8 @@ 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 +152,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. */ diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index b574b574a..148d60185 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -78,7 +78,7 @@ const std::array service_module_map{ false}, {"CECD", 0x00040130'00002602, CECD::InstallInterfaces, false}, {"CFG", 0x00040130'00001702, CFG::InstallInterfaces, false}, - {"DLP", 0x00040130'00002802, DLP::InstallInterfaces, false}, + {"DLP", 0x00040130'00002802, DLP::InstallInterfaces, true}, {"DSP", 0x00040130'00001A02, DSP::InstallInterfaces, false}, {"FRD", 0x00040130'00003202, FRD::InstallInterfaces, true}, {"GSP", 0x00040130'00001C02, GSP::InstallInterfaces, false},