From 2e6a289a0b4e53098d4ee4a9f6baf038d21981f8 Mon Sep 17 00:00:00 2001 From: Maufeat Date: Thu, 26 Jun 2025 18:55:34 +0000 Subject: [PATCH] Add Airplane Mode + Host Network Interface Details (#204) Adds Airplane Mode function to settings, host states, etc. Windows implemented only for now. Closes #203 Co-authored-by: crueter Co-authored-by: Aleksandr Popovich Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/204 Co-authored-by: Maufeat Co-committed-by: Maufeat --- CMakeModules/Findlibiw.cmake | 29 + .../features/settings/model/BooleanSetting.kt | 4 + .../settings/model/view/SettingsItem.kt | 11 + .../settings/ui/SettingsFragmentPresenter.kt | 3 +- src/android/app/src/main/jni/native.cpp | 1 + .../app/src/main/res/values/strings.xml | 3 + src/common/settings.h | 3 +- src/core/CMakeLists.txt | 23 + src/core/arm/nce/lru_cache.h | 4 +- src/core/arm/nce/patcher.cpp | 2 +- src/core/arm/nce/patcher.h | 2 +- src/core/frontend/applets/net_connect.cpp | 13 + src/core/frontend/applets/net_connect.h | 22 + .../am/frontend/applet_net_connect.cpp | 46 ++ .../service/am/frontend/applet_net_connect.h | 32 + src/core/hle/service/am/frontend/applets.cpp | 21 +- src/core/hle/service/am/frontend/applets.h | 8 +- .../service/am/service/display_controller.h | 2 +- src/core/hle/service/hid/hid_server.cpp | 3 + src/core/hle/service/hid/hid_server.h | 3 + src/core/hle/service/nifm/nifm.cpp | 655 ++++++++++++++---- src/core/hle/service/nifm/nifm.h | 16 +- .../service/ns/dynamic_rights_interface.cpp | 7 +- .../hle/service/ns/dynamic_rights_interface.h | 3 +- src/core/hle/service/sockets/bsd.cpp | 13 +- src/core/internal_network/emu_net_state.cpp | 101 +++ src/core/internal_network/emu_net_state.h | 37 + .../internal_network/network_interface.cpp | 84 +-- src/core/internal_network/network_interface.h | 14 + src/core/internal_network/wifi_scanner.cpp | 186 +++++ src/core/internal_network/wifi_scanner.h | 22 + src/yuzu/configuration/configure_network.cpp | 8 + src/yuzu/configuration/configure_network.ui | 9 +- src/yuzu/main.cpp | 6 +- 34 files changed, 1193 insertions(+), 203 deletions(-) create mode 100644 CMakeModules/Findlibiw.cmake create mode 100644 src/core/frontend/applets/net_connect.cpp create mode 100644 src/core/frontend/applets/net_connect.h create mode 100644 src/core/hle/service/am/frontend/applet_net_connect.cpp create mode 100644 src/core/hle/service/am/frontend/applet_net_connect.h create mode 100644 src/core/internal_network/emu_net_state.cpp create mode 100644 src/core/internal_network/emu_net_state.h create mode 100644 src/core/internal_network/wifi_scanner.cpp create mode 100644 src/core/internal_network/wifi_scanner.h diff --git a/CMakeModules/Findlibiw.cmake b/CMakeModules/Findlibiw.cmake new file mode 100644 index 0000000000..1d13d7705a --- /dev/null +++ b/CMakeModules/Findlibiw.cmake @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + +find_package(PkgConfig QUIET) +pkg_search_module(IW QUIET IMPORTED_TARGET iw) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Libiw + REQUIRED_VARS IW_LIBRARIES IW_INCLUDE_DIRS + VERSION_VAR IW_VERSION + FAIL_MESSAGE "libiw (Wireless Tools library) not found. Please install libiw-dev (Debian/Ubuntu), wireless-tools-devel (Fedora), wireless_tools (Arch), or net-wireless/iw (Gentoo)." +) + +if (Libiw_FOUND AND TARGET PkgConfig::IW AND NOT TARGET iw::iw) + add_library(iw::iw ALIAS PkgConfig::IW) +endif() + +if(Libiw_FOUND) + mark_as_advanced( + IW_INCLUDE_DIRS + IW_LIBRARIES + IW_LIBRARY_DIRS + IW_LINK_LIBRARIES # Often set by pkg-config + IW_LDFLAGS + IW_CFLAGS + IW_CFLAGS_OTHER + IW_VERSION + ) +endif() diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index 5048b18db9..c16de8f7a9 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -31,6 +34,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { HAPTIC_FEEDBACK("haptic_feedback"), SHOW_INPUT_OVERLAY("show_input_overlay"), TOUCHSCREEN("touchscreen"), + AIRPLANE_MODE("airplane_mode"), SHOW_SOC_OVERLAY("show_soc_overlay"), SHOW_DEVICE_MODEL("show_device_model"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index b4e49221ce..bdfafb82d8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -682,6 +685,14 @@ abstract class SettingsItem( valuesId = R.array.appletValues ) ) + + put( + SwitchSetting( + BooleanSetting.AIRPLANE_MODE, + titleId = R.string.airplane_mode, + descriptionId = R.string.airplane_mode_description + ) + ) } } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 7fdf580f3c..7ca75ed9e2 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.features.settings.ui @@ -457,6 +457,7 @@ class SettingsFragmentPresenter( private fun addAppletSettings(sl: ArrayList) { sl.apply { add(IntSetting.SWKBD_APPLET.key) + add(BooleanSetting.AIRPLANE_MODE.key) } } private fun addInputPlayer(sl: ArrayList, playerIndex: Int) { diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 309f298789..cc00cfe024 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -269,6 +269,7 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string nullptr, // Profile Selector std::move(android_keyboard), // Software Keyboard nullptr, // Web Browser + nullptr, // Net Connect }); // Initialize filesystem. diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index a3bb92d27f..10094a5b5f 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -966,6 +966,9 @@ Software Keyboard + Airplane Mode + Passes Airplane Mode to the Switch OS + Licenses FidelityFX-FSR diff --git a/src/common/settings.h b/src/common/settings.h index aa7d93c156..3a27546537 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -147,7 +147,7 @@ struct Values { Category::LibraryApplet}; Setting error_applet_mode{linkage, AppletMode::LLE, "error_applet_mode", Category::LibraryApplet}; - Setting net_connect_applet_mode{linkage, AppletMode::HLE, "net_connect_applet_mode", + Setting net_connect_applet_mode{linkage, AppletMode::LLE, "net_connect_applet_mode", Category::LibraryApplet}; Setting player_select_applet_mode{ linkage, AppletMode::LLE, "player_select_applet_mode", Category::LibraryApplet}; @@ -675,6 +675,7 @@ struct Values { // Network Setting network_interface{linkage, std::string(), "network_interface", Category::Network}; + SwitchableSetting airplane_mode{linkage, false, "airplane_mode", Category::Network}; // WebService Setting web_api_url{linkage, "api.ynet-fun.xyz", "web_api_url", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f66467d410..187e6a2b4a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -185,6 +185,8 @@ add_library(core STATIC frontend/applets/general.h frontend/applets/mii_edit.cpp frontend/applets/mii_edit.h + frontend/applets/net_connect.cpp + frontend/applets/net_connect.h frontend/applets/profile_select.cpp frontend/applets/profile_select.h frontend/applets/software_keyboard.cpp @@ -422,6 +424,8 @@ add_library(core STATIC hle/service/am/frontend/applet_mii_edit.cpp hle/service/am/frontend/applet_mii_edit.h hle/service/am/frontend/applet_mii_edit_types.h + hle/service/am/frontend/applet_net_connect.cpp + hle/service/am/frontend/applet_net_connect.h hle/service/am/frontend/applet_profile_select.cpp hle/service/am/frontend/applet_profile_select.h hle/service/am/frontend/applet_software_keyboard.cpp @@ -1099,6 +1103,8 @@ add_library(core STATIC hle/service/vi/vi_types.h hle/service/vi/vsync_manager.cpp hle/service/vi/vsync_manager.h + internal_network/emu_net_state.cpp + internal_network/emu_net_state.h internal_network/network.cpp internal_network/network.h internal_network/network_interface.cpp @@ -1106,6 +1112,8 @@ add_library(core STATIC internal_network/socket_proxy.cpp internal_network/socket_proxy.h internal_network/sockets.h + internal_network/wifi_scanner.cpp + internal_network/wifi_scanner.h loader/deconstructed_rom_directory.cpp loader/deconstructed_rom_directory.h loader/kip.cpp @@ -1142,6 +1150,20 @@ add_library(core STATIC tools/renderdoc.h ) +if (UNIX AND NOT APPLE AND NOT ANDROID) + # find_package(libiw REQUIRED) + target_link_libraries(core PRIVATE iw) +endif() + +if (WIN32) + target_compile_definitions(core PRIVATE _WIN32_WINNT=0x0A00 WINVER=0x0A00) + + if(TARGET iw) + target_link_libraries(core PRIVATE iw) + message(STATUS "Linking 'core' with iw on Linux.") + endif() +endif() + if (MSVC) target_compile_options(core PRIVATE /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data @@ -1231,6 +1253,7 @@ else() hle/service/ssl/ssl_backend_none.cpp) endif() + if (YUZU_USE_PRECOMPILED_HEADERS) target_precompile_headers(core PRIVATE precompiled_headers.h) endif() diff --git a/src/core/arm/nce/lru_cache.h b/src/core/arm/nce/lru_cache.h index b2d6b2f7e0..1bc00c8f14 100644 --- a/src/core/arm/nce/lru_cache.h +++ b/src/core/arm/nce/lru_cache.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once @@ -184,4 +184,4 @@ private: list_type cache_list_; map_type cache_map_; mutable Statistics stats_; -}; \ No newline at end of file +}; diff --git a/src/core/arm/nce/patcher.cpp b/src/core/arm/nce/patcher.cpp index 6f476caa81..b8387ce7cb 100644 --- a/src/core/arm/nce/patcher.cpp +++ b/src/core/arm/nce/patcher.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include "common/arm64/native_clock.h" diff --git a/src/core/arm/nce/patcher.h b/src/core/arm/nce/patcher.h index 11c255ef82..53a923138c 100644 --- a/src/core/arm/nce/patcher.h +++ b/src/core/arm/nce/patcher.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/core/frontend/applets/net_connect.cpp b/src/core/frontend/applets/net_connect.cpp new file mode 100644 index 0000000000..eba4a5cb2f --- /dev/null +++ b/src/core/frontend/applets/net_connect.cpp @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "common/logging/log.h" +#include "core/frontend/applets/net_connect.h" + +namespace Core::Frontend { + +NetConnectApplet::~NetConnectApplet() = default; + +void DefaultNetConnectApplet::Close() const {} + +} // namespace Core::Frontend diff --git a/src/core/frontend/applets/net_connect.h b/src/core/frontend/applets/net_connect.h new file mode 100644 index 0000000000..cca230213a --- /dev/null +++ b/src/core/frontend/applets/net_connect.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "core/frontend/applets/applet.h" + +namespace Core::Frontend { + +class NetConnectApplet : public Applet { +public: + virtual ~NetConnectApplet(); +}; + +class DefaultNetConnectApplet final : public NetConnectApplet { +public: + void Close() const override; +}; + +} // namespace Core::Frontend diff --git a/src/core/hle/service/am/frontend/applet_net_connect.cpp b/src/core/hle/service/am/frontend/applet_net_connect.cpp new file mode 100644 index 0000000000..91ba664b7a --- /dev/null +++ b/src/core/hle/service/am/frontend/applet_net_connect.cpp @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "common/assert.h" +#include "common/hex_util.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/frontend/applets/net_connect.h" +#include "core/hle/result.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applet_data_broker.h" +#include "core/hle/service/am/frontend/applet_net_connect.h" +#include "core/hle/service/am/service/storage.h" +#include "core/reporter.h" + +namespace Service::AM::Frontend { + +NetConnect::NetConnect(Core::System& system_, std::shared_ptr applet_, LibraryAppletMode applet_mode_, const Core::Frontend::NetConnectApplet& frontend_) + : FrontendApplet{system_, applet_, applet_mode_}, frontend{frontend_} {} + +NetConnect::~NetConnect() = default; + +void NetConnect::Initialize() { + FrontendApplet::Initialize(); + complete = false; +} + +Result NetConnect::GetStatus() const { + return ResultSuccess; +} + +void NetConnect::ExecuteInteractive() { + ASSERT_MSG(false, "Unexpected interactive applet data."); +} + +void NetConnect::Execute() { + if (complete) + return; +} + +Result NetConnect::RequestExit() { + frontend.Close(); + R_SUCCEED(); +} + +} // namespace Service::AM::Frontend diff --git a/src/core/hle/service/am/frontend/applet_net_connect.h b/src/core/hle/service/am/frontend/applet_net_connect.h new file mode 100644 index 0000000000..d5d503ed41 --- /dev/null +++ b/src/core/hle/service/am/frontend/applet_net_connect.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "core/hle/service/am/frontend/applets.h" + +namespace Core { +class System; +} + +namespace Service::AM::Frontend { + +class NetConnect final : public FrontendApplet { +public: + explicit NetConnect(Core::System& system_, std::shared_ptr applet_, + LibraryAppletMode applet_mode_, + const Core::Frontend::NetConnectApplet& frontend_); + ~NetConnect() override; + + void Initialize() override; + Result GetStatus() const override; + void ExecuteInteractive() override; + void Execute() override; + Result RequestExit() override; + +private: + const Core::Frontend::NetConnectApplet& frontend; + bool complete = false; +}; + +} // namespace Service::AM::Frontend diff --git a/src/core/hle/service/am/frontend/applets.cpp b/src/core/hle/service/am/frontend/applets.cpp index cdd4318576..bb69869ade 100644 --- a/src/core/hle/service/am/frontend/applets.cpp +++ b/src/core/hle/service/am/frontend/applets.cpp @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later #include @@ -10,6 +10,7 @@ #include "core/frontend/applets/error.h" #include "core/frontend/applets/general.h" #include "core/frontend/applets/mii_edit.h" +#include "core/frontend/applets/net_connect.h" #include "core/frontend/applets/profile_select.h" #include "core/frontend/applets/software_keyboard.h" #include "core/frontend/applets/web_browser.h" @@ -22,6 +23,7 @@ #include "core/hle/service/am/frontend/applet_error.h" #include "core/hle/service/am/frontend/applet_general.h" #include "core/hle/service/am/frontend/applet_mii_edit.h" +#include "core/hle/service/am/frontend/applet_net_connect.h" #include "core/hle/service/am/frontend/applet_profile_select.h" #include "core/hle/service/am/frontend/applet_software_keyboard.h" #include "core/hle/service/am/frontend/applet_web_browser.h" @@ -83,12 +85,13 @@ FrontendAppletSet::FrontendAppletSet(CabinetApplet cabinet_applet, MiiEdit mii_edit_, ParentalControlsApplet parental_controls_applet, PhotoViewer photo_viewer_, ProfileSelect profile_select_, - SoftwareKeyboard software_keyboard_, WebBrowser web_browser_) + SoftwareKeyboard software_keyboard_, WebBrowser web_browser_, NetConnect net_connect_) : cabinet{std::move(cabinet_applet)}, controller{std::move(controller_applet)}, error{std::move(error_applet)}, mii_edit{std::move(mii_edit_)}, parental_controls{std::move(parental_controls_applet)}, photo_viewer{std::move(photo_viewer_)}, profile_select{std::move(profile_select_)}, - software_keyboard{std::move(software_keyboard_)}, web_browser{std::move(web_browser_)} {} + software_keyboard{std::move(software_keyboard_)}, web_browser{std::move(web_browser_)}, + net_connect{std::move(net_connect_)} {} FrontendAppletSet::~FrontendAppletSet() = default; @@ -148,6 +151,10 @@ void FrontendAppletHolder::SetFrontendAppletSet(FrontendAppletSet set) { if (set.web_browser != nullptr) { frontend.web_browser = std::move(set.web_browser); } + + if (set.net_connect != nullptr) { + frontend.net_connect = std::move(set.net_connect); + } } void FrontendAppletHolder::SetCabinetMode(NFP::CabinetMode mode) { @@ -197,6 +204,10 @@ void FrontendAppletHolder::SetDefaultAppletsIfMissing() { if (frontend.web_browser == nullptr) { frontend.web_browser = std::make_unique(); } + + if (frontend.net_connect == nullptr) { + frontend.net_connect = std::make_unique(); + } } void FrontendAppletHolder::ClearAll() { @@ -230,6 +241,8 @@ std::shared_ptr FrontendAppletHolder::GetApplet(std::shared_ptr< return std::make_shared(system, applet, mode, *frontend.web_browser); case AppletId::PhotoViewer: return std::make_shared(system, applet, mode, *frontend.photo_viewer); + case AppletId::NetConnect: + return std::make_shared(system, applet, mode, *frontend.net_connect); default: UNIMPLEMENTED_MSG( "No backend implementation exists for applet_id={:02X}! Falling back to stub applet.", diff --git a/src/core/hle/service/am/frontend/applets.h b/src/core/hle/service/am/frontend/applets.h index 1e1fd28b8b..8eb08ea77b 100644 --- a/src/core/hle/service/am/frontend/applets.h +++ b/src/core/hle/service/am/frontend/applets.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -8,6 +11,7 @@ #include "common/swap.h" #include "core/hle/service/am/applet.h" +#include "core/frontend/applets/net_connect.h" union Result; @@ -90,13 +94,14 @@ struct FrontendAppletSet { using ProfileSelect = std::unique_ptr; using SoftwareKeyboard = std::unique_ptr; using WebBrowser = std::unique_ptr; + using NetConnect = std::unique_ptr; FrontendAppletSet(); FrontendAppletSet(CabinetApplet cabinet_applet, ControllerApplet controller_applet, ErrorApplet error_applet, MiiEdit mii_edit_, ParentalControlsApplet parental_controls_applet, PhotoViewer photo_viewer_, ProfileSelect profile_select_, SoftwareKeyboard software_keyboard_, - WebBrowser web_browser_); + WebBrowser web_browser_, NetConnect net_connect_); ~FrontendAppletSet(); FrontendAppletSet(const FrontendAppletSet&) = delete; @@ -114,6 +119,7 @@ struct FrontendAppletSet { ProfileSelect profile_select; SoftwareKeyboard software_keyboard; WebBrowser web_browser; + NetConnect net_connect; }; class FrontendAppletHolder { diff --git a/src/core/hle/service/am/service/display_controller.h b/src/core/hle/service/am/service/display_controller.h index 6e0cfff2db..171feace6e 100644 --- a/src/core/hle/service/am/service/display_controller.h +++ b/src/core/hle/service/am/service/display_controller.h @@ -20,7 +20,7 @@ public: private: Result GetLastForegroundCaptureImageEx(Out out_was_written, - OutBuffer out_image_data); + OutBuffer out_image_data); Result GetCallerAppletCaptureImageEx(Out out_was_written, OutBuffer out_image_data); Result TakeScreenShotOfOwnLayer(bool unknown0, s32 fbshare_layer_index); diff --git a/src/core/hle/service/hid/hid_server.cpp b/src/core/hle/service/hid/hid_server.cpp index 119fbd0af5..ebda7fc3f2 100644 --- a/src/core/hle/service/hid/hid_server.cpp +++ b/src/core/hle/service/hid/hid_server.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/hid/hid_server.h b/src/core/hle/service/hid/hid_server.h index df6adcf220..2a2d722937 100644 --- a/src/core/hle/service/hid/hid_server.h +++ b/src/core/hle/service/hid/hid_server.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index bb6e719cb0..d04e2a2954 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -1,17 +1,40 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include #include "core/core.h" #include "core/hle/kernel/k_event.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/kernel_helpers.h" #include "core/hle/service/nifm/nifm.h" #include "core/hle/service/server_manager.h" +#include "core/internal_network/emu_net_state.h" +#include "core/internal_network/network.h" +#include "core/internal_network/network_interface.h" +#include "core/internal_network/wifi_scanner.h" #include "network/network.h" +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#undef CreateEvent +#pragma push_macro("interface") +#undef interface +#include +#pragma pop_macro("interface") +#pragma comment(lib, "wlanapi.lib") +#endif + namespace { // Avoids name conflict with Windows' CreateEvent macro. @@ -22,11 +45,29 @@ namespace { } // Anonymous namespace -#include "core/internal_network/network.h" -#include "core/internal_network/network_interface.h" - namespace Service::NIFM { +static u128 MakeUuidFromName(std::string_view name) { + constexpr u64 kOff = 0xcbf29ce484222325ULL; + constexpr u64 kPrime = 0x100000001b3ULL; + + u64 h1 = kOff; + u64 h2 = kOff ^ 0x9e3779b97f4a7c15ULL; + + for (unsigned char c : name) { + h1 ^= c; + h1 *= kPrime; + + h2 ^= static_cast(c ^ 0x5A); + h2 *= kPrime; + } + if (h1 == 0 && h2 == 0) + h1 = 1; + + return {h1, h2}; +} + + // This is nn::nifm::RequestState enum class RequestState : u32 { NotSubmitted = 1, @@ -75,6 +116,20 @@ struct DnsSetting { }; static_assert(sizeof(DnsSetting) == 0x9, "DnsSetting has incorrect size."); +// This is nn::nifm::detail::sf::AccessPointData (for >= 19.0.0) +struct AccessPointDataV3 { + u8 ssid_len; // Length of SSID + char ssid[0x20]; // SSID Name (not null-terminated) + u8 unk_pad[11]; // Unk + u8 strength; // 0-3 = Signal Strengh Bar + u8 pad2[3]; // Unk 3 bytes + u8 visible; // 0 = Hidden, 1 = Shows up in UI + u8 has_password; // 0 = Open, 1 = Password Protected + u8 uk1; // Unk + u8 is_available; // 0 = Gray, 1 = Connectable +}; +static_assert(sizeof(AccessPointDataV3) == 52, "AccessPointData18 must be 64 bytes"); + // This is nn::nifm::AuthenticationSetting struct AuthenticationSetting { bool is_enabled{}; @@ -108,7 +163,7 @@ struct SfWirelessSettingData { std::array ssid{}; u8 unknown_1{}; u8 unknown_2{}; - u8 unknown_3{}; + u8 is_secured{}; std::array passphrase{}; }; static_assert(sizeof(SfWirelessSettingData) == 0x65, "SfWirelessSettingData has incorrect size."); @@ -132,10 +187,10 @@ struct SfNetworkProfileData { IpSettingData ip_setting_data{}; u128 uuid{}; std::array network_name{}; - u8 unknown_1{}; - u8 unknown_2{}; - u8 unknown_3{}; - u8 unknown_4{}; + u8 profile_type; + u8 interface_type; + u8 is_auto_connect; + u8 is_large_capacity; SfWirelessSettingData wireless_setting_data{}; INSERT_PADDING_BYTES(1); }; @@ -155,26 +210,131 @@ struct NifmNetworkProfileData { }; static_assert(sizeof(NifmNetworkProfileData) == 0x18E, "NifmNetworkProfileData has incorrect size."); -#pragma pack(pop) + +struct PendingProfile { + std::array ssid{}; + u8 ssid_len{}; + std::array passphrase{}; +}; + constexpr Result ResultPendingConnection{ErrorModule::NIFM, 111}; +constexpr Result ResultInvalidInput{ErrorModule::NIFM, 112}; constexpr Result ResultNetworkCommunicationDisabled{ErrorModule::NIFM, 1111}; +static std::mutex g_scan_mtx; +static std::vector g_last_scan_results; +static std::mutex g_profile_mtx; +static std::optional g_pending_profile; + class IScanRequest final : public ServiceFramework { public: - explicit IScanRequest(Core::System& system_) : ServiceFramework{system_, "IScanRequest"} { - // clang-format off - static const FunctionInfo functions[] = { - {0, nullptr, "Submit"}, - {1, nullptr, "IsProcessing"}, - {2, nullptr, "GetResult"}, - {3, nullptr, "GetSystemEventReadableHandle"}, - {4, nullptr, "SetChannels"}, - }; - // clang-format on + explicit IScanRequest(Core::System& system_) + : ServiceFramework{system_, "IScanRequest"}, svc_ctx{system_, "IScanRequest"} { + static const FunctionInfo functions[] = { + {0, &IScanRequest::Submit, "Submit"}, + {1, &IScanRequest::IsProcessing, "IsProcessing"}, + {2, &IScanRequest::GetResult, "GetResult"}, + {3, &IScanRequest::GetSystemEventReadableHandle, "GetSystemEventReadableHandle"}, + {4, &IScanRequest::SetChannels, "SetChannels"}, + }; RegisterHandlers(functions); + + evt_scan_complete = CreateKEvent(svc_ctx, "IScanRequest:Complete"); + evt_processing = CreateKEvent(svc_ctx, "IScanRequest:Processing"); } + + ~IScanRequest() override { + state.store(State::Idle); + svc_ctx.CloseEvent(evt_scan_complete); + svc_ctx.CloseEvent(evt_processing); + if (worker.joinable()) + worker.join(); + } + +private: + std::vector scan_results; + + void Submit(HLERequestContext& ctx) { + + if (state.load() == State::Finished) { + if (worker.joinable()) + worker.join(); + + state.store(State::Idle); + worker_result.store(ResultPendingConnection); + } + + if (state.load() != State::Idle) { + IPC::ResponseBuilder{ctx, 2}.Push(ResultSuccess); + return; + } + + state.store(State::Processing); + evt_processing->Signal(); + + worker = std::thread(&IScanRequest::WorkerThread, this); + IPC::ResponseBuilder{ctx, 2}.Push(ResultSuccess); + } + + void IsProcessing(HLERequestContext& ctx) + { + const bool processing = state.load() == State::Processing; + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(processing); + } + + void GetResult(HLERequestContext& ctx) { + const Result rc = worker_result.load(); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(rc); + } + + void GetSystemEventReadableHandle(HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 2}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(evt_scan_complete->GetReadableEvent(), + evt_processing->GetReadableEvent()); + } + + void SetChannels(HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder{ctx, 2}.Push(ResultSuccess); + } + + enum class State { Idle, Processing, Finished }; + + void WorkerThread() { + using namespace std::chrono_literals; + + scan_results = Network::ScanWifiNetworks(3s); + + { + std::scoped_lock lk{g_scan_mtx}; + g_last_scan_results = scan_results; + } + + // ❸ choose result code + const bool ok = !scan_results.empty(); + Finish(ok ? ResultSuccess : ResultPendingConnection); + } + + + void Finish(Result rc) { + worker_result.store(rc); + state.store(State::Finished); + evt_scan_complete->Signal(); + } + + KernelHelpers::ServiceContext svc_ctx; + + Kernel::KEvent* evt_scan_complete{}; + Kernel::KEvent* evt_processing{}; + std::thread worker; + std::atomic state{State::Idle}; + std::atomic worker_result{ResultPendingConnection}; }; class IRequest final : public ServiceFramework { @@ -190,7 +350,7 @@ public: {5, nullptr, "SetRequirement"}, {6, &IRequest::SetRequirementPreset, "SetRequirementPreset"}, {8, nullptr, "SetPriority"}, - {9, nullptr, "SetNetworkProfileId"}, + {9, &IRequest::SetNetworkProfileId, "SetNetworkProfileId"}, {10, nullptr, "SetRejectable"}, {11, &IRequest::SetConnectionConfirmationOption, "SetConnectionConfirmationOption"}, {12, nullptr, "SetPersistent"}, @@ -222,7 +382,7 @@ public: private: void Submit(HLERequestContext& ctx) { - LOG_DEBUG(Service_NIFM, "(STUBBED) called"); + LOG_WARNING(Service_NIFM, "(STUBBED) called"); if (state == RequestState::NotSubmitted) { UpdateState(RequestState::OnHold); @@ -233,7 +393,7 @@ private: } void GetRequestState(HLERequestContext& ctx) { - LOG_DEBUG(Service_NIFM, "(STUBBED) called"); + LOG_WARNING(Service_NIFM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); @@ -250,8 +410,22 @@ private: rb.Push(ResultSuccess); } + void SetNetworkProfileId(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto ssid_length = rp.Pop(); + if (ssid_length > 0x20) { + LOG_ERROR(Service_NIFM, "Invalid SSID length: {}", ssid_length); + IPC::ResponseBuilder{ctx, 2}.Push(ResultInvalidInput); + return; + } + LOG_WARNING(Service_NIFM, "(STUBBED) called, ssid_length={}", ssid_length); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(1); + } + void GetResult(HLERequestContext& ctx) { - LOG_DEBUG(Service_NIFM, "(STUBBED) called"); + LOG_WARNING(Service_NIFM, "(STUBBED) called"); const auto result = [this] { const auto has_connection = Network::GetHostIPv4Address().has_value(); @@ -304,7 +478,7 @@ private: ctx.WriteBuffer(out_buffer); - IPC::ResponseBuilder rb{ctx, 5}; + IPC::ResponseBuilder rb{ctx, 6}; rb.Push(ResultSuccess); rb.Push(0); rb.Push(0); @@ -312,6 +486,7 @@ private: } void UpdateState(RequestState new_state) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); state = new_state; event1->Signal(); } @@ -322,6 +497,10 @@ private: Kernel::KEvent* event1; Kernel::KEvent* event2; + + std::thread connect_worker; + std::atomic connect_done{false}; + std::atomic connect_result{ResultPendingConnection}; }; class INetworkProfile final : public ServiceFramework { @@ -364,15 +543,134 @@ void IGeneralService::CreateRequest(HLERequestContext& ctx) { } void IGeneralService::GetCurrentNetworkProfile(HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); + + Network::RefreshFromHost(); + const auto& st = Network::EmuNetState::Get(); + + SfNetworkProfileData profile{}; + + if (st.connected) { + + profile.ip_setting_data.ip_address_setting.is_automatic = true; + profile.ip_setting_data.ip_address_setting.ip_address = st.ip; + profile.ip_setting_data.ip_address_setting.subnet_mask = st.mask; + profile.ip_setting_data.ip_address_setting.default_gateway = st.gw; + + profile.ip_setting_data.dns_setting.is_automatic = true; + profile.ip_setting_data.dns_setting.primary_dns = {1, 1, 1, 1}; + profile.ip_setting_data.dns_setting.secondary_dns = {8, 8, 8, 8}; + + profile.uuid = MakeUuidFromName(st.ssid); + profile.profile_type = static_cast(NetworkProfileType::User); + profile.interface_type = static_cast(st.via_wifi ? NetworkInterfaceType::WiFi_Ieee80211 + : NetworkInterfaceType::Ethernet); + + std::strncpy(profile.network_name.data(), st.ssid, sizeof(profile.network_name) - 1); + + if (st.via_wifi) { + profile.wireless_setting_data.ssid_length = static_cast(std::strlen(st.ssid)); + std::memcpy(profile.wireless_setting_data.ssid.data(), st.ssid, + profile.wireless_setting_data.ssid_length); + } + } + + ctx.WriteBuffer(profile); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void IGeneralService::EnumerateNetworkInterfaces(HLERequestContext& ctx) { + + using Network::HostAdapterKind; + + const auto adapters = Network::GetAvailableNetworkInterfaces(); + constexpr size_t kEntry = 0x3F0; + + std::vector blob(adapters.size() * kEntry, 0); + + for (size_t i = 0; i < adapters.size(); ++i) { + const auto& host = adapters[i]; + u8* const base = blob.data() + i * kEntry; + + *reinterpret_cast(base + 0x0) = host.kind == HostAdapterKind::Wifi ? 1u : 2u; + *reinterpret_cast(base + 0x4) = 1u; + + *reinterpret_cast(base + 0x18) = host.ip_address; + *reinterpret_cast(base + 0x1C) = host.subnet_mask; + *reinterpret_cast(base + 0x20) = host.gateway; + + std::string name_utf8 = host.name; + name_utf8.resize(0x110, '\0'); + std::memcpy(base + 0x2E0, name_utf8.data(), 0x110); + } + + const size_t guest_bytes = ctx.GetWriteBufferSize(); + if (guest_bytes && !blob.empty()) + ctx.WriteBuffer(blob.data(), std::min(guest_bytes, blob.size())); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(static_cast(adapters.size())); +} + +void IGeneralService::EnumerateNetworkProfiles(HLERequestContext& ctx) { + const auto adapter = Network::GetSelectedNetworkInterface(); + + if (!adapter) { + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(0); + return; + } + + const u32 count = static_cast(1); + + std::vector uuids; + uuids.reserve(count); + + uuids.push_back(MakeUuidFromName(adapter.value().name)); + + const size_t guest_sz = ctx.GetWriteBufferSize(); + if (guest_sz && uuids.size()) { + const size_t to_copy = std::min(guest_sz, uuids.size() * sizeof(u128)); + ctx.WriteBuffer(uuids.data(), to_copy); + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(count); +} + +void IGeneralService::GetNetworkProfile(HLERequestContext& ctx) { + LOG_DEBUG(Service_NIFM, "GetNetworkProfile called"); + + IPC::RequestParser rp{ctx}; + + ASSERT_MSG(ctx.GetWriteBufferSize() == sizeof(SfNetworkProfileData), + "Caller expects SfNetworkProfileData (0x17C bytes)"); const auto net_iface = Network::GetSelectedNetworkInterface(); - SfNetworkProfileData network_profile_data = [&net_iface] { + SfNetworkProfileData profile = [&] { if (!net_iface) { return SfNetworkProfileData{}; } + const auto& net_state = Network::EmuNetState::Get(); + + std::array net_name{}; + const size_t ssid_len = std::min(std::strlen(net_state.ssid), net_name.size()); + std::memcpy(net_name.data(), net_state.ssid, ssid_len); + + SfWirelessSettingData wifi{}; + wifi.ssid_length = static_cast(std::min(std::strlen(net_state.ssid), net_name.size())); + wifi.is_secured = !net_state.secure; //somehow reversed + wifi.passphrase = {"password"}; + std::memcpy(wifi.ssid.data(), net_state.ssid, wifi.ssid_length); + + LOG_INFO(Service_NIFM, "ssid={} lenght={}", wifi.ssid, wifi.ssid_length); + return SfNetworkProfileData{ .ip_setting_data{ .ip_address_setting{ @@ -388,57 +686,51 @@ void IGeneralService::GetCurrentNetworkProfile(HLERequestContext& ctx) { }, .proxy_setting{ .is_enabled{false}, - .port{}, - .proxy_server{}, - .authentication{ - .is_enabled{}, - .user{}, - .password{}, - }, }, .mtu{1500}, }, - .uuid{0xdeadbeef, 0xdeadbeef}, - .network_name{"yuzu Network"}, - .wireless_setting_data{ - .ssid_length{12}, - .ssid{"yuzu Network"}, - .passphrase{"yuzupassword"}, - }, + .uuid{MakeUuidFromName(net_state.ssid)}, + .network_name{net_name}, + .profile_type = static_cast(NetworkProfileType::User), + .interface_type = + static_cast(net_iface->kind == + Network::HostAdapterKind::Wifi ? NetworkInterfaceType::WiFi_Ieee80211 : NetworkInterfaceType::Ethernet), + .wireless_setting_data{wifi} }; }(); - // When we're connected to a room, spoof the hosts IP address - if (auto room_member = Network::GetRoomMember().lock()) { - if (room_member->IsConnected()) { - network_profile_data.ip_setting_data.ip_address_setting.ip_address = - room_member->GetFakeIpAddress(); - } + ctx.WriteBuffer(profile); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(system); +} + +void IGeneralService::SetNetworkProfile(HLERequestContext& ctx) { + LOG_DEBUG(Service_NIFM, "SetNetworkProfile called"); + + if (!ctx.CanReadBuffer(0) || ctx.GetReadBufferSize() < sizeof(NifmNetworkProfileData)) { + IPC::ResponseBuilder{ctx, 2}.Push(ResultInvalidInput); + return; } - ctx.WriteBuffer(network_profile_data); + const auto data = ctx.ReadBufferCopy(0); + const auto* np = reinterpret_cast(data.data()); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + PendingProfile prof{}; + prof.ssid_len = np->wireless_setting_data.ssid_length; + std::memcpy(prof.ssid.data(), np->wireless_setting_data.ssid.data(), prof.ssid_len); + std::memcpy(prof.passphrase.data(), np->wireless_setting_data.passphrase.data(), + sizeof(prof.passphrase)); + + { + std::scoped_lock lk{g_profile_mtx}; + g_pending_profile = prof; + } + + IPC::ResponseBuilder{ctx, 2}.Push(ResultSuccess); } -void IGeneralService::EnumerateNetworkInterfaces(HLERequestContext& ctx) { - LOG_DEBUG(Service_NIFM, "(STUBBED) called."); - - // TODO (jarrodnorwell) - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); -} - -void IGeneralService::EnumerateNetworkProfiles(HLERequestContext& ctx) { - LOG_DEBUG(Service_NIFM, "(STUBBED) called."); - - // TODO (jarrodnorwell) - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); -} void IGeneralService::RemoveNetworkProfile(HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); @@ -447,6 +739,94 @@ void IGeneralService::RemoveNetworkProfile(HLERequestContext& ctx) { rb.Push(ResultSuccess); } +void IGeneralService::GetScanData(HLERequestContext& ctx) { + LOG_INFO(Service_NIFM, "GetScanData called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(0); +} + +void IGeneralService::GetScanDataV2(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + std::scoped_lock lk{g_scan_mtx}; + const auto& scans = g_last_scan_results; + + const auto& net_state = Network::EmuNetState::Get(); + + const std::size_t guest_bytes = ctx.GetWriteBufferSize(); + const std::size_t max_rows = guest_bytes / sizeof(AccessPointDataV3); + const std::size_t rows_copy = std::min(scans.size(), max_rows); + + + std::vector rows; + rows.resize(rows_copy); + + auto to_bars = [](u8 q) { + return static_cast((q + 16) / 33); // quick maths + }; + + for (std::size_t i = 0; i < rows_copy; ++i) { + const Network::ScanData& s = scans[i]; + auto& ap = rows[i]; + + ap.ssid_len = s.ssid_len; + std::memcpy(ap.ssid, s.ssid, s.ssid_len); + ap.strength = to_bars(s.quality); + + bool is_connected = std::strncmp(net_state.ssid, ap.ssid, ap.ssid_len) == 0 && + net_state.ssid[ap.ssid_len] == '\0'; + + ap.visible = (is_connected) ? 0 : 1; + ap.has_password = (s.flags & 2) ? 2 : 1; + ap.is_available = 1; + } + + if (rows_copy) + ctx.WriteBuffer(rows.data(), rows_copy * sizeof(AccessPointDataV3)); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(static_cast(scans.size())); +} + +void IGeneralService::GetScanDataV3(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + std::scoped_lock lk{g_scan_mtx}; + const auto& scans = g_last_scan_results; + + const std::size_t guest_bytes = ctx.GetWriteBufferSize(); + const std::size_t max_rows = guest_bytes / sizeof(AccessPointDataV3); + const std::size_t rows_copy = std::min(scans.size(), max_rows); + + std::vector rows; + rows.resize(rows_copy); + + auto to_bars = [](u8 q) { + return static_cast((q + 16) / 33); // quick maths + }; + + for (std::size_t i = 0; i < rows_copy; ++i) { + const Network::ScanData& s = scans[i]; + auto& ap = rows[i]; + + ap.ssid_len = s.ssid_len; + std::memcpy(ap.ssid, s.ssid, s.ssid_len); + ap.strength = to_bars(s.quality); + ap.visible = 1; + ap.has_password = (s.flags & 2) ? 2 : 1; + ap.is_available = 1; + } + + if (rows_copy) + ctx.WriteBuffer(rows.data(), rows_copy * sizeof(AccessPointDataV3)); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(static_cast(scans.size())); +} + void IGeneralService::GetCurrentIpAddress(HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); @@ -456,7 +836,6 @@ void IGeneralService::GetCurrentIpAddress(HLERequestContext& ctx) { ipv4.emplace(Network::IPv4Address{0, 0, 0, 0}); } - // When we're connected to a room, spoof the hosts IP address if (auto room_member = Network::GetRoomMember().lock()) { if (room_member->IsConnected()) { ipv4 = room_member->GetFakeIpAddress(); @@ -484,74 +863,85 @@ void IGeneralService::CreateTemporaryNetworkProfile(HLERequestContext& ctx) { } void IGeneralService::GetCurrentIpConfigInfo(HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); + Network::RefreshFromHost(); + const auto& st = Network::EmuNetState::Get(); struct IpConfigInfo { - IpAddressSetting ip_address_setting{}; - DnsSetting dns_setting{}; + IpAddressSetting ip{}; + DnsSetting dns{}; }; - static_assert(sizeof(IpConfigInfo) == sizeof(IpAddressSetting) + sizeof(DnsSetting), - "IpConfigInfo has incorrect size."); + static_assert(sizeof(IpConfigInfo) == sizeof(IpAddressSetting) + sizeof(DnsSetting)); - const auto net_iface = Network::GetSelectedNetworkInterface(); + IpConfigInfo info{}; - IpConfigInfo ip_config_info = [&net_iface] { - if (!net_iface) { - return IpConfigInfo{}; - } + if (st.connected) { + info.ip.is_automatic = true; + info.ip.ip_address = st.ip; + info.ip.subnet_mask = st.mask; + info.ip.default_gateway = st.gw; - return IpConfigInfo{ - .ip_address_setting{ - .is_automatic{true}, - .ip_address{Network::TranslateIPv4(net_iface->ip_address)}, - .subnet_mask{Network::TranslateIPv4(net_iface->subnet_mask)}, - .default_gateway{Network::TranslateIPv4(net_iface->gateway)}, - }, - .dns_setting{ - .is_automatic{true}, - .primary_dns{1, 1, 1, 1}, - .secondary_dns{1, 0, 0, 1}, - }, - }; - }(); - - // When we're connected to a room, spoof the hosts IP address - if (auto room_member = Network::GetRoomMember().lock()) { - if (room_member->IsConnected()) { - ip_config_info.ip_address_setting.ip_address = room_member->GetFakeIpAddress(); - } + info.dns.is_automatic = true; + info.dns.primary_dns = {1, 1, 1, 1}; + info.dns.secondary_dns = {8, 8, 8, 8}; } - IPC::ResponseBuilder rb{ctx, 2 + (sizeof(IpConfigInfo) + 3) / sizeof(u32)}; + IPC::ResponseBuilder rb{ctx, 2 + (sizeof(IpConfigInfo) + 3) / 4}; rb.Push(ResultSuccess); - rb.PushRaw(ip_config_info); + rb.PushRaw(info); +} + +void IGeneralService::SetWirelessCommunicationEnabled(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u8 enable = rp.Pop(); + + Settings::values.airplane_mode.SetValue(enable == 0); + + IPC::ResponseBuilder{ctx, 2}.Push(ResultSuccess); } void IGeneralService::IsWirelessCommunicationEnabled(HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); - + const bool en = !Settings::values.airplane_mode.GetValue(); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(1); + rb.Push(en); } void IGeneralService::GetInternetConnectionStatus(HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); + Network::RefreshFromHost(); + + const auto& st = Network::EmuNetState::Get(); struct Output { - u8 type{static_cast(NetworkInterfaceType::WiFi_Ieee80211)}; - u8 wifi_strength{3}; - InternetConnectionStatus state{InternetConnectionStatus::Connected}; + u8 type; // 1 Wi-Fi, 2 Ethernet + u8 bars; // 0-3 + InternetConnectionStatus state; }; - static_assert(sizeof(Output) == 0x3, "Output has incorrect size."); + Output out{}; - constexpr Output out{}; + if (!st.connected) { + out.type = 1; + out.bars = 0; + out.state = InternetConnectionStatus::ConnectingUnknown1; + } else { + out.type = st.via_wifi ? 1 : 2; + out.bars = st.bars; + out.state = InternetConnectionStatus::Connected; + } IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); rb.PushRaw(out); } +void IGeneralService::SetEthernetCommunicationEnabled(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u8 enable = rp.Pop(); + + Network::EmuNetState::Get().ethernet_enabled.store(enable != 0); + + IPC::ResponseBuilder{ctx, 2}.Push(ResultSuccess); +} + void IGeneralService::IsEthernetCommunicationEnabled(HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); @@ -615,28 +1005,18 @@ void IGeneralService::SetBackgroundRequestEnabled(HLERequestContext& ctx) { } void IGeneralService::GetCurrentAccessPoint(HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); + Network::RefreshFromHost(); - struct AccessPointInfo { - u8 ssid_length{}; - std::array ssid{}; - u8 unknown_1{}; - u8 unknown_2{}; - u8 unknown_3{}; - std::array passphrase{}; - }; - static_assert(sizeof(AccessPointInfo) == 0x65, "AccessPointInfo has incorrect size."); + const auto& st = Network::EmuNetState::Get(); - const auto net_iface = Network::GetSelectedNetworkInterface(); - AccessPointInfo access_point_info{}; + AccessPointDataV3 access_point_info{}; - if (net_iface) { - const std::string ssid = "yuzu Network"; - access_point_info.ssid_length = static_cast(ssid.size()); - std::memcpy(access_point_info.ssid.data(), ssid.c_str(), ssid.size()); - - const std::string passphrase = "yuzupassword"; - std::memcpy(access_point_info.passphrase.data(), passphrase.c_str(), passphrase.size()); + if (st.connected && st.via_wifi) { + access_point_info.ssid_len = static_cast(std::strlen(st.ssid)); + access_point_info.strength = st.bars; + access_point_info.visible = 1; + access_point_info.is_available = 1; + std::memcpy(access_point_info.ssid, st.ssid, access_point_info.ssid_len); } ctx.WriteBuffer(access_point_info); @@ -648,6 +1028,7 @@ void IGeneralService::GetCurrentAccessPoint(HLERequestContext& ctx) { IGeneralService::IGeneralService(Core::System& system_) : ServiceFramework{system_, "IGeneralService"} { // clang-format off + static const FunctionInfo functions[] = { {1, &IGeneralService::GetClientId, "GetClientId"}, {2, &IGeneralService::CreateScanRequest, "CreateScanRequest"}, @@ -655,18 +1036,18 @@ IGeneralService::IGeneralService(Core::System& system_) {5, &IGeneralService::GetCurrentNetworkProfile, "GetCurrentNetworkProfile"}, {6, &IGeneralService::EnumerateNetworkInterfaces, "EnumerateNetworkInterfaces"}, {7, &IGeneralService::EnumerateNetworkProfiles, "EnumerateNetworkProfiles"}, - {8, nullptr, "GetNetworkProfile"}, - {9, nullptr, "SetNetworkProfile"}, + {8, &IGeneralService::GetNetworkProfile, "GetNetworkProfile"}, + {9, &IGeneralService::SetNetworkProfile, "SetNetworkProfile"}, {10, &IGeneralService::RemoveNetworkProfile, "RemoveNetworkProfile"}, - {11, nullptr, "GetScanDataOld"}, + {11, &IGeneralService::GetScanData, "GetScanDataOld"}, {12, &IGeneralService::GetCurrentIpAddress, "GetCurrentIpAddress"}, {13, nullptr, "GetCurrentAccessPointOld"}, {14, &IGeneralService::CreateTemporaryNetworkProfile, "CreateTemporaryNetworkProfile"}, {15, &IGeneralService::GetCurrentIpConfigInfo, "GetCurrentIpConfigInfo"}, - {16, nullptr, "SetWirelessCommunicationEnabled"}, + {16, &IGeneralService::SetWirelessCommunicationEnabled, "SetWirelessCommunicationEnabled"}, {17, &IGeneralService::IsWirelessCommunicationEnabled, "IsWirelessCommunicationEnabled"}, {18, &IGeneralService::GetInternetConnectionStatus, "GetInternetConnectionStatus"}, - {19, nullptr, "SetEthernetCommunicationEnabled"}, + {19, &IGeneralService::SetEthernetCommunicationEnabled, "SetEthernetCommunicationEnabled"}, {20, &IGeneralService::IsEthernetCommunicationEnabled, "IsEthernetCommunicationEnabled"}, {21, &IGeneralService::IsAnyInternetRequestAccepted, "IsAnyInternetRequestAccepted"}, {22, &IGeneralService::IsAnyForegroundRequestAccepted, "IsAnyForegroundRequestAccepted"}, @@ -682,7 +1063,7 @@ IGeneralService::IGeneralService(Core::System& system_) {32, nullptr, "GetTelemetryInfo"}, {33, &IGeneralService::ConfirmSystemAvailability, "ConfirmSystemAvailability"}, // 2.0.0+ {34, &IGeneralService::SetBackgroundRequestEnabled, "SetBackgroundRequestEnabled"}, // 4.0.0+ - {35, nullptr, "GetScanData"}, + {35, &IGeneralService::GetScanDataV2, "GetScanData"}, {36, &IGeneralService::GetCurrentAccessPoint, "GetCurrentAccessPoint"}, {37, nullptr, "Shutdown"}, {38, nullptr, "GetAllowedChannels"}, @@ -694,7 +1075,7 @@ IGeneralService::IGeneralService(Core::System& system_) {44, nullptr, "IsWiredConnectionAvailable"}, // 18.0.0+ {45, nullptr, "IsNetworkEmulationFeatureEnabled"}, // 18.0.0+ {46, nullptr, "SelectActiveNetworkEmulationProfileIdForDebug"}, // 18.0.0+ - {47, nullptr, "GetActiveNetworkEmulationProfileId"}, // 18.0.0+ + {47, &IGeneralService::GetScanDataV3, "GetScanData"}, // 19.0.0+ {50, nullptr, "IsRewriteFeatureEnabled"}, // 18.0.0+ {51, nullptr, "CreateRewriteRule"}, // 18.0.0+ {52, nullptr, "DestroyRewriteRule"} // 18.0.0+ diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h index 7071fa90d7..3b5a438c75 100644 --- a/src/core/hle/service/nifm/nifm.h +++ b/src/core/hle/service/nifm/nifm.h @@ -1,9 +1,9 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator +// Project// SPDX-License-Identifier: GPL-2.0-or-later + #pragma once #include "core/hle/service/service.h" @@ -14,6 +14,9 @@ class System; namespace Service::NIFM { +class IScanRequest; +class IRequest; + void LoopProcess(Core::System& system); class IGeneralService final : public ServiceFramework { @@ -28,19 +31,26 @@ private: void GetCurrentNetworkProfile(HLERequestContext& ctx); void EnumerateNetworkInterfaces(HLERequestContext& ctx); void EnumerateNetworkProfiles(HLERequestContext& ctx); + void GetNetworkProfile(HLERequestContext& ctx); + void SetNetworkProfile(HLERequestContext& ctx); void RemoveNetworkProfile(HLERequestContext& ctx); + void GetScanData(HLERequestContext& ctx); void GetCurrentIpAddress(HLERequestContext& ctx); void CreateTemporaryNetworkProfile(HLERequestContext& ctx); void GetCurrentIpConfigInfo(HLERequestContext& ctx); + void SetWirelessCommunicationEnabled(HLERequestContext& ctx); void IsWirelessCommunicationEnabled(HLERequestContext& ctx); void GetInternetConnectionStatus(HLERequestContext& ctx); + void SetEthernetCommunicationEnabled(HLERequestContext& ctx); void IsEthernetCommunicationEnabled(HLERequestContext& ctx); void IsAnyInternetRequestAccepted(HLERequestContext& ctx); void IsAnyForegroundRequestAccepted(HLERequestContext& ctx); void GetSsidListVersion(HLERequestContext& ctx); + void GetScanDataV2(HLERequestContext& ctx); void ConfirmSystemAvailability(HLERequestContext& ctx); void SetBackgroundRequestEnabled(HLERequestContext& ctx); void GetCurrentAccessPoint(HLERequestContext& ctx); + void GetScanDataV3(HLERequestContext& ctx); }; } // namespace Service::NIFM diff --git a/src/core/hle/service/ns/dynamic_rights_interface.cpp b/src/core/hle/service/ns/dynamic_rights_interface.cpp index e1d4974268..b5a507d6e9 100644 --- a/src/core/hle/service/ns/dynamic_rights_interface.cpp +++ b/src/core/hle/service/ns/dynamic_rights_interface.cpp @@ -63,9 +63,10 @@ Result IDynamicRightsInterface::VerifyActivatedRightsOwners(u64 rights_handle) { R_SUCCEED(); } -Result IDynamicRightsInterface::HasAccountRestrictedRightsInRunningApplications(Out out_bool) { - LOG_WARNING(Service_NS, "(STUBBED) called"); - *out_bool = 0; +Result IDynamicRightsInterface::HasAccountRestrictedRightsInRunningApplications( + Out out_status, u64 rights_handle) { + LOG_WARNING(Service_NS, "(STUBBED) called, rights_handle={:#x}", rights_handle); + *out_status = 0; R_SUCCEED(); } diff --git a/src/core/hle/service/ns/dynamic_rights_interface.h b/src/core/hle/service/ns/dynamic_rights_interface.h index 0f78c4d5bd..e9429ea915 100644 --- a/src/core/hle/service/ns/dynamic_rights_interface.h +++ b/src/core/hle/service/ns/dynamic_rights_interface.h @@ -20,7 +20,8 @@ private: Result NotifyApplicationRightsCheckStart(); Result GetRunningApplicationStatus(Out out_status, u64 rights_handle); Result VerifyActivatedRightsOwners(u64 rights_handle); - Result HasAccountRestrictedRightsInRunningApplications(Out out_bool); + Result HasAccountRestrictedRightsInRunningApplications(Out out_status, + u64 rights_handle); }; } // namespace Service::NS diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index 4be70e0559..07df83caad 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -1,9 +1,9 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + #include #include #include @@ -22,6 +22,7 @@ #include "core/internal_network/socket_proxy.h" #include "core/internal_network/sockets.h" #include "network/network.h" +#include using Common::Expected; using Common::Unexpected; @@ -489,6 +490,12 @@ void BSD::ExecuteWork(HLERequestContext& ctx, Work work) { } std::pair BSD::SocketImpl(Domain domain, Type type, Protocol protocol) { + + if (Settings::values.airplane_mode.GetValue()) { + LOG_ERROR(Service, "Airplane mode is enabled, cannot create socket"); + return {-1, Errno::NOTCONN}; + } + if (type == Type::SEQPACKET) { UNIMPLEMENTED_MSG("SOCK_SEQPACKET errno management"); } else if (type == Type::RAW && (domain != Domain::INET || protocol != Protocol::ICMP)) { diff --git a/src/core/internal_network/emu_net_state.cpp b/src/core/internal_network/emu_net_state.cpp new file mode 100644 index 0000000000..1d4052e4f8 --- /dev/null +++ b/src/core/internal_network/emu_net_state.cpp @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "common/logging/log.h" +#include "core/internal_network/emu_net_state.h" +#include "core/internal_network/network.h" +#include "core/internal_network/network_interface.h" + +#ifdef _WIN32 +#define NOMINMAX +#include +#include +#pragma comment(lib, "wlanapi.lib") +#endif +#include + +namespace Network { + +EmuNetState& EmuNetState::Get() { + static EmuNetState instance; + return instance; +} + +u8 QualityToBars(u8 q) { + if (q == 0) + return 0; + else if (q < 34) + return 1; + else if (q < 67) + return 2; + else + return 3; +} + +void RefreshFromHost() { + auto& st = Network::EmuNetState::Get(); + std::scoped_lock lk{st.mtx}; + + const auto sel = GetSelectedNetworkInterface(); + + if (!sel.has_value()) { + st.connected = false; + st.via_wifi = false; + st.wifi_enabled = false; + st.ethernet_enabled = false; + std::memset(st.ssid, 0, sizeof(st.ssid)); + st.secure = false; + st.bars = 0; + return; + } + + st.wifi_enabled = !Settings::values.airplane_mode.GetValue(); + st.ethernet_enabled = sel->kind == HostAdapterKind::Ethernet; + + st.connected = true; + st.via_wifi = sel->kind == HostAdapterKind::Wifi; + std::strncpy(st.ssid, sel->name.c_str(), sizeof(st.ssid) - 1); + st.secure = true; + st.ip = TranslateIPv4(sel->ip_address); + st.mask = TranslateIPv4(sel->subnet_mask); + st.gw = TranslateIPv4(sel->gateway); + +#ifdef _WIN32 + + if (st.via_wifi) { + HANDLE hClient{}; + DWORD ver{}; + if (WlanOpenHandle(2, nullptr, &ver, &hClient) == ERROR_SUCCESS) { + PWLAN_INTERFACE_INFO_LIST ifs{}; + if (WlanEnumInterfaces(hClient, nullptr, &ifs) == ERROR_SUCCESS) { + const auto& g = ifs->InterfaceInfo[0].InterfaceGuid; + PWLAN_CONNECTION_ATTRIBUTES attr{}; + DWORD attrSize = 0; + WLAN_OPCODE_VALUE_TYPE opType{}; + if (WlanQueryInterface(hClient, &g, wlan_intf_opcode_current_connection, nullptr, + &attrSize, reinterpret_cast(&attr), + &opType) == ERROR_SUCCESS) { + + const DOT11_SSID& ssid = attr->wlanAssociationAttributes.dot11Ssid; + const size_t len = std::min(ssid.uSSIDLength, sizeof(st.ssid) - 1); + std::memset(st.ssid, 0, sizeof(st.ssid)); + std::memcpy(st.ssid, ssid.ucSSID, len); + + st.bars = QualityToBars( + static_cast(attr->wlanAssociationAttributes.wlanSignalQuality)); + WlanFreeMemory(attr); + } + WlanFreeMemory(ifs); + } + WlanCloseHandle(hClient, nullptr); + } + } else { + st.bars = 3; + } +#else + /* non-Windows stub */ + st.bars = st.via_wifi ? 2 : 3; +#endif +} + +} // namespace Network diff --git a/src/core/internal_network/emu_net_state.h b/src/core/internal_network/emu_net_state.h new file mode 100644 index 0000000000..0aa747f9a4 --- /dev/null +++ b/src/core/internal_network/emu_net_state.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once +#include +#include + +namespace Network { + +struct EmuNetState { + + static EmuNetState& Get(); + + EmuNetState(const EmuNetState&) = delete; + EmuNetState& operator=(const EmuNetState&) = delete; + EmuNetState(EmuNetState&&) = delete; + EmuNetState& operator=(EmuNetState&&) = delete; + + std::atomic wifi_enabled{true}; + std::atomic ethernet_enabled{true}; + + std::mutex mtx; + bool connected = false; + bool via_wifi = false; + char ssid[20] = {}; + u8 bars = 0; + bool secure = false; + IPv4Address ip{}, mask{}, gw{}; + +private: + EmuNetState() = default; +}; + +void RefreshFromHost(); +u8 QualityToBars(u8 quality); + +} // namespace Network diff --git a/src/core/internal_network/network_interface.cpp b/src/core/internal_network/network_interface.cpp index f92c3b7b66..42def7af92 100644 --- a/src/core/internal_network/network_interface.cpp +++ b/src/core/internal_network/network_interface.cpp @@ -12,6 +12,7 @@ #include "common/polyfill_ranges.h" #include "common/settings.h" #include "common/string_util.h" +#include "core/internal_network/emu_net_state.h" #include "core/internal_network/network_interface.h" #ifdef _WIN32 @@ -27,65 +28,65 @@ namespace Network { #ifdef _WIN32 std::vector GetAvailableNetworkInterfaces() { - std::vector adapter_addresses; - DWORD ret = ERROR_BUFFER_OVERFLOW; - DWORD buf_size = 0; - // retry up to 5 times - for (int i = 0; i < 5 && ret == ERROR_BUFFER_OVERFLOW; i++) { - ret = GetAdaptersAddresses( + ULONG buf_size = 0; + if (GetAdaptersAddresses( AF_INET, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_GATEWAYS, - nullptr, adapter_addresses.data(), &buf_size); - - if (ret != ERROR_BUFFER_OVERFLOW) { - break; - } - - adapter_addresses.resize((buf_size / sizeof(IP_ADAPTER_ADDRESSES)) + 1); + nullptr, nullptr, &buf_size) != ERROR_BUFFER_OVERFLOW) { + LOG_ERROR(Network, "GetAdaptersAddresses(overrun probe) failed"); + return {}; } - if (ret != NO_ERROR) { - LOG_ERROR(Network, "Failed to get network interfaces with GetAdaptersAddresses"); + std::vector buffer(buf_size, 0); + auto* addrs = reinterpret_cast(buffer.data()); + + if (GetAdaptersAddresses( + AF_INET, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_GATEWAYS, + nullptr, addrs, &buf_size) != NO_ERROR) { + LOG_ERROR(Network, "GetAdaptersAddresses(data) failed"); return {}; } std::vector result; - for (auto current_address = adapter_addresses.data(); current_address != nullptr; - current_address = current_address->Next) { - if (current_address->FirstUnicastAddress == nullptr || - current_address->FirstUnicastAddress->Address.lpSockaddr == nullptr) { + for (auto* a = addrs; a; a = a->Next) { + + if (a->OperStatus != IfOperStatusUp || !a->FirstUnicastAddress || + !a->FirstUnicastAddress->Address.lpSockaddr) continue; - } - if (current_address->OperStatus != IfOperStatusUp) { + const in_addr ip = + reinterpret_cast(a->FirstUnicastAddress->Address.lpSockaddr)->sin_addr; + + ULONG mask_raw = 0; + if (ConvertLengthToIpv4Mask(a->FirstUnicastAddress->OnLinkPrefixLength, &mask_raw) != + NO_ERROR) continue; - } - const auto ip_addr = Common::BitCast( - *current_address->FirstUnicastAddress->Address.lpSockaddr) - .sin_addr; + in_addr mask{.S_un{.S_addr{mask_raw}}}; - ULONG mask = 0; - if (ConvertLengthToIpv4Mask(current_address->FirstUnicastAddress->OnLinkPrefixLength, - &mask) != NO_ERROR) { - LOG_ERROR(Network, "Failed to convert IPv4 prefix length to subnet mask"); - continue; - } + in_addr gw{.S_un{.S_addr{0}}}; + if (a->FirstGatewayAddress && a->FirstGatewayAddress->Address.lpSockaddr) + gw = reinterpret_cast(a->FirstGatewayAddress->Address.lpSockaddr) + ->sin_addr; - struct in_addr gateway = {.S_un{.S_addr{0}}}; - if (current_address->FirstGatewayAddress != nullptr && - current_address->FirstGatewayAddress->Address.lpSockaddr != nullptr) { - gateway = Common::BitCast( - *current_address->FirstGatewayAddress->Address.lpSockaddr) - .sin_addr; + HostAdapterKind kind = HostAdapterKind::Ethernet; + switch (a->IfType) { + case IF_TYPE_IEEE80211: // 802.11 Wi-Fi + kind = HostAdapterKind::Wifi; + break; + default: + kind = HostAdapterKind::Ethernet; + break; } result.emplace_back(NetworkInterface{ - .name{Common::UTF16ToUTF8(std::wstring{current_address->FriendlyName})}, - .ip_address{ip_addr}, - .subnet_mask = in_addr{.S_un{.S_addr{mask}}}, - .gateway = gateway}); + .name = Common::UTF16ToUTF8(std::wstring{a->FriendlyName}), + .ip_address = ip, + .subnet_mask = mask, + .gateway = gw, + .kind = kind + }); } return result; @@ -197,6 +198,7 @@ std::vector GetAvailableNetworkInterfaces() { #endif // _WIN32 std::optional GetSelectedNetworkInterface() { + const auto& selected_network_interface = Settings::values.network_interface.GetValue(); const auto network_interfaces = Network::GetAvailableNetworkInterfaces(); if (network_interfaces.empty()) { diff --git a/src/core/internal_network/network_interface.h b/src/core/internal_network/network_interface.h index 175e61b1f9..769f4489ec 100644 --- a/src/core/internal_network/network_interface.h +++ b/src/core/internal_network/network_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -15,15 +18,26 @@ namespace Network { +enum class HostAdapterKind { Wifi, Ethernet }; + struct NetworkInterface { std::string name; struct in_addr ip_address; struct in_addr subnet_mask; struct in_addr gateway; + HostAdapterKind kind{HostAdapterKind::Ethernet}; }; std::vector GetAvailableNetworkInterfaces(); std::optional GetSelectedNetworkInterface(); void SelectFirstNetworkInterface(); +struct HostAdapter { + in_addr ip_address; + in_addr subnet_mask; + in_addr gateway; + std::string name; + HostAdapterKind kind; +}; + } // namespace Network diff --git a/src/core/internal_network/wifi_scanner.cpp b/src/core/internal_network/wifi_scanner.cpp new file mode 100644 index 0000000000..bab644d161 --- /dev/null +++ b/src/core/internal_network/wifi_scanner.cpp @@ -0,0 +1,186 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include + +#include "common/logging/log.h" +#include "core/internal_network/wifi_scanner.h" + +using namespace std::chrono_literals; + +namespace Network { + +#ifdef _WIN32 +#define NOMINMAX +#include +#include +#pragma comment(lib, "wlanapi.lib") + +static u8 QualityToPercent(DWORD q) { + return static_cast(q); +} + +static std::vector ScanWifiWin(std::chrono::milliseconds deadline) { + std::vector out; + + HANDLE hClient{}; + DWORD ver{}; + if (WlanOpenHandle(2, nullptr, &ver, &hClient) != ERROR_SUCCESS) + return out; + + PWLAN_INTERFACE_INFO_LIST ifs{}; + if (WlanEnumInterfaces(hClient, nullptr, &ifs) != ERROR_SUCCESS || ifs->dwNumberOfItems == 0) { + WlanCloseHandle(hClient, nullptr); + return out; + } + + // fire a scan on every adapter + for (DWORD i = 0; i < ifs->dwNumberOfItems; ++i) + WlanScan(hClient, &ifs->InterfaceInfo[i].InterfaceGuid, nullptr, nullptr, nullptr); + + const auto start = std::chrono::steady_clock::now(); + bool have = false; + + while (!have && std::chrono::steady_clock::now() - start < deadline) { + std::this_thread::sleep_for(100ms); + out.clear(); + + for (DWORD i = 0; i < ifs->dwNumberOfItems; ++i) { + PWLAN_AVAILABLE_NETWORK_LIST list{}; + if (WlanGetAvailableNetworkList(hClient, &ifs->InterfaceInfo[i].InterfaceGuid, + WLAN_AVAILABLE_NETWORK_INCLUDE_ALL_ADHOC_PROFILES, + nullptr, &list) != ERROR_SUCCESS) + continue; + + for (DWORD n = 0; n < list->dwNumberOfItems; ++n) { + const auto& nw = list->Network[n]; + ScanData sd{}; + sd.ssid_len = static_cast(nw.dot11Ssid.uSSIDLength); + std::memcpy(sd.ssid, nw.dot11Ssid.ucSSID, sd.ssid_len); + sd.quality = QualityToPercent(nw.wlanSignalQuality); + if (nw.bNetworkConnectable) + sd.flags |= 1; + if (nw.bSecurityEnabled) + sd.flags |= 2; + out.emplace_back(sd); + + char tmp[0x22]{}; + std::memcpy(tmp, sd.ssid, sd.ssid_len); + LOG_INFO(Network, "[WifiScan] +%u \"%s\" quality=%u flags=0x%X", sd.ssid_len, tmp, + sd.quality, sd.flags); + } + WlanFreeMemory(list); + } + have = !out.empty(); + } + + WlanFreeMemory(ifs); + WlanCloseHandle(hClient, nullptr); + return out; +} +#endif /* _WIN32 */ + +#if defined(__linux__) && !defined(_WIN32) && !defined(ANDROID) +#include + +static u8 QualityToPercent(const iwrange& r, const wireless_scan* ws) { + const iw_quality qual = ws->stats.qual; + const int lvl = qual.level; + const int max = r.max_qual.level ? r.max_qual.level : 100; + return static_cast(std::clamp(100 * lvl / max, 0, 100)); +} + +static int wifi_callback(int skfd, char* ifname, char* args[], int count) +{ + iwrange range; + + int res = iw_get_range_info(skfd, ifname, &range); + + LOG_INFO(Network, "ifname {} returned {} on iw_get_range_info", ifname, res); + + if (res >= 0) { + strncpy(args[0], ifname, IFNAMSIZ - 1); + args[0][IFNAMSIZ - 1] = 0; + + return 1; + } + + return 0; +} + +// TODO(crueter, Maufeat): Check if driver supports wireless extensions, fallback to nl80211 if not +static std::vector ScanWifiLinux(std::chrono::milliseconds deadline) { + std::vector out; + int sock = iw_sockets_open(); + if (sock < 0) { + LOG_ERROR(Network, "iw_sockets_open() failed"); + return out; + } + + char ifname[IFNAMSIZ] = {0}; + char *args[1] = {ifname}; + + iw_enum_devices(sock, &wifi_callback, args, 0); + + if (strlen(ifname) == 0) { + LOG_WARNING(Network, "No wireless interface found"); + iw_sockets_close(sock); + return out; + } + + iwrange range{}; + if (iw_get_range_info(sock, ifname, &range) < 0) { + LOG_WARNING(Network, "iw_get_range_info failed on {}", ifname); + iw_sockets_close(sock); + return out; + } + + wireless_scan_head head{}; + const auto start = std::chrono::steady_clock::now(); + bool have = false; + + while (!have && std::chrono::steady_clock::now() - start < deadline) { + std::this_thread::sleep_for(100ms); + if (iw_scan(sock, ifname, range.we_version_compiled, &head) != 0) + continue; + + out.clear(); + for (auto* ws = head.result; ws; ws = ws->next) { + if (!ws->b.has_essid) + continue; + + ScanData sd{}; + sd.ssid_len = static_cast(std::min(ws->b.essid_len, 0x20)); + std::memcpy(sd.ssid, ws->b.essid, sd.ssid_len); + sd.quality = QualityToPercent(range, ws); + sd.flags |= 1; + if (ws->b.has_key) + sd.flags |= 2; + + out.emplace_back(sd); + char tmp[0x22]{}; + std::memcpy(tmp, sd.ssid, sd.ssid_len); + } + have = !out.empty(); + } + + iw_sockets_close(sock); + return out; +} +#endif /* linux */ + +std::vector ScanWifiNetworks(std::chrono::milliseconds deadline) { +#ifdef _WIN32 + return ScanWifiWin(deadline); +#elif defined(__linux__) && !defined(ANDROID) + return ScanWifiLinux(deadline); +#else + std::this_thread::sleep_for(deadline); + return {}; // unsupported host, pretend no results +#endif +} + +} // namespace Network diff --git a/src/core/internal_network/wifi_scanner.h b/src/core/internal_network/wifi_scanner.h new file mode 100644 index 0000000000..a57edc303b --- /dev/null +++ b/src/core/internal_network/wifi_scanner.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +#include "core/core.h" + +namespace Network { + +struct ScanData { + u8 ssid_len{}; + char ssid[0x21]{}; + u8 quality{}; + u32 flags{}; +}; +static_assert(sizeof(ScanData) <= 0x2C, "ScanData layout changed – update conversions!"); + +std::vector ScanWifiNetworks(std::chrono::milliseconds deadline); +} diff --git a/src/yuzu/configuration/configure_network.cpp b/src/yuzu/configuration/configure_network.cpp index ba1986eb1b..7df5e51a7f 100644 --- a/src/yuzu/configuration/configure_network.cpp +++ b/src/yuzu/configuration/configure_network.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -24,6 +27,7 @@ ConfigureNetwork::~ConfigureNetwork() = default; void ConfigureNetwork::ApplyConfiguration() { Settings::values.network_interface = ui->network_interface->currentText().toStdString(); + Settings::values.airplane_mode = ui->airplane_mode->isChecked(); } void ConfigureNetwork::changeEvent(QEvent* event) { @@ -42,7 +46,11 @@ void ConfigureNetwork::SetConfiguration() { const bool runtime_lock = !system.IsPoweredOn(); const std::string& network_interface = Settings::values.network_interface.GetValue(); + const bool& airplane_mode = Settings::values.airplane_mode.GetValue(); ui->network_interface->setCurrentText(QString::fromStdString(network_interface)); ui->network_interface->setEnabled(runtime_lock); + + ui->airplane_mode->setChecked(airplane_mode); + ui->network_interface->setEnabled(true); } diff --git a/src/yuzu/configuration/configure_network.ui b/src/yuzu/configuration/configure_network.ui index f10e973b12..1a515ccd16 100644 --- a/src/yuzu/configuration/configure_network.ui +++ b/src/yuzu/configuration/configure_network.ui @@ -35,6 +35,13 @@ + + + + Enable Airplane Mode + + + @@ -43,7 +50,7 @@ - Qt::Vertical + Qt::Orientation::Vertical diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index ae48907274..cd3239205e 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1859,11 +1859,12 @@ bool GMainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPa system->GetUserChannel().clear(); } + system->SetFrontendAppletSet({ - std::make_unique(*this), // Amiibo Settings + std::make_unique(*this), // Amiibo Settings (UISettings::values.controller_applet_disabled.GetValue() == true) ? nullptr - : std::make_unique(*this), // Controller Selector + : std::make_unique(*this), // Controller Selector std::make_unique(*this), // Error Display nullptr, // Mii Editor nullptr, // Parental Controls @@ -1871,6 +1872,7 @@ bool GMainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPa std::make_unique(*this), // Profile Selector std::make_unique(*this), // Software Keyboard std::make_unique(*this), // Web Browser + nullptr, // Net Connect }); /** Game Updates check */