Skip to content

Instantly share code, notes, and snippets.

@ericbroska
Last active December 15, 2015 22:59
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ericbroska/5336463 to your computer and use it in GitHub Desktop.
Save ericbroska/5336463 to your computer and use it in GitHub Desktop.
Code injection for CleanMyMac 2.0.2

How to build this up and inject in the app yourself?

Note: you must have an Xcode to do all that stuff.

$ git clone https://github.com/andymatuschak/Sparkle.git MySparkleForInjection
$ cd MySparkleForInjection
$ open SUAppcast.m
(sure, you can use any other file with «.m» extension in there)

  1. Insert a code from injectcmm202.mm in the, say, just end of the file.

You also should insert #include <objc/runtime.h> just after the last #import <XXX.h> line at the beginning of the file you choose.

  1. It's time to build your own Sparkle!
    $ xcodebuild -target Sparkle -configuration Release

  2. Replace Sparkle.framework from CleanMyMac 2.app/Contents/Frameworks with the one you've just built (it will be located in MySparkleForInjection/build/Release).

  3. Done! Who's the pirate now?

(^^,)

#include <objc/runtime.h>
@interface EBInjector : NSObject
@end
/* ------------------------------------------------------ */
@implementation EBInjector
+ (void)replaceMethod: (SEL)selector inClass: (Class)class
{
Method originalMethod = class_getInstanceMethod(class, selector);
if (!originalMethod) return;
Method newMethod = class_getInstanceMethod(self, selector);
if (!newMethod) return;
IMP newImp = method_getImplementation(newMethod);
if (!class_addMethod(class, selector, newImp, method_getTypeEncoding(originalMethod))) {
method_exchangeImplementations(originalMethod, newMethod);
}
}
- (NSDirectoryEnumerator *)enumeratorAtURL: (NSURL *)url
includingPropertiesForKeys: (NSArray *)keys
options: (NSDirectoryEnumerationOptions)mask
errorHandler: (BOOL (^)(NSURL *url, NSError *error))handler
{
/* If we try to enumerate ./Contents/Frameworks directory then
return an enumerator for ./Contents/MacOS.
*/
CFURLRef private_frameworks_url = CFBundleCopyPrivateFrameworksURL(CFBundleGetMainBundle());
if ([[url path] isEqualToString: [(NSURL *)private_frameworks_url path]]) {
NSString *path = [NSString stringWithFormat:
@"%@/Contents/MacOS", [[NSBundle mainBundle] bundlePath]];
NSURL *executables_url = [NSURL fileURLWithPath: path];
return [self enumeratorAtURL: executables_url includingPropertiesForKeys: keys
options: mask errorHandler: handler];
} else {
/* The original implementation is stored in this class now */
EBInjector *foo = [[[EBInjector alloc] init] autorelease];
return [foo enumeratorAtURL: url includingPropertiesForKeys: keys
options: mask errorHandler: handler];
}
}
/*
* CMMainWindowController
*/
- (void)applicationDidFinishLaunching
{
/* Hide the «Unlock Full Version» button */
SEL unlockFullVersionButton_sel = NSSelectorFromString(@"unlockFullVersionButton");
if ([self respondsToSelector: unlockFullVersionButton_sel]) {
[(NSButton *)[self performSelector: unlockFullVersionButton_sel] setHidden: YES];
}
/* And make it unavailable for anybody */
[EBInjector replaceMethod: NSSelectorFromString(@"unlockFullVersionButton")
inClass: NSClassFromString(@"CMMainWindowController")];
}
- (id)unlockFullVersionButton
{
return nil;
}
- (void)updateTrialSizeLeft: (id)size
{
return;
}
- (void)scannerController: (id)controller shouldPauseCleaningForItemSize: (id)size
{
return;
}
#define failme(x) do { if (!(x)) { goto end; }} while(0)
- (void)initHardOperations
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Class nc_class = NSClassFromString(@"CMNotificationController"),
growl_ab_class = NSClassFromString(@"GrowlApplicationBridge"),
sys_info_class = NSClassFromString(@"CMSystemInfo");
SEL shared_instance_sel = NSSelectorFromString(@"sharedInstance"),
set_growl_delegate_sel = NSSelectorFromString(@"setGrowlDelegate:"),
preload_data_sel = NSSelectorFromString(@"preloadData"),
init_schedulers_sel = NSSelectorFromString(@"initSchedulers"),
app_controller_sel = NSSelectorFromString(@"applicationsController"),
scan_sel = NSSelectorFromString(@"scan");
failme([nc_class respondsToSelector: shared_instance_sel]);
failme([growl_ab_class respondsToSelector: set_growl_delegate_sel]);
failme([sys_info_class respondsToSelector: shared_instance_sel]);
failme([self respondsToSelector: init_schedulers_sel]);
failme([self respondsToSelector: app_controller_sel]);
/*
CMNotificationController *notifier = [CMNotificationController sharedInstance];
[GrowlApplicationBridge setGrowlDelegate: notifier];
*/
id notification_center =[[nc_class performSelector: shared_instance_sel] retain];
[growl_ab_class performSelector: set_growl_delegate_sel
withObject: notification_center];
[notification_center release];
/*
CMSystemInfo *info = [[MSystemInfo sharedInstance];
[info preloadData];
*/
id cm_system_info = [[sys_info_class performSelector: shared_instance_sel] retain];
failme([cm_system_info respondsToSelector: preload_data_sel]);
[cm_system_info performSelector: preload_data_sel];
[cm_system_info release];
/*
[self initSchedulers];
CMApplicationController *controller = [self applicationsController];
[controller scan];
*/
[self performSelector: init_schedulers_sel];
id app_controller = [[self performSelector: app_controller_sel] retain];
failme([app_controller respondsToSelector: scan_sel]);
[app_controller performSelector: scan_sel];
[app_controller release];
end:
[pool drain];
}
/*
* CMActivationManager
*/
- (void)showCleanActivationReminderIfNeedsWithContinueHandler: (void *)handler
{
/* Really, we don't care about anything, so just call this handlers */
void (^myBlock)() = Block_copy(handler);
myBlock();
Block_release(myBlock);
}
- (NSInteger)appActivationStatus
{
NSInteger magic = 0x6d3;
/* We can not be sure, that this value won't be read from the ivar directly */
object_setInstanceVariable(self, [@"_appActivationStatus" UTF8String], *(NSInteger **)&magic);
return magic;
}
- (NSInteger)isAppActivated { return 0x1; }
/* Just for fun; actually has no effect until you remove
-appActivationStatus and -isAppActivated
from |todo| dictionary in +load
*/
- (NSUInteger)trialSizeLeft { return 0xffffffffffffffff; }
/*
* NSApplication
*/
- (NSDictionary *)licenseUserInfo
{
return @{
@"activation_date" : [NSDate dateWithString: @"2000-01-01 00:00:00 +0000"],
@"expiration_date" : [NSDate dateWithString: @"2666-01-01 00:00:00 +0000"],
@"activation_id" : @"(^^,)",
@"expiration_version" : @"666"
};
}
+ (void)load
{
NSDictionary *todo = @ {
@"CMActivationManager" : @[@"isAppActivated",
@"appActivationStatus",
@"trialSizeLeft",
@"showCleanActivationReminderIfNeedsWithContinueHandler:"],
@"NSFileManager" : @[@"enumeratorAtURL:includingPropertiesForKeys:options:errorHandler:"],
@"NSApplication" : @[@"licenseUserInfo"],
@"CMMainWindowController": @[@"applicationDidFinishLaunching",
@"scannerController:shouldPauseCleaningForItemSize:",
@"updateTrialSizeLeft:",
@"initHardOperations"]
};
[todo enumerateKeysAndObjectsUsingBlock: ^(NSString *class_name, NSArray *selectors, BOOL *stop1) {
[selectors enumerateObjectsUsingBlock: ^(NSString *selector_name, NSUInteger index, BOOL *stop2) {
[self replaceMethod: NSSelectorFromString(selector_name)
inClass: NSClassFromString(class_name)];
}];
}];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment