diff --git a/PlayTools/PlayLoader.m b/PlayTools/PlayLoader.m index 73e18e1a..348ab022 100644 --- a/PlayTools/PlayLoader.m +++ b/PlayTools/PlayLoader.m @@ -3,6 +3,7 @@ // PlayTools // +#include #include #include @@ -230,11 +231,62 @@ static int pt_unlink(char const* path) { return unlink(ue_fix_filename(path)); } +static NSMutableDictionary *thread_sleep_counters = nil; +static NSMutableDictionary *last_sleep_attempts = nil; +static dispatch_once_t thread_sleep_once; +static NSLock *thread_sleep_lock = nil; + +static int pt_usleep(useconds_t time) { + dispatch_once(&thread_sleep_once, ^{ + thread_sleep_counters = [NSMutableDictionary dictionary]; + last_sleep_attempts = [NSMutableDictionary dictionary]; + thread_sleep_lock = [[NSLock alloc] init]; + [thread_sleep_lock lock]; + }); + + if ([[PlaySettings shared] blockSleepSpamming]) { + int thread_id = pthread_mach_thread_np(pthread_self()); + NSNumber *threadKey = @(thread_id); + + int thread_sleep_counter = [thread_sleep_counters[threadKey] intValue]; + int last_sleep_attempt = [last_sleep_attempts[threadKey] intValue]; + + if (time == 100000) { + int timestamp = (int)[[NSDate date] timeIntervalSince1970]; + // If it sleeps too fast, increase counter + if (timestamp - last_sleep_attempt < 2) { + thread_sleep_counter++; + } else { + thread_sleep_counter = 1; + } + last_sleep_attempt = timestamp; + thread_sleep_counters[threadKey] = @(thread_sleep_counter); + last_sleep_attempts[threadKey] = @(last_sleep_attempt); + + } + + if (thread_sleep_counter > 100) { + // Stop this thread from spamming usleep calls + NSLog(@"[PC] Thread %i exceeded usleep limit. Seem sus, stopping this " + @"thread FOREVER", + thread_id); + + [thread_sleep_lock lock]; + [thread_sleep_lock unlock]; + + return 0; + } + } + + return usleep(time); +} + DYLD_INTERPOSE(pt_open, open) DYLD_INTERPOSE(pt_stat, stat) DYLD_INTERPOSE(pt_access, access) DYLD_INTERPOSE(pt_rename, rename) DYLD_INTERPOSE(pt_unlink, unlink) +DYLD_INTERPOSE(pt_usleep, usleep) @implementation PlayLoader @@ -250,6 +302,16 @@ static void __attribute__((constructor)) initialize(void) { if (ue_status == 2) { [PlayKeychain debugLogger: [NSString stringWithFormat:@"UnrealEngine Hooked"]]; } + + if ([[PlaySettings shared] blockSleepSpamming]) { + // Add an observer so we can unlock threads on app termination + [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification * _Nonnull note) { + [thread_sleep_lock unlock]; + }]; + } } @end diff --git a/PlayTools/PlaySettings.swift b/PlayTools/PlaySettings.swift index bd570b91..c20d6d32 100644 --- a/PlayTools/PlaySettings.swift +++ b/PlayTools/PlaySettings.swift @@ -93,6 +93,8 @@ let settings = PlaySettings.shared @objc lazy var limitMotionUpdateFrequency = settingsData.limitMotionUpdateFrequency @objc lazy var disableBuiltinMouse = settingsData.disableBuiltinMouse + + @objc lazy var blockSleepSpamming = settingsData.blockSleepSpamming } struct AppSettingsData: Codable { @@ -125,4 +127,5 @@ struct AppSettingsData: Codable { var resizableAspectRatioType = 0 var resizableAspectRatioWidth = 0 var resizableAspectRatioHeight = 0 + var blockSleepSpamming = false }