//
//  Tweak.x
//  主入口文件 - 包含所有 Hooks
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <WebKit/WebKit.h>
#import <CoreMotion/CoreMotion.h>
#import <CoreTelephony/CTCarrier.h>
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#import <SystemConfiguration/SystemConfiguration.h>
#import <AppTrackingTransparency/AppTrackingTransparency.h>
#import <AVFoundation/AVFoundation.h>
#import <sys/utsname.h>
#include <sys/sysctl.h>
#import <ifaddrs.h>
#import <arpa/inet.h>
#import <net/if.h>
#import <objc/runtime.h>
#import "XSLog.h"
#import "XHookSettingsManager.h"

// 设置日志
void SetupXSLog() {
    XSLogSetupLogWithFilePath(@"/var/mobile/Documents/App.log", 50 * 1024 * 1024);
    XSLogRedirectNSLog();
}

#pragma mark - ============== HOOKS GROUP ==============

%group XHooks

#pragma mark - ATTrackingManager Hook

%hook ATTrackingManager

+ (ATTrackingManagerAuthorizationStatus)trackingAuthorizationStatus {
    ATTrackingManagerAuthorizationStatus originalStatus = %orig;
    NSUInteger status = [XHookSettings trackingStatus];
    return (ATTrackingManagerAuthorizationStatus)status;
}

+ (void)requestTrackingAuthorizationWithCompletionHandler:(void (^)(ATTrackingManagerAuthorizationStatus status))completion {
    ATTrackingManagerAuthorizationStatus attStatus = (ATTrackingManagerAuthorizationStatus)[XHookSettings trackingStatus];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        if (completion) {
            completion(attStatus);
        }
    });
}

%end

#pragma mark - ASIdentifierManager Hook

%hook ASIdentifierManager

- (NSUUID *)advertisingIdentifier {
    NSUUID *idfa = [XHookSettings idfa];
    return idfa ?: %orig;
}

- (BOOL)isAdvertisingTrackingEnabled {
    NSUInteger status = [XHookSettings trackingStatus];
    return (status >= 3);
}

%end

#pragma mark - UIDevice Hook

%hook UIDevice

- (NSUUID *)identifierForVendor {
    NSUUID *idfv = [XHookSettings idfv];
    return idfv ?: %orig;
}

- (NSString *)name {
    NSString *name = [XHookSettings deviceName];
    return name ?: %orig;
}

- (NSString *)model {
    return %orig;
}

- (NSString *)localizedModel {
    return %orig;
}

- (NSString *)systemName {
    return %orig;
}

- (NSString *)systemVersion {
    NSString *version = [XHookSettings osVersion];
    return version ?: %orig;
}

- (UIDeviceBatteryState)batteryState {
    UIDeviceBatteryState state = [XHookSettings batteryState];
    return (state != UIDeviceBatteryStateUnknown) ? state : %orig;
}

- (float)batteryLevel {
    float level = [XHookSettings batteryLevel];
    return (level > -1.0f) ? level : %orig;
}

- (UIDeviceOrientation)orientation {
    return UIDeviceOrientationPortrait;
}

%end

#pragma mark - NSProcessInfo Hook

%hook NSProcessInfo

- (NSTimeInterval)systemUptime {
    NSTimeInterval res = %orig;
    double offset = [XHookSettings systemUptimeOffset];
    if (offset > -1.0) {
        res += offset;
    }
    return res;
}

- (unsigned long long)physicalMemory {
    unsigned long long memory = [XHookSettings physicalMemory];
    return (memory > 1000) ? memory : %orig;
}

- (NSUInteger)processorCount {
    NSUInteger cores = [XHookSettings cpuCore];
    return (cores > 0) ? cores : %orig;
}

- (NSOperatingSystemVersion)operatingSystemVersion {
    NSOperatingSystemVersion version = %orig;
    NSString *versionString = [XHookSettings osVersion];
    
    if (!versionString || ![versionString length]) {
        return version;
    }
    
    NSArray *components = [versionString componentsSeparatedByString:@"."];
    if (components.count == 0 || components.count > 3) {
        return version;
    }
    
    for (NSString *component in components) {
        if (![[NSScanner scannerWithString:component] scanInteger:NULL]) {
            return version;
        }
    }
    
    version.majorVersion = [components[0] integerValue];
    version.minorVersion = components.count > 1 ? [components[1] integerValue] : 0;
    version.patchVersion = components.count > 2 ? [components[2] integerValue] : 0;
    
    return version;
}

%end

#pragma mark - UIWindow Hook

%hook UIWindow

- (UIEdgeInsets)safeAreaInsets {
    UIEdgeInsets originalInsets = %orig;
    NSDictionary *dic = [XHookSettings safeAreaInsets];
    
    if (dic) {
        CGFloat top = [dic[@"top"] doubleValue];
        CGFloat bottom = [dic[@"bottom"] doubleValue];
        originalInsets = UIEdgeInsetsMake(top, 0, bottom, 0);
    }
    
    return originalInsets;
}

%end

#pragma mark - UIScreen Hook

%hook UIScreen

- (CGFloat)brightness {
    CGFloat brightness = [XHookSettings screenBrightness];
    return (brightness > -1.0) ? brightness : %orig;
}

- (CGRect)bounds {
    NSDictionary *dic = [XHookSettings screenBounds];
    if (dic) {
        double w = [dic[@"x"] doubleValue];
        double h = [dic[@"y"] doubleValue];
        return CGRectMake(0, 0, w, h);
    }
    return %orig;
}

- (CGFloat)scale {
    CGFloat scale = [XHookSettings screenScale];
    return (scale > -1.0) ? scale : %orig;
}

- (CGRect)nativeBounds {
    NSDictionary *dic = [XHookSettings screenBounds];
    if (dic) {
        double w = [dic[@"nx"] doubleValue];
        double h = [dic[@"ny"] doubleValue];
        return CGRectMake(0, 0, w, h);
    }
    return %orig;
}

- (BOOL)isCaptured {
    return NO;
}

- (UIScreen *)mirroredScreen {
    return nil;
}

%end

#pragma mark - UIScreenMode Hook

%hook UIScreenMode

- (CGSize)size {
    NSDictionary *dic = [XHookSettings screenBounds];
    if (dic) {
        double w = [dic[@"x"] doubleValue];
        double h = [dic[@"y"] doubleValue];
        return CGSizeMake(w, h);
    }
    return %orig;
}

%end

#pragma mark - UIStatusBarManager Hook

%hook UIStatusBarManager

- (CGRect)statusBarFrame {
    CGRect originalFrame = %orig;
    NSDictionary *dic = [XHookSettings safeAreaInsets];
    if (dic && dic[@"height"]) {
        double barHeight = [dic[@"height"] doubleValue];
        originalFrame.size.height = barHeight;
    }
    return originalFrame;
}

%end

#pragma mark - AVAudioSession Hook

%hook AVAudioSession

- (float)outputVolume {
    float volume = [XHookSettings outputVolume];
    return (volume > -1.0f) ? volume : %orig;
}

%end

#pragma mark - NSFileManager Hook

%hook NSFileManager

- (NSDictionary<NSFileAttributeKey, id> *)attributesOfFileSystemForPath:(NSString *)path error:(NSError **)error {
    NSDictionary<NSFileAttributeKey, id> *res = %orig;
    
    if ([path isEqualToString:NSHomeDirectory()]) {
        NSMutableDictionary *modifiedAttributes = [res mutableCopy];
        
        unsigned long long diskSize = [XHookSettings diskSize];
        if (diskSize > 0) {
            modifiedAttributes[NSFileSystemSize] = @(diskSize);
        }
        
        unsigned long long diskFreeSize = [XHookSettings diskFreeSize];
        if (diskFreeSize > 0) {
            modifiedAttributes[NSFileSystemFreeSize] = @(diskFreeSize);
        }
        
        return [modifiedAttributes copy];
    }
    
    return res;
}

