mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-04-21 16:48:58 +02: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
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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue