Skip to content

Commit

Permalink
Fix PlayTools not loading for some apps in #57
Browse files Browse the repository at this point in the history
The problem with seems to have been a combination of the delay in `initMenu` and/or apps having their own `NSObject` categories.

Fixes:
- Remove delay in `initMenu`
- Apply method swizzling from a class that will not have categories or subclasses (mildly "enforced" with a "hidden" attribute)
  • Loading branch information
jslegendre committed Dec 23, 2022
1 parent 9d58e34 commit a4cb88d
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 30 deletions.
68 changes: 47 additions & 21 deletions PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
#import <PlayTools/PlayTools-Swift.h>
#import "PTFakeMetaTouch.h"

__attribute__((visibility("hidden")))
@interface PTSwizzleLoader : NSObject
@end

@implementation NSObject (Swizzle)

- (void) swizzleInstanceMethod:(SEL)origSelector withMethod:(SEL)newSelector
Expand Down Expand Up @@ -45,20 +49,6 @@ - (void) swizzleInstanceMethod:(SEL)origSelector withMethod:(SEL)newSelector
}
}

+ (void) load
{
// TODO: UINSview

if ([[PlaySettings shared] adaptiveDisplay]) {
[objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frame)];
[objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_bounds)];
[objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_size)];
}

[objc_getClass("IOSViewController") swizzleInstanceMethod:@selector(prefersPointerLocked) withMethod:@selector(hook_prefersPointerLocked)];
[self swizzleInstanceMethod:@selector(init) withMethod:@selector(hook_init)];
}

- (BOOL) hook_prefersPointerLocked {
return false;
}
Expand All @@ -76,15 +66,51 @@ - (CGSize) hook_size {
}

bool menuWasCreated = false;

-(id) hook_init {
- (id) initWithRootMenuHook:(id)rootMenu {
self = [self initWithRootMenuHook:rootMenu];
if (!menuWasCreated) {
if ([[self class] isEqual: NSClassFromString(@"_UIMenuBuilder")]) {
[PlayCover initMenuWithMenu: self];
menuWasCreated = TRUE;
}
[PlayCover initMenuWithMenu: self];
menuWasCreated = TRUE;
}

return self;
}

@end

/*
This class only exists to apply swizzles from the +load of a class that won't have any categories/extensions. The reason
for not doing this in a C module initializer is that obj-c initialization happens before any __attribute__((constructor))
is called. This way we can guarantee the hooks will be applied before [PlayCover launch] is called (in PlayLoader.m).
Side note:
While adding method replacements to NSObject does work, I'm not certain this doesn't (or won't) have any side effects. The
way Apple does method swizzling internally is by creating a category of the swizzled class and adding the replacements there.
This keeps all those replacements "local" to that class. Example:
'''
@interface FBSSceneSettings (Swizzle)
-(CGRect) hook_frame {
...
}
@end
Somewhere else:
swizzle(FBSSceneSettings.class, @selector(frame), @selector(hook_frame);
'''
However, doing this would require generating @interface declarations (either with class-dump or by hand) which would add a lot
of code and complexity. I'm not sure this trade-off is "worth it", at least at the time of writing.
*/
@implementation PTSwizzleLoader
+ (void)load {
if ([[PlaySettings shared] adaptiveDisplay]) {
[objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frame)];
[objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_bounds)];
[objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_size)];
}

[objc_getClass("_UIMenuBuilder") swizzleInstanceMethod:sel_getUid("initWithRootMenu:") withMethod:@selector(initWithRootMenuHook:)];
[objc_getClass("IOSViewController") swizzleInstanceMethod:@selector(prefersPointerLocked) withMethod:@selector(hook_prefersPointerLocked)];
}

@end
11 changes: 2 additions & 9 deletions PlayTools/PlayCover.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,8 @@ public class PlayCover: NSObject {
}

@objc static public func initMenu(menu: NSObject) {
delay(0.005) {
guard let menuBuilder = menu as? UIMenuBuilder else { return }

shared.menuController = MenuController(with: menuBuilder)
delay(0.005) {
UIMenuSystem.main.setNeedsRebuild()
UIMenuSystem.main.setNeedsRevalidate()
}
}
guard let menuBuilder = menu as? UIMenuBuilder else { return }
shared.menuController = MenuController(with: menuBuilder)
}

static public func quitWhenClose() {
Expand Down

0 comments on commit a4cb88d

Please sign in to comment.