- (BOOL)fileExistsAtPath:(NSString *)event {
    NSArray<NSString *> *array = @[@"/Application/Cydia.app",
                                   @"/Application/Sileo.app",
                                   @"/usr/lib/TweakInject",
                                   @"/Library/TweakInject",
                                   @"/Library/MobileSubstrate/MobileSubstrate.dylib",
                                   @"/bin/bash",
                                   @"/usr/sbin/sshd",
                                   @"/etc/apt",
                                   @"/usr/bin/ssh",
                                   @"/private/var/lib/apt",
                                   @"/private/var/lib/cydia",
                                   @"/private/var/tmp/cydia.log",
                                   @"/Applications/WinterBoard.app",
                                   @"/var/lib/cydia",
                                   @"/private/etc/dpkg/origins/debian",
                                   @"/bin.sh",
                                   @"/private/etc/apt",
                                   @"/etc/ssh/sshd_config",
                                   @"/private/etc/ssh/sshd_config",
                                   @"/Applications/SBSetttings.app",
                                   @"/private/var/mobileLibrary/SBSettingsThemes/",
                                   @"/private/var/stash",
                                   @"/usr/libexec/sftp-server",
                                   @"/usr/libexec/cydia/",
                                   @"/usr/sbin/frida-server",
                                   @"/usr/bin/cycript",
                                   @"/usr/local/bin/cycript",
                                   @"/usr/lib/libcycript.dylib",
                                   @"/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist",
                                   @"/System/Library/LaunchDaemons/com.ikey.bbot.plist",
                                   @"/Applications/FakeCarrier.app",
                                   @"/Library/MobileSubstrate/DynamicLibraries/Veency.plist",
                                   @"/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist",
                                   @"/usr/libexec/ssh-keysign",
                                   @"/usr/libexec/sftp-server",
                                   @"/Applications/blackra1n.app",
                                   @"/Applications/IntelliScreen.app",
                                   @"/Applications/Snoop-itConfig.app",
                                   @"/var/lib/dpkg/info",
                                   @"/User/Applications"];
    
    NSInteger index = [array indexOfObjectPassingTest:^BOOL(NSString *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        BOOL ret = [event hasPrefix:obj];
        if (ret) {
            *stop = YES;
        }
        return ret;
    }];
    if (index != NSNotFound) {
        return NO;
    }
    return %orig;
}

%end

#pragma mark - NSLocale Hook

%hook NSLocale

+ (NSArray *)preferredLanguages {
    return @[@"en-US"];
}

+ (NSLocale *)currentLocale {
    NSString *loc = [XHookSettings locale];
    if (loc) {
        return [[NSLocale alloc] initWithLocaleIdentifier:loc];
    }
    return %orig;
}

+ (NSLocale *)systemLocale {
    NSString *loc = [XHookSettings locale];
    if (loc) {
        return [[NSLocale alloc] initWithLocaleIdentifier:loc];
    }
    return %orig;
}

%end

#pragma mark - NSTimeZone Hook

%hook NSTimeZone

+ (NSTimeZone *)localTimeZone {
    NSString *tz = [XHookSettings timeZone];
    if (tz) {
        return [NSTimeZone timeZoneWithName:tz];
    }
    return %orig;
}

+ (NSTimeZone *)systemTimeZone {
    NSString *tz = [XHookSettings timeZone];
    if (tz) {
        return [NSTimeZone timeZoneWithName:tz];
    }
    return %orig;
}

+ (NSTimeZone *)defaultTimeZone {
    NSString *tz = [XHookSettings timeZone];
    if (tz) {
        return [NSTimeZone timeZoneWithName:tz];
    }
    return %orig;
}

%end

#pragma mark - NSMutableURLRequest Hook

%hook NSMutableURLRequest

