diff --git a/JRSwizzle.h b/JRSwizzle.h new file mode 100644 index 0000000..7d29bc2 --- /dev/null +++ b/JRSwizzle.h @@ -0,0 +1,13 @@ +// JRSwizzle.h semver:1.0 +// Copyright (c) 2007-2011 Jonathan 'Wolf' Rentzsch: http://rentzsch.com +// Some rights reserved: http://opensource.org/licenses/MIT +// https://github.com/rentzsch/jrswizzle + +#import + +@interface NSObject (JRSwizzle) + ++ (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_; ++ (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_; + +@end diff --git a/JRSwizzle.m b/JRSwizzle.m new file mode 100644 index 0000000..4e582bf --- /dev/null +++ b/JRSwizzle.m @@ -0,0 +1,134 @@ +// JRSwizzle.m semver:1.0 +// Copyright (c) 2007-2011 Jonathan 'Wolf' Rentzsch: http://rentzsch.com +// Some rights reserved: http://opensource.org/licenses/MIT +// https://github.com/rentzsch/jrswizzle + +#import "JRSwizzle.h" + +#if TARGET_OS_IPHONE + #import + #import +#else + #import +#endif + +#define SetNSErrorFor(FUNC, ERROR_VAR, FORMAT,...) \ + if (ERROR_VAR) { \ + NSString *errStr = [NSString stringWithFormat:@"%s: " FORMAT,FUNC,##__VA_ARGS__]; \ + *ERROR_VAR = [NSError errorWithDomain:@"NSCocoaErrorDomain" \ + code:-1 \ + userInfo:[NSDictionary dictionaryWithObject:errStr forKey:NSLocalizedDescriptionKey]]; \ + } +#define SetNSError(ERROR_VAR, FORMAT,...) SetNSErrorFor(__func__, ERROR_VAR, FORMAT, ##__VA_ARGS__) + +#if OBJC_API_VERSION >= 2 +#define GetClass(obj) object_getClass(obj) +#else +#define GetClass(obj) (obj ? obj->isa : Nil) +#endif + +@implementation NSObject (JRSwizzle) + ++ (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_ { +#if OBJC_API_VERSION >= 2 + Method origMethod = class_getInstanceMethod(self, origSel_); + if (!origMethod) { +#if TARGET_OS_IPHONE + SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self class]); +#else + SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]); +#endif + return NO; + } + + Method altMethod = class_getInstanceMethod(self, altSel_); + if (!altMethod) { +#if TARGET_OS_IPHONE + SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self class]); +#else + SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]); +#endif + return NO; + } + + class_addMethod(self, + origSel_, + class_getMethodImplementation(self, origSel_), + method_getTypeEncoding(origMethod)); + class_addMethod(self, + altSel_, + class_getMethodImplementation(self, altSel_), + method_getTypeEncoding(altMethod)); + + method_exchangeImplementations(class_getInstanceMethod(self, origSel_), class_getInstanceMethod(self, altSel_)); + return YES; +#else + // Scan for non-inherited methods. + Method directOriginalMethod = NULL, directAlternateMethod = NULL; + + void *iterator = NULL; + struct objc_method_list *mlist = class_nextMethodList(self, &iterator); + while (mlist) { + int method_index = 0; + for (; method_index < mlist->method_count; method_index++) { + if (mlist->method_list[method_index].method_name == origSel_) { + assert(!directOriginalMethod); + directOriginalMethod = &mlist->method_list[method_index]; + } + if (mlist->method_list[method_index].method_name == altSel_) { + assert(!directAlternateMethod); + directAlternateMethod = &mlist->method_list[method_index]; + } + } + mlist = class_nextMethodList(self, &iterator); + } + + // If either method is inherited, copy it up to the target class to make it non-inherited. + if (!directOriginalMethod || !directAlternateMethod) { + Method inheritedOriginalMethod = NULL, inheritedAlternateMethod = NULL; + if (!directOriginalMethod) { + inheritedOriginalMethod = class_getInstanceMethod(self, origSel_); + if (!inheritedOriginalMethod) { + SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]); + return NO; + } + } + if (!directAlternateMethod) { + inheritedAlternateMethod = class_getInstanceMethod(self, altSel_); + if (!inheritedAlternateMethod) { + SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]); + return NO; + } + } + + int hoisted_method_count = !directOriginalMethod && !directAlternateMethod ? 2 : 1; + struct objc_method_list *hoisted_method_list = malloc(sizeof(struct objc_method_list) + (sizeof(struct objc_method)*(hoisted_method_count-1))); + hoisted_method_list->obsolete = NULL; // soothe valgrind - apparently ObjC runtime accesses this value and it shows as uninitialized in valgrind + hoisted_method_list->method_count = hoisted_method_count; + Method hoisted_method = hoisted_method_list->method_list; + + if (!directOriginalMethod) { + bcopy(inheritedOriginalMethod, hoisted_method, sizeof(struct objc_method)); + directOriginalMethod = hoisted_method++; + } + if (!directAlternateMethod) { + bcopy(inheritedAlternateMethod, hoisted_method, sizeof(struct objc_method)); + directAlternateMethod = hoisted_method; + } + class_addMethods(self, hoisted_method_list); + } + + // Swizzle. + IMP temp = directOriginalMethod->method_imp; + directOriginalMethod->method_imp = directAlternateMethod->method_imp; + directAlternateMethod->method_imp = temp; + + return YES; +#endif +} + ++ (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_ { + return [GetClass((id)self) jr_swizzleMethod:origSel_ withMethod:altSel_ error:error_]; +} + +@end diff --git a/README-Chinese.md b/README-Chinese.md index f99551c..50e0691 100644 --- a/README-Chinese.md +++ b/README-Chinese.md @@ -8,12 +8,14 @@ ## 截图 -![](Screenshot/WeChatTweak-macOS.png) +![](Screenshot/0x01.png) +![](Screenshot/0x02.png) ## 功能 - 阻止消息撤回 - 客户端无限多开 + - 右键 Dock Icon 登录新的微信账号 - 命令行执行:`open -n /Applications/WeChat.app` ## 使用 @@ -33,6 +35,7 @@ ## 依赖 +- [JRSwizzle](https://github.com/rentzsch/jrswizzle) - [insert_dylib](https://github.com/Tyilo/insert_dylib) ## License diff --git a/README.md b/README.md index 5703750..e672732 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,19 @@ A dynamic library tweak for WeChat macOS. ## Screenshot -![](Screenshot/WeChatTweak-macOS.png) +![](Screenshot/0x01.png) +![](Screenshot/0x02.png) ## Feature - Prevent message revoked - Multiple WeChat Instance + - Right click on the Dock icon to login another WeChat account - Run command: `open -n /Applications/WeChat.app` ## Usage -- `sudo make install` Inject the dylib to `WeChat` by [insert_dylib](https://github.com/Tyilo/insert_dylib) +- `sudo make install` Inject the dylib to `WeChat` - `sudo make uninstall` Uninstall the injection ## Development @@ -33,6 +35,7 @@ Run `xcode-select --install` to install Command Line Tools. ## Dependency +- [JRSwizzle](https://github.com/rentzsch/jrswizzle) - [insert_dylib](https://github.com/Tyilo/insert_dylib) ## License diff --git a/Screenshot/WeChatTweak-macOS.png b/Screenshot/0x01.png similarity index 100% rename from Screenshot/WeChatTweak-macOS.png rename to Screenshot/0x01.png diff --git a/Screenshot/0x02.png b/Screenshot/0x02.png new file mode 100644 index 0000000..2369c46 Binary files /dev/null and b/Screenshot/0x02.png differ diff --git a/WeChatTweak.dylib b/WeChatTweak.dylib index 931ccd3..4ab4827 100755 Binary files a/WeChatTweak.dylib and b/WeChatTweak.dylib differ diff --git a/WeChatTweak.m b/WeChatTweak.m index c63fe9f..bcc67fc 100755 --- a/WeChatTweak.m +++ b/WeChatTweak.m @@ -2,34 +2,51 @@ #import #import #import +#import "JRSwizzle.h" -// Tweak for no revoke message. -__attribute__((constructor(101))) static void noRevokeTweak(void) { - Class class = NSClassFromString(@"MessageService"); - SEL selector = NSSelectorFromString(@"onRevokeMsg:"); - Method method = class_getInstanceMethod(class, selector); - IMP imp = imp_implementationWithBlock(^(id self, NSString *message) { - NSRange begin = [message rangeOfString:@""]; - NSRange subRange = NSMakeRange(begin.location + begin.length,end.location - begin.location - begin.length); +@implementation NSObject (WeChatTweak) - NSString *informativeText = [message substringWithRange:subRange]; - dispatch_async(dispatch_get_main_queue(), ^{ - NSUserNotification *userNotification = [[NSUserNotification alloc] init]; - userNotification.informativeText = informativeText; - [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification]; - }); - }); - class_replaceMethod(class, selector, imp, method_getTypeEncoding(method)); +#pragma mark - Constructor + +static void __attribute__((constructor)) tweak(void) { + [objc_getClass("MessageService") jr_swizzleMethod:NSSelectorFromString(@"onRevokeMsg:") withMethod:@selector(tweak_OnRevokeMsg:) error:nil]; + [objc_getClass("CUtility") jr_swizzleClassMethod:NSSelectorFromString(@"HasWechatInstance") withClassMethod:@selector(tweak_HasWechatInstance) error:nil]; + class_addMethod(objc_getClass("AppDelegate"), @selector(applicationDockMenu:), method_getImplementation(class_getInstanceMethod(objc_getClass("AppDelegate"), @selector(applicationDockMenu:))), "@:@"); } -// Tweak for multiple instance. -__attribute__((constructor(102))) static void multipleInstanceTweak(void) { - Class class = object_getClass(NSClassFromString(@"CUtility")); - SEL selector = NSSelectorFromString(@"HasWechatInstance"); - Method method = class_getInstanceMethod(class, selector); - IMP imp = imp_implementationWithBlock(^(id self) { - return 0; +#pragma mark - No Revoke Message + +- (void)tweak_OnRevokeMsg:(NSString *)message { + NSRange begin = [message rangeOfString:@""]; + NSRange subRange = NSMakeRange(begin.location + begin.length,end.location - begin.location - begin.length); + NSString *informativeText = [message substringWithRange:subRange]; + dispatch_async(dispatch_get_main_queue(), ^{ + NSUserNotification *userNotification = [[NSUserNotification alloc] init]; + userNotification.informativeText = informativeText; + [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification]; }); - class_replaceMethod(class, selector, imp, method_getTypeEncoding(method)); } + +#pragma mark - Mutiple Instance + ++ (BOOL)tweak_HasWechatInstance { + return NO; +} + +- (NSMenu *)applicationDockMenu:(NSApplication *)sender { + NSMenu *menu = [[objc_getClass("NSMenu") alloc] init]; + NSMenuItem *menuItem = [[objc_getClass("NSMenuItem") alloc] initWithTitle:@"登录新的微信账号" action:@selector(openNewWeChatInstace:) keyEquivalent:@""]; + [menu insertItem:menuItem atIndex:0]; + return menu; +} + +- (void)openNewWeChatInstace:(id)sender { + NSString *applicationPath = [[objc_getClass("NSBundle") mainBundle] bundlePath]; + NSTask *task = [[objc_getClass("NSTask") alloc] init]; + task.launchPath = @"/usr/bin/open"; + task.arguments = @[@"-n", applicationPath]; + [task launch]; +} + +@end