2026-04-28 16:35:51 +08:00
|
|
|
|
#import <Foundation/Foundation.h>
|
|
|
|
|
|
#import <SystemConfiguration/CaptiveNetwork.h>
|
2026-07-06 16:49:18 +08:00
|
|
|
|
#import <NetworkExtension/NetworkExtension.h>
|
|
|
|
|
|
#import <CoreLocation/CoreLocation.h>
|
2026-08-07 16:14:10 +08:00
|
|
|
|
#import <string.h>
|
2026-07-06 16:49:18 +08:00
|
|
|
|
|
|
|
|
|
|
static CLLocationManager *locationManager = nil;
|
2026-04-28 16:35:51 +08:00
|
|
|
|
|
2026-08-07 16:14:10 +08:00
|
|
|
|
// UnitySendMessage 声明
|
|
|
|
|
|
extern void UnitySendMessage(const char *obj, const char *method, const char *msg);
|
|
|
|
|
|
|
|
|
|
|
|
// 授权变化回调目标(通过 UnitySendMessage 通知 C#)
|
|
|
|
|
|
static const char *callbackGameObject = nil;
|
|
|
|
|
|
static const char *callbackMethod = nil;
|
|
|
|
|
|
|
|
|
|
|
|
// GetWiFiSSID 前向声明(定义在 extern "C" 块中)
|
|
|
|
|
|
extern "C" const char* GetWiFiSSID();
|
|
|
|
|
|
|
|
|
|
|
|
// 是否正在等待定位更新(收到第一个位置后立即获取 SSID)
|
|
|
|
|
|
static BOOL pendingSSIDFetch = NO;
|
|
|
|
|
|
|
|
|
|
|
|
// 延迟获取 SSID 并回调 C#
|
|
|
|
|
|
static void FetchSSIDAndCallback() {
|
|
|
|
|
|
char *ssid = (char *)GetWiFiSSID();
|
|
|
|
|
|
if (callbackGameObject != nil && callbackMethod != nil) {
|
|
|
|
|
|
if (ssid != NULL) {
|
|
|
|
|
|
UnitySendMessage(callbackGameObject, callbackMethod, ssid);
|
|
|
|
|
|
free(ssid);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
UnitySendMessage(callbackGameObject, callbackMethod, "");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@interface WiFiHelperLocationDelegate : NSObject <CLLocationManagerDelegate>
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
@implementation WiFiHelperLocationDelegate
|
|
|
|
|
|
- (void)locationManagerDidChangeAuthorization:(CLLocationManager *)manager {
|
|
|
|
|
|
if (@available(iOS 14.0, *)) {
|
|
|
|
|
|
// iOS 14+ 新回调:参数是 manager,需自行查询授权状态
|
|
|
|
|
|
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
|
|
|
|
|
|
if (status == kCLAuthorizationStatusAuthorizedWhenInUse ||
|
|
|
|
|
|
status == kCLAuthorizationStatusAuthorizedAlways) {
|
|
|
|
|
|
NSLog(@"[WiFiHelper] Location permission granted after request");
|
|
|
|
|
|
[WiFiHelperLocationDelegate startSSIDFetchFlow];
|
|
|
|
|
|
} else if (status == kCLAuthorizationStatusDenied ||
|
|
|
|
|
|
status == kCLAuthorizationStatusRestricted) {
|
|
|
|
|
|
NSLog(@"[WiFiHelper] Location permission denied after request");
|
|
|
|
|
|
if (callbackGameObject != nil && callbackMethod != nil) {
|
|
|
|
|
|
UnitySendMessage(callbackGameObject, callbackMethod, "");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
|
|
|
|
|
|
if (@available(iOS 14.0, *)) return; // iOS 14+ 由上面的新回调处理
|
|
|
|
|
|
// iOS 13 及以下用旧回调方法
|
|
|
|
|
|
if (status == kCLAuthorizationStatusAuthorizedWhenInUse ||
|
|
|
|
|
|
status == kCLAuthorizationStatusAuthorizedAlways) {
|
|
|
|
|
|
NSLog(@"[WiFiHelper] Location permission granted after request (legacy callback)");
|
|
|
|
|
|
[WiFiHelperLocationDelegate startSSIDFetchFlow];
|
|
|
|
|
|
} else if (status == kCLAuthorizationStatusDenied ||
|
|
|
|
|
|
status == kCLAuthorizationStatusRestricted) {
|
|
|
|
|
|
NSLog(@"[WiFiHelper] Location permission denied after request (legacy callback)");
|
|
|
|
|
|
if (callbackGameObject != nil && callbackMethod != nil) {
|
|
|
|
|
|
UnitySendMessage(callbackGameObject, callbackMethod, "");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 收到第一个位置更新后再获取 SSID(NEHotspotNetwork.fetchCurrent 的隐式要求)
|
|
|
|
|
|
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
|
|
|
|
|
|
if (pendingSSIDFetch) {
|
|
|
|
|
|
NSLog(@"[WiFiHelper] First location update received, fetching SSID now");
|
|
|
|
|
|
pendingSSIDFetch = NO;
|
|
|
|
|
|
[manager stopUpdatingLocation];
|
|
|
|
|
|
FetchSSIDAndCallback();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 定位失败时也兜底尝试获取一次
|
|
|
|
|
|
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
|
|
|
|
|
|
NSLog(@"[WiFiHelper] Location manager failed: %@", error.localizedDescription);
|
|
|
|
|
|
if (pendingSSIDFetch) {
|
|
|
|
|
|
pendingSSIDFetch = NO;
|
|
|
|
|
|
[manager stopUpdatingLocation];
|
|
|
|
|
|
FetchSSIDAndCallback();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 启动定位并等待第一个位置更新,超时则直接获取
|
|
|
|
|
|
+ (void)startSSIDFetchFlow {
|
|
|
|
|
|
if (![CLLocationManager locationServicesEnabled]) {
|
|
|
|
|
|
NSLog(@"[WiFiHelper] Location services disabled on device, fetching SSID anyway");
|
|
|
|
|
|
FetchSSIDAndCallback();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (locationManager == nil) {
|
|
|
|
|
|
locationManager = [[CLLocationManager alloc] init];
|
|
|
|
|
|
}
|
|
|
|
|
|
pendingSSIDFetch = YES;
|
|
|
|
|
|
[locationManager startUpdatingLocation];
|
|
|
|
|
|
// 超时兜底:定位迟迟无响应(如首次定位慢)时,3 秒后直接获取
|
|
|
|
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
|
|
|
|
|
if (pendingSSIDFetch) {
|
|
|
|
|
|
NSLog(@"[WiFiHelper] Timeout waiting for location update, fetching SSID anyway");
|
|
|
|
|
|
pendingSSIDFetch = NO;
|
|
|
|
|
|
[locationManager stopUpdatingLocation];
|
|
|
|
|
|
FetchSSIDAndCallback();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
static WiFiHelperLocationDelegate *locationDelegate = nil;
|
|
|
|
|
|
|
2026-04-28 16:35:51 +08:00
|
|
|
|
extern "C" {
|
2026-08-07 16:14:10 +08:00
|
|
|
|
|
2026-07-06 16:49:18 +08:00
|
|
|
|
void RequestLocationPermission() {
|
|
|
|
|
|
if (locationManager != nil) return;
|
|
|
|
|
|
|
|
|
|
|
|
locationManager = [[CLLocationManager alloc] init];
|
|
|
|
|
|
|
|
|
|
|
|
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
|
|
|
|
|
|
if (status == kCLAuthorizationStatusAuthorizedWhenInUse ||
|
|
|
|
|
|
status == kCLAuthorizationStatusAuthorizedAlways) {
|
|
|
|
|
|
NSLog(@"[WiFiHelper] Location permission already granted (status: %d)", (int)status);
|
|
|
|
|
|
// 位置权限已获得但还需要启动定位才能让WiFi API工作
|
|
|
|
|
|
[locationManager startUpdatingLocation];
|
|
|
|
|
|
// 短暂启动后停止(不需要持续定位,只是激活权限链)
|
|
|
|
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
|
|
|
|
|
[locationManager stopUpdatingLocation];
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (status == kCLAuthorizationStatusNotDetermined) {
|
|
|
|
|
|
NSLog(@"[WiFiHelper] Requesting WhenInUse location permission");
|
|
|
|
|
|
[locationManager requestWhenInUseAuthorization];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
NSLog(@"[WiFiHelper] Location permission denied/restricted (status: %d)", (int)status);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-07 16:14:10 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 请求位置权限(如未授权),授权完成后自动获取 WiFi 名称并回调 C#。
|
|
|
|
|
|
/// 已授权时立即获取并回调。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
void RequestWiFiSSIDNative(const char *gameObjectName, const char *methodName) {
|
|
|
|
|
|
if (callbackGameObject != nil) {
|
|
|
|
|
|
free((void *)callbackGameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (callbackMethod != nil) {
|
|
|
|
|
|
free((void *)callbackMethod);
|
|
|
|
|
|
}
|
|
|
|
|
|
callbackGameObject = gameObjectName ? strdup(gameObjectName) : nil;
|
|
|
|
|
|
callbackMethod = methodName ? strdup(methodName) : nil;
|
|
|
|
|
|
|
|
|
|
|
|
if (locationManager == nil) {
|
|
|
|
|
|
locationManager = [[CLLocationManager alloc] init];
|
|
|
|
|
|
}
|
|
|
|
|
|
if (locationDelegate == nil) {
|
|
|
|
|
|
locationDelegate = [[WiFiHelperLocationDelegate alloc] init];
|
|
|
|
|
|
}
|
|
|
|
|
|
locationManager.delegate = locationDelegate;
|
|
|
|
|
|
|
|
|
|
|
|
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
|
|
|
|
|
|
if (status == kCLAuthorizationStatusAuthorizedWhenInUse ||
|
|
|
|
|
|
status == kCLAuthorizationStatusAuthorizedAlways) {
|
|
|
|
|
|
NSLog(@"[WiFiHelper] Location already granted, fetching SSID directly");
|
|
|
|
|
|
[WiFiHelperLocationDelegate startSSIDFetchFlow];
|
|
|
|
|
|
return;
|
2026-07-06 16:49:18 +08:00
|
|
|
|
}
|
2026-04-28 16:35:51 +08:00
|
|
|
|
|
2026-08-07 16:14:10 +08:00
|
|
|
|
if (status == kCLAuthorizationStatusNotDetermined) {
|
|
|
|
|
|
NSLog(@"[WiFiHelper] Requesting location permission, will fetch SSID on grant");
|
|
|
|
|
|
[locationManager requestWhenInUseAuthorization];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
NSLog(@"[WiFiHelper] Location permission denied/restricted (status: %d)", (int)status);
|
|
|
|
|
|
if (callbackGameObject != nil && callbackMethod != nil) {
|
|
|
|
|
|
UnitySendMessage(callbackGameObject, callbackMethod, "");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const char* GetWiFiSSID() {
|
|
|
|
|
|
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
|
|
|
|
|
|
BOOL locationEnabled = [CLLocationManager locationServicesEnabled];
|
|
|
|
|
|
NSLog(@"[WiFiHelper] GetWiFiSSID: auth=%d servicesEnabled=%d", (int)status, (int)locationEnabled);
|
|
|
|
|
|
|
2026-07-06 16:49:18 +08:00
|
|
|
|
__block NSString *resultSSID = nil;
|
|
|
|
|
|
__block BOOL finished = NO;
|
2026-04-28 16:35:51 +08:00
|
|
|
|
|
2026-07-06 16:49:18 +08:00
|
|
|
|
if (@available(iOS 14.0, *)) {
|
|
|
|
|
|
NSLog(@"[WiFiHelper] Using NEHotspotNetwork.fetchCurrent");
|
|
|
|
|
|
|
|
|
|
|
|
[NEHotspotNetwork fetchCurrentWithCompletionHandler:^(NEHotspotNetwork * _Nullable network) {
|
|
|
|
|
|
if (network && network.SSID.length > 0) {
|
|
|
|
|
|
resultSSID = network.SSID;
|
2026-08-07 16:14:10 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
NSLog(@"[WiFiHelper] NEHotspotNetwork fetchCurrent returned nil");
|
2026-07-06 16:49:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
finished = YES;
|
|
|
|
|
|
}];
|
2026-04-28 16:35:51 +08:00
|
|
|
|
|
2026-07-06 16:49:18 +08:00
|
|
|
|
// 用 CFRunLoop 等待,不阻塞主线程事件处理
|
|
|
|
|
|
NSTimeInterval deadline = [[NSDate date] timeIntervalSinceReferenceDate] + 3.0;
|
|
|
|
|
|
while (!finished && [[NSDate date] timeIntervalSinceReferenceDate] < deadline) {
|
|
|
|
|
|
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
|
2026-04-28 16:35:51 +08:00
|
|
|
|
}
|
2026-07-06 16:49:18 +08:00
|
|
|
|
|
|
|
|
|
|
if (resultSSID != nil) {
|
|
|
|
|
|
NSLog(@"[WiFiHelper] NEHotspotNetwork SSID: %@", resultSSID);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
NSLog(@"[WiFiHelper] NEHotspotNetwork returned nil (finished=%d)", finished);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Fallback
|
|
|
|
|
|
if (resultSSID == nil) {
|
|
|
|
|
|
NSLog(@"[WiFiHelper] Fallback to CNCopyCurrentNetworkInfo");
|
|
|
|
|
|
NSArray *interfaces = (__bridge_transfer NSArray *)CNCopySupportedInterfaces();
|
|
|
|
|
|
if (interfaces != nil) {
|
|
|
|
|
|
for (NSString *interfaceName in interfaces) {
|
|
|
|
|
|
NSDictionary *info = (__bridge_transfer NSDictionary *)CNCopyCurrentNetworkInfo((__bridge CFStringRef)interfaceName);
|
|
|
|
|
|
if (info && info[(NSString *)kCNNetworkInfoKeySSID]) {
|
|
|
|
|
|
resultSSID = info[(NSString *)kCNNetworkInfoKeySSID];
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (resultSSID == nil) {
|
|
|
|
|
|
NSLog(@"[WiFiHelper] No WiFi SSID found.");
|
2026-04-28 16:35:51 +08:00
|
|
|
|
return NULL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-06 16:49:18 +08:00
|
|
|
|
NSLog(@"[WiFiHelper] SSID: %@", resultSSID);
|
|
|
|
|
|
|
|
|
|
|
|
const char *utf8String = [resultSSID UTF8String];
|
|
|
|
|
|
size_t len = strlen(utf8String) + 1;
|
|
|
|
|
|
char *result = (char *)malloc(len);
|
|
|
|
|
|
if (result == NULL) return NULL;
|
|
|
|
|
|
memcpy(result, utf8String, len);
|
2026-04-28 16:35:51 +08:00
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|