- (instancetype)initWithURL:(NSURL *)URL {
    self = %orig(URL);
    if (self) {
        NSString *userAgent = [XHookSettings userAgent];
        if (userAgent) {
            [self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
        }
    }
    return self;
}

- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field {
    NSString *finalValue = value;
    if ([field isEqualToString:@"User-Agent"]) {
        NSString *userAgent = [XHookSettings userAgent];
        if (userAgent) {
            finalValue = userAgent;
        }
    }
    %orig(finalValue, field);
}

%end

#pragma mark - NSURLSession Hook

%hook NSURLSession

+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration {
    NSString *userAgent = [XHookSettings userAgent];
    if (userAgent) {
        configuration.HTTPAdditionalHeaders = @{@"User-Agent": userAgent};
    }
    return %orig;
}

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request 
                           completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler {
    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    NSString *userAgent = [XHookSettings userAgent];
    if (userAgent) {
        [mutableRequest setValue:userAgent forHTTPHeaderField:@"User-Agent"];
    }
    return %orig(mutableRequest, completionHandler);
}

%end

#pragma mark - WKWebView Hook

%hook WKWebView

- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *error))completionHandler {
    NSString *userAgent = [XHookSettings userAgent];
    if (userAgent) {
        [self setCustomUserAgent:userAgent];
    }
    %orig(javaScriptString, completionHandler);
}

- (void)setCustomUserAgent:(NSString *)userAgent {
    NSString *customUA = [XHookSettings userAgent];
    %orig(customUA ?: userAgent);
}

- (NSString *)customUserAgent {
    NSString *userAgent = [XHookSettings userAgent];
    return userAgent ?: %orig;
}

%end

#pragma mark - CTTelephonyNetworkInfo Hook

%hook CTTelephonyNetworkInfo

- (NSDictionary<NSString *, NSString *> *)serviceCurrentRadioAccessTechnology {
    NSDictionary<NSString *, NSString *> *originalTechnologies = %orig;
    NSDictionary *simInfoDic = [XHookSettings simInfo];
    
    if (!simInfoDic) {
        return originalTechnologies;
    }
    
    NSMutableDictionary<NSString *, NSString *> *modifiedProviders = [[NSMutableDictionary alloc] init];
    
    for (NSString *service in simInfoDic.allKeys) {
        NSDictionary *dic = simInfoDic[service];
        NSString *wwan = dic[@"wwan"];
        if ([wwan isEqual:@"5G"]) {
            modifiedProviders[service] = @"CTRadioAccessTechnologyNR";
        } else {
            modifiedProviders[service] = @"CTRadioAccessTechnologyLTE";
        }
    }
    
    return [modifiedProviders count] > 0 ? [modifiedProviders copy] : originalTechnologies;
}

- (NSDictionary<NSString *, CTCarrier *> *)serviceSubscriberCellularProviders {
    NSDictionary<NSString *, CTCarrier *> *originalProviders = %orig;
    NSDictionary *simInfoDic = [XHookSettings simInfo];
    
    if (!simInfoDic) {
        return originalProviders;
    }
    
    NSMutableDictionary<NSString *, CTCarrier *> *modifiedProviders = [[NSMutableDictionary alloc] init];
    
    for (NSString *service in simInfoDic.allKeys) {
        NSDictionary *dic = simInfoDic[service];
        if (!dic) continue;
        
        if (dic[@"id"] && ![dic[@"id"] isEqual:[NSNull null]]) {
            [[NSUserDefaults standardUserDefaults] setValue:dic[@"id"] forKey:@"XHook-carrier-id"];
        }
        
        CTCarrier *carrier = [[CTCarrier alloc] init];
        [carrier setValue:dic[@"carrierName"] forKey:@"carrierName"];
        [carrier setValue:dic[@"mobileCountryCode"] forKey:@"mobileCountryCode"];
        [carrier setValue:dic[@"mobileNetworkCode"] forKey:@"mobileNetworkCode"];
        [carrier setValue:dic[@"isoCountryCode"] forKey:@"isoCountryCode"];
        modifiedProviders[service] = carrier;
    }
    
    return [modifiedProviders count] > 0 ? [modifiedProviders copy] : originalProviders;
}

%end

#pragma mark - CMMotionManager Hook (陀螺仪)

static double randomRadiansPerSecond() {
    double randomValue = (double)arc4random() / UINT32_MAX;
    return (randomValue * 20.0) - 10.0;
}

@interface CustomCMGyroData : CMGyroData
@property (nonatomic, assign) CMRotationRate customRotationRate;
@end

@implementation CustomCMGyroData
- (CMRotationRate)rotationRate {
    return self.customRotationRate;
}
@end

%hook CMMotionManager

- (void)startGyroUpdatesToQueue:(NSOperationQueue *)queue withHandler:(CMGyroHandler)handler {
    if (!queue || !handler) return %orig;
    
    CMGyroHandler newHandler = ^(CMGyroData *gyroData, NSError *error) {
        CMRotationRate randomRate;
        randomRate.x = randomRadiansPerSecond();
        randomRate.y = randomRadiansPerSecond();
        randomRate.z = randomRadiansPerSecond();
        
        CustomCMGyroData *customGyroData = (CustomCMGyroData *)[[objc_getClass("CMGyroData") alloc] init];
        customGyroData.customRotationRate = randomRate;
        
        if (handler) {
            handler((CMGyroData *)customGyroData, error);
        }
    };
    
    %orig(queue, newHandler);
}

%end

#pragma mark - C Function Hooks

%hookf(int, uname, struct utsname *systemInfo) {
    int nRet = %orig;
    NSString *productStr = [XHookSettings productStr];
    if (productStr) {
        char str_machine_name[100];
        [productStr getCString:str_machine_name maxLength:100 encoding:NSUTF8StringEncoding];
        strcpy(systemInfo->machine, str_machine_name);
    }
    return nRet;
}

%hookf(int, sysctl, const int *name, u_int namelen, void *oldp, size_t *oldlenp, const void *newp, size_t newlen) {
    if (namelen < 2) {
        return %orig;
    }
    
    if (name[0] == CTL_KERN && name[1] == KERN_BOOTTIME) {
        int nRet = %orig;
        if (nRet == 0 && oldp && oldlenp && *oldlenp >= sizeof(struct timeval)) {
            struct timeval *a = (struct timeval *)oldp;
            NSInteger kernBootTimeOffset = [XHookSettings kernBootTimeOffset];
            if (kernBootTimeOffset != 0) {
                a->tv_sec += kernBootTimeOffset;
            }
        }
        return nRet;
    }
    else if (name[0] == CTL_HW && name[1] == HW_MACHINE) {
        NSString *modelName = [XHookSettings productStr];
        const char *spoofedModel = [modelName UTF8String];
        if (!spoofedModel) {
            return %orig;
        }
        
        size_t modelLen = strlen(spoofedModel) + 1;
        
        if (!oldp && oldlenp) {
            *oldlenp = modelLen;
            return 0;
        }
        
        if (oldp && oldlenp) {
            if (*oldlenp < modelLen) {
                return ENOMEM;
            }
            strlcpy((char *)oldp, spoofedModel, *oldlenp);
            *oldlenp = modelLen;
            return 0;
        }
        
        return EINVAL;
    }
    
    return %orig;
}

%hookf(int, sysctlbyname, const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen) {
    if (strcmp(name, "hw.machine") == 0) {
        int ret = %orig;
        NSString *machine = [XHookSettings productStr];
        if (oldp && machine) {
            const char *hwMachineCh = [machine UTF8String];
            if (hwMachineCh) {
                size_t len = strlen(hwMachineCh);
                if (*oldlenp > len) {
                    strlcpy((char *)oldp, hwMachineCh, *oldlenp);
                    *oldlenp = len;
                }
            }
        }
        return ret;
    } else if (strcmp(name, "hw.model") == 0) {
        int ret = %orig;
        NSString *modelStr = [XHookSettings hwModel];
        if (oldp && modelStr) {
            const char *modelStrCh = [modelStr UTF8String];
            if (modelStrCh) {
                size_t len = strlen(modelStrCh);
                if (*oldlenp > len) {
                    strlcpy((char *)oldp, modelStrCh, *oldlenp);
                    *oldlenp = len;
                }
            }
        }
        return ret;
    }
    return %orig;
}

