mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2025-07-20 00:05:47 +00:00
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 <swurl@swurl.xyz> Co-authored-by: Aleksandr Popovich <alekpopo@pm.me> Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/204 Co-authored-by: Maufeat <sahyno1996@gmail.com> Co-committed-by: Maufeat <sahyno1996@gmail.com>
This commit is contained in:
parent
b2e602325c
commit
2e6a289a0b
34 changed files with 1193 additions and 203 deletions
29
CMakeModules/Findlibiw.cmake
Normal file
29
CMakeModules/Findlibiw.cmake
Normal file
|
@ -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()
|
|
@ -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"),
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<SettingsItem>) {
|
||||
sl.apply {
|
||||
add(IntSetting.SWKBD_APPLET.key)
|
||||
add(BooleanSetting.AIRPLANE_MODE.key)
|
||||
}
|
||||
}
|
||||
private fun addInputPlayer(sl: ArrayList<SettingsItem>, playerIndex: Int) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -966,6 +966,9 @@
|
|||
|
||||
<string name="swkbd_applet">Software Keyboard</string>
|
||||
|
||||
<string name="airplane_mode">Airplane Mode</string>
|
||||
<string name="airplane_mode_description">Passes Airplane Mode to the Switch OS</string>
|
||||
|
||||
<!-- Licenses screen strings -->
|
||||
<string name="licenses">Licenses</string>
|
||||
<string name="license_fidelityfx_fsr" translatable="false">FidelityFX-FSR</string>
|
||||
|
|
|
@ -147,7 +147,7 @@ struct Values {
|
|||
Category::LibraryApplet};
|
||||
Setting<AppletMode> error_applet_mode{linkage, AppletMode::LLE, "error_applet_mode",
|
||||
Category::LibraryApplet};
|
||||
Setting<AppletMode> net_connect_applet_mode{linkage, AppletMode::HLE, "net_connect_applet_mode",
|
||||
Setting<AppletMode> net_connect_applet_mode{linkage, AppletMode::LLE, "net_connect_applet_mode",
|
||||
Category::LibraryApplet};
|
||||
Setting<AppletMode> player_select_applet_mode{
|
||||
linkage, AppletMode::LLE, "player_select_applet_mode", Category::LibraryApplet};
|
||||
|
@ -675,6 +675,7 @@ struct Values {
|
|||
// Network
|
||||
Setting<std::string> network_interface{linkage, std::string(), "network_interface",
|
||||
Category::Network};
|
||||
SwitchableSetting<bool> airplane_mode{linkage, false, "airplane_mode", Category::Network};
|
||||
|
||||
// WebService
|
||||
Setting<std::string> web_api_url{linkage, "api.ynet-fun.xyz", "web_api_url",
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
13
src/core/frontend/applets/net_connect.cpp
Normal file
13
src/core/frontend/applets/net_connect.cpp
Normal file
|
@ -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
|
22
src/core/frontend/applets/net_connect.h
Normal file
22
src/core/frontend/applets/net_connect.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#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
|
46
src/core/hle/service/am/frontend/applet_net_connect.cpp
Normal file
46
src/core/hle/service/am/frontend/applet_net_connect.cpp
Normal file
|
@ -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> 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
|
32
src/core/hle/service/am/frontend/applet_net_connect.h
Normal file
32
src/core/hle/service/am/frontend/applet_net_connect.h
Normal file
|
@ -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> 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
|
|
@ -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 <cstring>
|
||||
|
||||
|
@ -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<Core::Frontend::DefaultWebBrowserApplet>();
|
||||
}
|
||||
|
||||
if (frontend.net_connect == nullptr) {
|
||||
frontend.net_connect = std::make_unique<Core::Frontend::DefaultNetConnectApplet>();
|
||||
}
|
||||
}
|
||||
|
||||
void FrontendAppletHolder::ClearAll() {
|
||||
|
@ -230,6 +241,8 @@ std::shared_ptr<FrontendApplet> FrontendAppletHolder::GetApplet(std::shared_ptr<
|
|||
return std::make_shared<WebBrowser>(system, applet, mode, *frontend.web_browser);
|
||||
case AppletId::PhotoViewer:
|
||||
return std::make_shared<PhotoViewer>(system, applet, mode, *frontend.photo_viewer);
|
||||
case AppletId::NetConnect:
|
||||
return std::make_shared<NetConnect>(system, applet, mode, *frontend.net_connect);
|
||||
default:
|
||||
UNIMPLEMENTED_MSG(
|
||||
"No backend implementation exists for applet_id={:02X}! Falling back to stub applet.",
|
||||
|
|
|
@ -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<Core::Frontend::ProfileSelectApplet>;
|
||||
using SoftwareKeyboard = std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet>;
|
||||
using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>;
|
||||
using NetConnect = std::unique_ptr<Core::Frontend::NetConnectApplet>;
|
||||
|
||||
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 {
|
||||
|
|
|
@ -20,7 +20,7 @@ public:
|
|||
|
||||
private:
|
||||
Result GetLastForegroundCaptureImageEx(Out<bool> out_was_written,
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_image_data);
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_image_data);
|
||||
Result GetCallerAppletCaptureImageEx(Out<bool> out_was_written,
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_image_data);
|
||||
Result TakeScreenShotOfOwnLayer(bool unknown0, s32 fbshare_layer_index);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 <filesystem>
|
||||
#include <fstream>
|
||||
#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 <atomic>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <unordered_set>
|
||||
#include <common/settings.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#undef CreateEvent
|
||||
#pragma push_macro("interface")
|
||||
#undef interface
|
||||
#include <wlanapi.h>
|
||||
#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<u8>(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<char, 0x20> ssid{};
|
||||
u8 unknown_1{};
|
||||
u8 unknown_2{};
|
||||
u8 unknown_3{};
|
||||
u8 is_secured{};
|
||||
std::array<char, 0x41> passphrase{};
|
||||
};
|
||||
static_assert(sizeof(SfWirelessSettingData) == 0x65, "SfWirelessSettingData has incorrect size.");
|
||||
|
@ -132,10 +187,10 @@ struct SfNetworkProfileData {
|
|||
IpSettingData ip_setting_data{};
|
||||
u128 uuid{};
|
||||
std::array<char, 0x40> 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<char, 0x21> ssid{};
|
||||
u8 ssid_len{};
|
||||
std::array<char, 0x41> 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<Network::ScanData> g_last_scan_results;
|
||||
static std::mutex g_profile_mtx;
|
||||
static std::optional<PendingProfile> g_pending_profile;
|
||||
|
||||
class IScanRequest final : public ServiceFramework<IScanRequest> {
|
||||
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<Network::ScanData> 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<u8>(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{State::Idle};
|
||||
std::atomic<Result> worker_result{ResultPendingConnection};
|
||||
};
|
||||
|
||||
class IRequest final : public ServiceFramework<IRequest> {
|
||||
|
@ -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<u32>();
|
||||
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<u32>(0);
|
||||
rb.Push<u32>(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<bool> connect_done{false};
|
||||
std::atomic<Result> connect_result{ResultPendingConnection};
|
||||
};
|
||||
|
||||
class INetworkProfile final : public ServiceFramework<INetworkProfile> {
|
||||
|
@ -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<u8>(NetworkProfileType::User);
|
||||
profile.interface_type = static_cast<u8>(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<u8>(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<u8> 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<u32*>(base + 0x0) = host.kind == HostAdapterKind::Wifi ? 1u : 2u;
|
||||
*reinterpret_cast<u32*>(base + 0x4) = 1u;
|
||||
|
||||
*reinterpret_cast<in_addr*>(base + 0x18) = host.ip_address;
|
||||
*reinterpret_cast<in_addr*>(base + 0x1C) = host.subnet_mask;
|
||||
*reinterpret_cast<in_addr*>(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<u32>(static_cast<u32>(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<u32>(1);
|
||||
|
||||
std::vector<u128> 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<char, 0x40> net_name{};
|
||||
const size_t ssid_len = std::min<size_t>(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<u8>(std::min<size_t>(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<u8>(NetworkProfileType::User),
|
||||
.interface_type =
|
||||
static_cast<u8>(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<INetworkProfile>(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<const NifmNetworkProfileData*>(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<std::size_t>(scans.size(), max_rows);
|
||||
|
||||
|
||||
std::vector<AccessPointDataV3> rows;
|
||||
rows.resize(rows_copy);
|
||||
|
||||
auto to_bars = [](u8 q) {
|
||||
return static_cast<u8>((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<u32>(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<std::size_t>(scans.size(), max_rows);
|
||||
|
||||
std::vector<AccessPointDataV3> rows;
|
||||
rows.resize(rows_copy);
|
||||
|
||||
auto to_bars = [](u8 q) {
|
||||
return static_cast<u8>((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<u32>(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<IpConfigInfo>(ip_config_info);
|
||||
rb.PushRaw(info);
|
||||
}
|
||||
|
||||
void IGeneralService::SetWirelessCommunicationEnabled(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u8 enable = rp.Pop<u8>();
|
||||
|
||||
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<u8>(1);
|
||||
rb.Push<u8>(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<u8>(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<u8>();
|
||||
|
||||
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<char, 0x20> ssid{};
|
||||
u8 unknown_1{};
|
||||
u8 unknown_2{};
|
||||
u8 unknown_3{};
|
||||
std::array<char, 0x41> 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<u8>(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<u8>(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+
|
||||
|
|
|
@ -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<IGeneralService> {
|
||||
|
@ -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
|
||||
|
|
|
@ -63,9 +63,10 @@ Result IDynamicRightsInterface::VerifyActivatedRightsOwners(u64 rights_handle) {
|
|||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IDynamicRightsInterface::HasAccountRestrictedRightsInRunningApplications(Out<u8> out_bool) {
|
||||
LOG_WARNING(Service_NS, "(STUBBED) called");
|
||||
*out_bool = 0;
|
||||
Result IDynamicRightsInterface::HasAccountRestrictedRightsInRunningApplications(
|
||||
Out<u32> out_status, u64 rights_handle) {
|
||||
LOG_WARNING(Service_NS, "(STUBBED) called, rights_handle={:#x}", rights_handle);
|
||||
*out_status = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,8 @@ private:
|
|||
Result NotifyApplicationRightsCheckStart();
|
||||
Result GetRunningApplicationStatus(Out<u32> out_status, u64 rights_handle);
|
||||
Result VerifyActivatedRightsOwners(u64 rights_handle);
|
||||
Result HasAccountRestrictedRightsInRunningApplications(Out<u8> out_bool);
|
||||
Result HasAccountRestrictedRightsInRunningApplications(Out<u32> out_status,
|
||||
u64 rights_handle);
|
||||
};
|
||||
|
||||
} // namespace Service::NS
|
||||
|
|
|
@ -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 <array>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
@ -22,6 +22,7 @@
|
|||
#include "core/internal_network/socket_proxy.h"
|
||||
#include "core/internal_network/sockets.h"
|
||||
#include "network/network.h"
|
||||
#include <common/settings.h>
|
||||
|
||||
using Common::Expected;
|
||||
using Common::Unexpected;
|
||||
|
@ -489,6 +490,12 @@ void BSD::ExecuteWork(HLERequestContext& ctx, Work work) {
|
|||
}
|
||||
|
||||
std::pair<s32, Errno> 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)) {
|
||||
|
|
101
src/core/internal_network/emu_net_state.cpp
Normal file
101
src/core/internal_network/emu_net_state.cpp
Normal file
|
@ -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 <windows.h>
|
||||
#include <wlanapi.h>
|
||||
#pragma comment(lib, "wlanapi.lib")
|
||||
#endif
|
||||
#include <common/settings.h>
|
||||
|
||||
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<PVOID*>(&attr),
|
||||
&opType) == ERROR_SUCCESS) {
|
||||
|
||||
const DOT11_SSID& ssid = attr->wlanAssociationAttributes.dot11Ssid;
|
||||
const size_t len = std::min<size_t>(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<u8>(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
|
37
src/core/internal_network/emu_net_state.h
Normal file
37
src/core/internal_network/emu_net_state.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
#include <atomic>
|
||||
#include <common/socket_types.h>
|
||||
|
||||
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<bool> wifi_enabled{true};
|
||||
std::atomic<bool> 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
|
|
@ -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<NetworkInterface> GetAvailableNetworkInterfaces() {
|
||||
std::vector<IP_ADAPTER_ADDRESSES> 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<u8> buffer(buf_size, 0);
|
||||
auto* addrs = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(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<NetworkInterface> 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<sockaddr_in*>(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<struct sockaddr_in>(
|
||||
*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<sockaddr_in*>(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<struct sockaddr_in>(
|
||||
*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<NetworkInterface> GetAvailableNetworkInterfaces() {
|
|||
#endif // _WIN32
|
||||
|
||||
std::optional<NetworkInterface> GetSelectedNetworkInterface() {
|
||||
|
||||
const auto& selected_network_interface = Settings::values.network_interface.GetValue();
|
||||
const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
|
||||
if (network_interfaces.empty()) {
|
||||
|
|
|
@ -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<NetworkInterface> GetAvailableNetworkInterfaces();
|
||||
std::optional<NetworkInterface> GetSelectedNetworkInterface();
|
||||
void SelectFirstNetworkInterface();
|
||||
|
||||
struct HostAdapter {
|
||||
in_addr ip_address;
|
||||
in_addr subnet_mask;
|
||||
in_addr gateway;
|
||||
std::string name;
|
||||
HostAdapterKind kind;
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
|
|
186
src/core/internal_network/wifi_scanner.cpp
Normal file
186
src/core/internal_network/wifi_scanner.cpp
Normal file
|
@ -0,0 +1,186 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/internal_network/wifi_scanner.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace Network {
|
||||
|
||||
#ifdef _WIN32
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
#include <wlanapi.h>
|
||||
#pragma comment(lib, "wlanapi.lib")
|
||||
|
||||
static u8 QualityToPercent(DWORD q) {
|
||||
return static_cast<u8>(q);
|
||||
}
|
||||
|
||||
static std::vector<ScanData> ScanWifiWin(std::chrono::milliseconds deadline) {
|
||||
std::vector<ScanData> 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<u8>(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 <iwlib.h>
|
||||
|
||||
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<u8>(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<ScanData> ScanWifiLinux(std::chrono::milliseconds deadline) {
|
||||
std::vector<ScanData> 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<u8>(std::min<int>(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<ScanData> 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
|
22
src/core/internal_network/wifi_scanner.h
Normal file
22
src/core/internal_network/wifi_scanner.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
|
||||
#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<ScanData> ScanWifiNetworks(std::chrono::milliseconds deadline);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -35,6 +35,13 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="airplane_mode">
|
||||
<property name="text">
|
||||
<string>Enable Airplane Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -43,7 +50,7 @@
|
|||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
|
|
|
@ -1859,11 +1859,12 @@ bool GMainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPa
|
|||
system->GetUserChannel().clear();
|
||||
}
|
||||
|
||||
|
||||
system->SetFrontendAppletSet({
|
||||
std::make_unique<QtAmiiboSettings>(*this), // Amiibo Settings
|
||||
std::make_unique<QtAmiiboSettings>(*this), // Amiibo Settings
|
||||
(UISettings::values.controller_applet_disabled.GetValue() == true)
|
||||
? nullptr
|
||||
: std::make_unique<QtControllerSelector>(*this), // Controller Selector
|
||||
: std::make_unique<QtControllerSelector>(*this), // Controller Selector
|
||||
std::make_unique<QtErrorDisplay>(*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<QtProfileSelector>(*this), // Profile Selector
|
||||
std::make_unique<QtSoftwareKeyboard>(*this), // Software Keyboard
|
||||
std::make_unique<QtWebBrowser>(*this), // Web Browser
|
||||
nullptr, // Net Connect
|
||||
});
|
||||
|
||||
/** Game Updates check */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue