import type { RobotHotspotAdapter, RobotHotspotAdapterCandidate } from './adapter'; import { RobotHotspotAdapterError, abortableDelay, throwIfAborted } from './adapter'; const ERROR_SUCCESS = 0; const ERROR_ACCESS_DENIED = 5; const WLAN_CONNECTION_MODE_DISCOVERY_UNSECURE = 3; const DOT11_BSS_TYPE_INFRASTRUCTURE = 1; const WLAN_INTERFACE_STATE_CONNECTED = 1; const WLAN_INTF_OPCODE_CURRENT_CONNECTION = 7; interface WindowsNativeKey { interfaceGuid: Record; ssid: { uSSIDLength: number; ucSSID: Uint8Array | number[] }; } function nativeFailure(result: number, operation: 'scan' | 'connect'): never { if (result === ERROR_ACCESS_DENIED) throw new RobotHotspotAdapterError('permission_denied'); throw new RobotHotspotAdapterError(operation === 'scan' ? 'scan_failed' : 'connect_failed'); } function bytesToSsid(value: { uSSIDLength: number; ucSSID: Uint8Array | number[] }): string { return Buffer.from(value.ucSSID).subarray(0, value.uSSIDLength).toString('utf8'); } function guidKey(value: Record): string { return JSON.stringify(value); } export async function createWindowsRobotHotspotAdapter(): Promise { if (process.platform !== 'win32') throw new RobotHotspotAdapterError('scan_failed'); const koffiModule = await import('koffi'); const koffi = (koffiModule as typeof koffiModule & { default: typeof koffiModule }).default; const wlan = koffi.load('wlanapi.dll'); const GUID = koffi.struct({ Data1: 'uint32_t', Data2: 'uint16_t', Data3: 'uint16_t', Data4: koffi.array('uint8_t', 8), }); const DOT11_SSID = koffi.struct({ uSSIDLength: 'uint32_t', ucSSID: koffi.array('uint8_t', 32) }); const WLAN_INTERFACE_INFO = koffi.struct({ InterfaceGuid: GUID, strInterfaceDescription: koffi.array('char16_t', 256, 'String'), isState: 'int32_t', }); const WLAN_AVAILABLE_NETWORK = koffi.struct({ strProfileName: koffi.array('char16_t', 256, 'String'), dot11Ssid: DOT11_SSID, dot11BssType: 'int32_t', uNumberOfBssids: 'uint32_t', bNetworkConnectable: 'int32_t', wlanNotConnectableReason: 'uint32_t', uNumberOfPhyTypes: 'uint32_t', dot11PhyTypes: koffi.array('int32_t', 8), bMorePhyTypes: 'int32_t', wlanSignalQuality: 'uint32_t', bSecurityEnabled: 'int32_t', dot11DefaultAuthAlgorithm: 'uint32_t', dot11DefaultCipherAlgorithm: 'uint32_t', dwFlags: 'uint32_t', dwReserved: 'uint32_t', }); const WLAN_ASSOCIATION_ATTRIBUTES = koffi.struct({ dot11Ssid: DOT11_SSID, dot11BssType: 'int32_t', dot11Bssid: koffi.array('uint8_t', 6), dot11PhyType: 'int32_t', uDot11PhyIndex: 'uint32_t', wlanSignalQuality: 'uint32_t', ulRxRate: 'uint32_t', ulTxRate: 'uint32_t', }); const WLAN_SECURITY_ATTRIBUTES = koffi.struct({ bSecurityEnabled: 'int32_t', bOneXEnabled: 'int32_t', dot11AuthAlgorithm: 'uint32_t', dot11CipherAlgorithm: 'uint32_t', }); const WLAN_CONNECTION_ATTRIBUTES = koffi.struct({ isState: 'int32_t', wlanConnectionMode: 'int32_t', strProfileName: koffi.array('char16_t', 256, 'String'), wlanAssociationAttributes: WLAN_ASSOCIATION_ATTRIBUTES, wlanSecurityAttributes: WLAN_SECURITY_ATTRIBUTES, }); const WLAN_CONNECTION_PARAMETERS = koffi.struct({ wlanConnectionMode: 'int32_t', strProfile: 'str16', pDot11Ssid: koffi.pointer(DOT11_SSID), pDesiredBssidList: 'void *', dot11BssType: 'int32_t', dwFlags: 'uint32_t', }); const WlanOpenHandle = wlan.func('WlanOpenHandle', 'uint32_t', ['uint32_t', 'void *', koffi.out(koffi.pointer('uint32_t')), koffi.out(koffi.pointer('void *'))]); const WlanCloseHandle = wlan.func('WlanCloseHandle', 'uint32_t', ['void *', 'void *']); const WlanEnumInterfaces = wlan.func('WlanEnumInterfaces', 'uint32_t', ['void *', 'void *', koffi.out(koffi.pointer('void *'))]); const WlanScan = wlan.func('WlanScan', 'uint32_t', ['void *', koffi.pointer(GUID), 'void *', 'void *', 'void *']); const WlanGetAvailableNetworkList = wlan.func('WlanGetAvailableNetworkList', 'uint32_t', ['void *', koffi.pointer(GUID), 'uint32_t', 'void *', koffi.out(koffi.pointer('void *'))]); const WlanConnect = wlan.func('WlanConnect', 'uint32_t', ['void *', koffi.pointer(GUID), koffi.pointer(WLAN_CONNECTION_PARAMETERS), 'void *']); const WlanQueryInterface = wlan.func('WlanQueryInterface', 'uint32_t', ['void *', koffi.pointer(GUID), 'int32_t', 'void *', koffi.out(koffi.pointer('uint32_t')), koffi.out(koffi.pointer('void *')), koffi.out(koffi.pointer('int32_t'))]); const WlanFreeMemory = wlan.func('WlanFreeMemory', 'void', ['void *']); let connectingInterfaceGuid: Record | null = null; async function withHandle(operation: 'scan' | 'connect', body: (handle: unknown) => Promise): Promise { const negotiated = [0]; const handle = [null] as unknown[]; const result = WlanOpenHandle(2, null, negotiated, handle) as number; if (result !== ERROR_SUCCESS || !handle[0]) nativeFailure(result, operation); try { return await body(handle[0]); } finally { WlanCloseHandle(handle[0], null); } } function interfaces(handle: unknown, operation: 'scan' | 'connect'): Array> { const output = [null] as unknown[]; const result = WlanEnumInterfaces(handle, null, output) as number; if (result !== ERROR_SUCCESS || !output[0]) nativeFailure(result, operation); try { const count = koffi.decode(output[0], 'uint32_t') as number; const offset = 8; return Array.from({ length: Math.min(count, 64) }, (_, index) => koffi.decode(output[0], offset + index * koffi.sizeof(WLAN_INTERFACE_INFO), WLAN_INTERFACE_INFO) as Record); } finally { WlanFreeMemory(output[0]); } } function currentOnInterface(handle: unknown, interfaceGuid: Record, operation: 'scan' | 'connect'): string | null { const size = [0]; const data = [null] as unknown[]; const valueType = [0]; const result = WlanQueryInterface(handle, interfaceGuid, WLAN_INTF_OPCODE_CURRENT_CONNECTION, null, size, data, valueType) as number; if (result !== ERROR_SUCCESS || !data[0]) { if (result === ERROR_ACCESS_DENIED) nativeFailure(result, operation); return null; } try { const connection = koffi.decode(data[0], WLAN_CONNECTION_ATTRIBUTES) as { isState: number; wlanAssociationAttributes: { dot11Ssid: WindowsNativeKey['ssid'] } }; return connection.isState === WLAN_INTERFACE_STATE_CONNECTED ? bytesToSsid(connection.wlanAssociationAttributes.dot11Ssid) : null; } finally { WlanFreeMemory(data[0]); } } return { async scan(signal) { return withHandle('scan', async (handle) => { const foundInterfaces = interfaces(handle, 'scan'); for (const item of foundInterfaces) { throwIfAborted(signal); const result = WlanScan(handle, item.InterfaceGuid, null, null, null) as number; if (result !== ERROR_SUCCESS) nativeFailure(result, 'scan'); } // WlanScan is asynchronous and usually needs about four seconds before the // available-network cache represents the requested scan. await abortableDelay(4_000, signal); const candidates: RobotHotspotAdapterCandidate[] = []; for (const item of foundInterfaces) { throwIfAborted(signal); const output = [null] as unknown[]; const result = WlanGetAvailableNetworkList(handle, item.InterfaceGuid, 0, null, output) as number; if (result !== ERROR_SUCCESS || !output[0]) nativeFailure(result, 'scan'); try { const count = koffi.decode(output[0], 'uint32_t') as number; const connectedSsid = currentOnInterface(handle, item.InterfaceGuid as Record, 'scan'); for (let index = 0; index < Math.min(count, 512); index += 1) { const network = koffi.decode(output[0], 8 + index * koffi.sizeof(WLAN_AVAILABLE_NETWORK), WLAN_AVAILABLE_NETWORK) as { dot11Ssid: WindowsNativeKey['ssid']; dot11BssType: number; wlanSignalQuality: number; bSecurityEnabled: number; bNetworkConnectable: number; }; if (network.dot11BssType !== DOT11_BSS_TYPE_INFRASTRUCTURE || network.bSecurityEnabled || !network.bNetworkConnectable) continue; const ssid = bytesToSsid(network.dot11Ssid); candidates.push({ ssid, signalPercent: network.wlanSignalQuality, connected: connectedSsid === ssid, open: true, nativeKey: { interfaceGuid: item.InterfaceGuid, ssid: network.dot11Ssid } satisfies WindowsNativeKey, }); } } finally { WlanFreeMemory(output[0]); } } return candidates; }); }, async connect(candidate, signal) { throwIfAborted(signal); const key = candidate.nativeKey as WindowsNativeKey; await withHandle('connect', async (handle) => { const match = interfaces(handle, 'connect').find((item) => guidKey(item.InterfaceGuid as Record) === guidKey(key.interfaceGuid)); if (!match) throw new RobotHotspotAdapterError('connect_failed'); const parameters = { wlanConnectionMode: WLAN_CONNECTION_MODE_DISCOVERY_UNSECURE, strProfile: null, pDot11Ssid: key.ssid, pDesiredBssidList: null, dot11BssType: DOT11_BSS_TYPE_INFRASTRUCTURE, dwFlags: 0, }; const result = WlanConnect(handle, match.InterfaceGuid, parameters, null) as number; if (result !== ERROR_SUCCESS) nativeFailure(result, 'connect'); connectingInterfaceGuid = match.InterfaceGuid as Record; }); }, async currentSsid(signal) { throwIfAborted(signal); return withHandle('connect', async (handle) => { const foundInterfaces = interfaces(handle, 'connect'); const orderedInterfaces = connectingInterfaceGuid ? [...foundInterfaces].sort((left, right) => Number(guidKey(right.InterfaceGuid as Record) === guidKey(connectingInterfaceGuid as Record)) - Number(guidKey(left.InterfaceGuid as Record) === guidKey(connectingInterfaceGuid as Record))) : foundInterfaces; for (const item of orderedInterfaces) { const ssid = currentOnInterface(handle, item.InterfaceGuid as Record, 'connect'); if (ssid) return ssid; } return null; }); }, clear() { connectingInterfaceGuid = null; }, }; }