%hookf(int, clock_gettime, clockid_t __clock_id, struct timespec *__tp) {
    int nret = %orig;
    if(__clock_id == CLOCK_MONOTONIC_RAW) {
        // Reserved
    }
    return nret;
}

#pragma mark - Network Interface Helpers

static struct ifaddrs* createInterface(const char* name, const char* ipv4, const char* ipv6) {
    if (!name || !ipv4 || !ipv6) return NULL;
    
    @try {
        struct ifaddrs *new_ifaddr = (struct ifaddrs *)calloc(1, sizeof(struct ifaddrs));
        if (!new_ifaddr) return NULL;
        
        new_ifaddr->ifa_name = strdup(name);
        if (!new_ifaddr->ifa_name) {
            free(new_ifaddr);
            return NULL;
        }
        new_ifaddr->ifa_flags = IFF_UP | IFF_RUNNING;
        
        struct sockaddr_in *addr_in = (struct sockaddr_in *)calloc(1, sizeof(struct sockaddr_in));
        if (!addr_in) {
            free(new_ifaddr->ifa_name);
            free(new_ifaddr);
            return NULL;
        }
        
        addr_in->sin_family = AF_INET;
        if (inet_pton(AF_INET, ipv4, &addr_in->sin_addr) != 1) {
            free(addr_in);
            free(new_ifaddr->ifa_name);
            free(new_ifaddr);
            return NULL;
        }
        new_ifaddr->ifa_addr = (struct sockaddr *)addr_in;
        
        struct ifaddrs *next_ifaddr = (struct ifaddrs *)calloc(1, sizeof(struct ifaddrs));
        if (!next_ifaddr) {
            free(addr_in);
            free(new_ifaddr->ifa_name);
            free(new_ifaddr);
            return NULL;
        }
        
        next_ifaddr->ifa_name = strdup(name);
        if (!next_ifaddr->ifa_name) {
            free(next_ifaddr);
            free(addr_in);
            free(new_ifaddr->ifa_name);
            free(new_ifaddr);
            return NULL;
        }
        next_ifaddr->ifa_flags = IFF_UP | IFF_RUNNING;
        
        struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)calloc(1, sizeof(struct sockaddr_in6));
        if (!addr_in6) {
            free(next_ifaddr->ifa_name);
            free(next_ifaddr);
            free(addr_in);
            free(new_ifaddr->ifa_name);
            free(new_ifaddr);
            return NULL;
        }
        
        addr_in6->sin6_family = AF_INET6;
        if (inet_pton(AF_INET6, ipv6, &addr_in6->sin6_addr) != 1) {
            free(addr_in6);
            free(next_ifaddr->ifa_name);
            free(next_ifaddr);
            free(addr_in);
            free(new_ifaddr->ifa_name);
            free(new_ifaddr);
            return NULL;
        }
        next_ifaddr->ifa_addr = (struct sockaddr *)addr_in6;
        
        new_ifaddr->ifa_next = next_ifaddr;
        return new_ifaddr;
    } @catch (NSException *exception) {
        return NULL;
    }
}

static void modifyInterfaceIP(struct ifaddrs *interface, const char *ipv4, const char *ipv6) {
    if (!interface || !ipv4 || !ipv6) return;
    
    @try {
        if (interface->ifa_addr) {
            if (((struct sockaddr_in*)interface->ifa_addr)->sin_family == AF_INET) {
                struct sockaddr_in *addr = (struct sockaddr_in*)interface->ifa_addr;
                inet_pton(AF_INET, ipv4, &addr->sin_addr);
            } else if (((struct sockaddr_in*)interface->ifa_addr)->sin_family == AF_INET6) {
                struct sockaddr_in6 *addr6 = (struct sockaddr_in6*)interface->ifa_addr;
                inet_pton(AF_INET6, ipv6, &addr6->sin6_addr);
            }
        }
    } @catch (NSException *exception) {
    }
}

%hookf(int, getifaddrs, struct ifaddrs **interfaces) {
    if (!interfaces) return EINVAL;
    
    int nRet = %orig;
    if (nRet != 0) return nRet;
    
    @try {
        NSDictionary *networkConfig = [XHookSettings networkInterfacesInfo];
        if (!networkConfig) {
            return nRet;
        }
        
        struct ifaddrs *interface;
        bool isHookEn0IpV4 = false;
        
        for (interface = *interfaces; interface; interface = interface->ifa_next) {
            if (!interface->ifa_name) continue;
            
            NSString *name = @(interface->ifa_name);
            if ([name isEqualToString:@"en0"]) {
                NSDictionary *en0Config = networkConfig[@"en0"];
                if (en0Config) {
                    const char *ipv4 = [en0Config[@"ipv4"] UTF8String];
                    const char *ipv6 = [en0Config[@"ipv6"] UTF8String];
                    if (ipv4 && ipv6) {
                        modifyInterfaceIP(interface, ipv4, ipv6);
                        isHookEn0IpV4 = true;
                    }
                }
            }
        }
        
        if (!isHookEn0IpV4) {
            NSDictionary *en0Config = networkConfig[@"en0"];
            if (en0Config) {
                const char *ipv4 = [en0Config[@"ipv4"] UTF8String];
                const char *ipv6 = [en0Config[@"ipv6"] UTF8String];
                struct ifaddrs *new_en0 = createInterface("en0", ipv4, ipv6);
                if (new_en0) {
                    new_en0->ifa_next->ifa_next = *interfaces;
                    *interfaces = new_en0;
                }
            }
        }
        
        NSDictionary *pdpConfig = networkConfig[@"pdp_ip0"];
        if (pdpConfig) {
            const char *ipv4 = [pdpConfig[@"ipv4"] UTF8String];
            const char *ipv6 = [pdpConfig[@"ipv6"] UTF8String];
            struct ifaddrs *new_pdp = createInterface("pdp_ip0", ipv4, ipv6);
            if (new_pdp) {
                new_pdp->ifa_next->ifa_next = *interfaces;
                *interfaces = new_pdp;
            }
        }
        
    } @catch (NSException *exception) {
    }
    
    return nRet;
}

%hookf(Boolean, SCNetworkReachabilityGetFlags, SCNetworkReachabilityRef target, SCNetworkReachabilityFlags *flags) {
    Boolean result = %orig(target, flags);
    
    if (result && flags != NULL) {
        *flags |= kSCNetworkReachabilityFlagsReachable;
        *flags |= kSCNetworkReachabilityFlagsIsWWAN; 
    }
    
    return result;
}

%hookf(BOOL, UIAccessibilityIsClosedCaptioningEnabled) {
    return YES;
}

%hookf(BOOL, UIAccessibilityIsMonoAudioEnabled) {
    return YES;
}

%end // end of %group XHooks

#pragma mark - ============== CONSTRUCTOR & DESTRUCTOR ==============

%ctor {
    NSLog(@"XHook: Starting...");
    @try {
        @autoreleasepool {
            NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
            NSString *jsonPath = @"/var/mobile/Documents/changeapp.json";
            
            BOOL loaded = [XHookSettings loadSettingsFromPath:jsonPath];
            if (!loaded) {
                NSLog(@"XHook: Failed to load settings from %@", jsonPath);
                return;
            }
            
            if ([XHookSettings shouldHookApp:bundleIdentifier]) {
                NSUUID *idfa = [XHookSettings idfa];
                
                SetupXSLog();
                NSLog(@"XHook: Hooks started for %@; IDFA: %@", bundleIdentifier, idfa);
                
                [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"kXHookSSFaceKey"];
                
                %init(XHooks);
            } else {
                NSLog(@"XHook: App %@ not in hook list, skipping", bundleIdentifier);
            }
        }
    } @catch (NSException *exception) {
        NSLog(@"XHook: Error in ctor: %@", exception);
    }
}

%dtor {
    NSLog(@"XHook: Cleaning up...");
    [XHookSettings cleanup];
}
