Last active
August 29, 2015 14:14
-
-
Save Noitidart/3571dbc1b75f28532bc2 to your computer and use it in GitHub Desktop.
_ff-addon-snippet-ObjC_SwizzleWithExchangeImplementation - Demonstrates how to swizzle with js-ctypes through use of method_exchangeImplementations. The swizzle is on [NSImage imageNamed] taking my custom icon whenever it asks for @'NSApplicationIcon'. [objectivec] [jsctypes] [osx]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Cu.import('resource://gre/modules/osfile.jsm'); | |
Cu.import('resource://gre/modules/ctypes.jsm'); | |
var objc = ctypes.open(ctypes.libraryName('objc')); | |
/** START - edit these **/ | |
var jsStr_imagePath = OS.Path.join(OS.Constants.Path.desktopDir, 'ff-logos', 'beta48.png'); | |
/** END - edit these **/ | |
// BASIC TYPES | |
var BOOL = ctypes.signed_char; | |
var CHAR = ctypes.char; | |
var CLASS = ctypes.voidptr_t; | |
var ID = ctypes.voidptr_t; | |
var METHOD = ctypes.voidptr_t; | |
var NSUINTEGER = ctypes.unsigned_long; | |
var SEL = ctypes.voidptr_t; | |
var VOID = ctypes.void_t; | |
// ADVANCED TYPES | |
var IMP_for_imageNamed = ctypes.FunctionType(ctypes.default_abi, ID, [ID, SEL, ID]).ptr; //repalced variadic with ID as its specific to my use otherwise doing class_addMethod throws error saying expected pointer blah blah //ctypes.FunctionType(ctypes.default_abi, ID, [ID, SEL, '...']).ptr; | |
// COMMON FUNCTIONS | |
var class_addMethod = objc.declare('class_addMethod', ctypes.default_abi, BOOL, CLASS, SEL, IMP_for_imageNamed, CHAR.ptr); | |
var class_getClassMethod = objc.declare('class_getClassMethod', ctypes.default_abi, METHOD, CLASS, SEL); | |
var method_exchangeImplementations = objc.declare('method_exchangeImplementations', ctypes.default_abi, VOID, METHOD, METHOD); | |
var objc_getClass = objc.declare('objc_getClass', ctypes.default_abi, ID, CHAR.ptr); | |
var objc_getMetaClass = objc.declare('objc_getMetaClass', ctypes.default_abi, ID, CHAR.ptr); | |
var objc_msgSend = objc.declare('objc_msgSend', ctypes.default_abi, ID, ID, SEL, '...'); | |
var sel_registerName = objc.declare('sel_registerName', ctypes.default_abi, SEL, CHAR.ptr); | |
// COMMON SELECTORS | |
var alloc = sel_registerName('alloc'); | |
var init = sel_registerName('init'); | |
var release = sel_registerName('release'); | |
// my personal globals for this code | |
var myIcon; // global so i can do release on it at shutdown | |
var swizzled_imageNamed; // important to make this global otherwise the callback will be GC'ed and firefox will crash | |
function shutdown() { | |
//put code here to unswizzle it | |
if (myIcon) { | |
objc_msgSend(myIcon, release); | |
} | |
objc.close(); | |
} | |
var promise_makeMyNSImage = OS.File.read(jsStr_imagePath); | |
promise_makeMyNSImage.then( | |
function(iconData) { | |
// NOTE: iconData is Uint8Array | |
var length = NSUINTEGER(iconData.length); | |
var bytes = ctypes.uint8_t.array()(iconData); | |
// data = [NSData dataWithBytes: bytes length: length]; | |
var NSData = objc_getClass('NSData'); | |
var dataWithBytes_length = sel_registerName('dataWithBytes:length:'); | |
var data = objc_msgSend(NSData, dataWithBytes_length, bytes, length); | |
// myIcon = [[NSImage alloc] initWithData: data]; | |
var NSImage = objc_getClass('NSImage'); | |
var initWithData = sel_registerName('initWithData:'); | |
myIcon = objc_msgSend(objc_msgSend(NSImage, alloc), initWithData, data); | |
if (myIcon.isNull()) { | |
throw new Error('Image file is corrupted. Will not continue to swizzle.'); | |
} | |
var UTF8String = sel_registerName('UTF8String'); | |
var noitSwizzled_imageNamed = sel_registerName('noitSwizzled_imageNamed:'); | |
var objc_msgSend_CHAR = objc.declare('objc_msgSend', ctypes.default_abi, CHAR.ptr, ID, SEL, '...'); | |
var js_swizzled_imageNamed = function(c_arg1__self, c_arg2__sel, objc_arg1__NSStringPtr) { | |
console.log('SWIZZLED: imageNamed called'); | |
var tt_read = objc_msgSend_CHAR(objc_arg1__NSStringPtr, UTF8String); | |
console.info('tt_read:', tt_read, tt_read.toString(), uneval(tt_read)); | |
var tt_read_jsStr = tt_read.readStringReplaceMalformed(); | |
console.info('tt_read_jsStr:', tt_read_jsStr, tt_read_jsStr.toString(), uneval(tt_read_jsStr)); // TypeError: tt_read_jsStr.isNull is not a function | |
if (tt_read_jsStr == 'NSApplicationIcon') { | |
// do my hook | |
console.log('this is purpose of this swizzle, to return myIcon when they ask for NSApplicationicon'); | |
return myIcon; | |
} else { | |
// do normal | |
console.log('doing regular imageNamed'); | |
var icon = objc_msgSend(NSImage, noitSwizzled_imageNamed, objc_arg1__NSStringPtr); //cuz i did method_exchangeImplementations using selector of `noitSwizzled_imageNamed` will call the original | |
return icon; | |
} | |
} | |
//var IMP_for_imageNamed_specific = ctypes.FunctionType(ctypes.default_abi, ID, [ID, SEL, ID]).ptr; // return of ID is really NSIMAGE and third arg is NSSTRING | |
swizzled_imageNamed = IMP_for_imageNamed(js_swizzled_imageNamed); //if use IMP_for_imageNamed_specific and have variadic IMP_for_imageNamed defined above, it keeps throwing expecting pointer blah blah. and it wouldnt accept me putting in variadic on this line if do use varidic, on this line it throws `Can't delcare a variadic callback function` | |
// add swizzled_imageNamed to NSImage | |
var NSImageMeta = objc_getMetaClass('NSImage'); // have to add on meta class otherwise class_getClassMethod will fail to find it, meaning `var alternateMethod = class_getClassMethod(NSImage, noitSwizzled_imageNamed);` will return NIL // this quirk was discovered from: http://stackoverflow.com/questions/9377840/how-to-dynamically-add-a-class-method | |
var rez_class_addMethod = class_addMethod(NSImageMeta, noitSwizzled_imageNamed, swizzled_imageNamed, '@@:@'); // because return of callback is NSImage so `ctypes.voidptr_t`, first argument is c_arg1__self which is `id` and c_arg2__id sel `SEL` and objc_arg1__NSStringPtr is `voidptr_t` | |
console.info('rez_class_addMethod:', rez_class_addMethod, rez_class_addMethod.toString(), uneval(rez_class_addMethod)); | |
if (rez_class_addMethod != 1) { | |
// when i ran this twice, so it was already there, the second time it came back as 0 | |
throw new Error('rez_class_addMethod is not 1, so class_addMethod failed'); | |
} | |
var imageNamed = sel_registerName('imageNamed:'); | |
var originalMethod = class_getClassMethod(NSImage, imageNamed); //verified i dont need to do pass [NSImage class] as `NSImage = objc_getClass('NSImage')` is same as `NSImageClass = objc_msgSend(NSImage, sel_registerName('class'))` by doing a .toString comparison on both of them | |
console.info('originalMethod:', originalMethod, originalMethod.toString(), uneval(originalMethod)); | |
if (originalMethod.isNull()) { | |
throw new Error('originalMethod is NIL'); | |
} | |
var alternateMethod = class_getClassMethod(NSImage, noitSwizzled_imageNamed); | |
console.info('alternateMethod:', alternateMethod, alternateMethod.toString(), uneval(alternateMethod)); | |
if (alternateMethod.isNull()) { | |
throw new Error('alternateMethod is NIL'); | |
} | |
var rez = method_exchangeImplementations(originalMethod, alternateMethod); | |
// rez is void | |
console.log('Assuming succesfully swizzled as no errors occured'); | |
}, | |
function(aReason) { | |
var rejObj = { | |
nameOfRejector: 'promise_makeMyNSImage', | |
aReason: aReason | |
}; | |
console.warn(rejObj); | |
throw rejObj; | |
} | |
).catch(function(aCaught) { | |
shutdown(); | |
console.error('promise_makeMyNSImage Catch:', aCaught); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
README
See Also GitHubGIST :: Noitidart / _ff-addon-snippet-ObjC_SwizzleWithSetImplementation.js
See Also GitHubGIST :: Noitidart / _ff-addon-snippet-ObjC_SetApplicationIconImage.js
Rev1
To see the swizzling in action, download an image, or open scratchpad and save file as blah.js and then do save as of blah.js again this makes it show dialog prompt you will see the swizzled image. Both cases are shown in screenshot here, notice the icons have the "Beta" sash:
IMP
not as the general way (ctypes.FunctionType(ctypes.default_abi, ID, [ID, SEL, '...']).ptr
, you have to use specific to what your callback is. You cannot have a variadic (...
) in callback apparentlyswizzled_imageNamed
must be globally declared, otherwise it will get GC'ed within seconds and Firefox will crash.class_addMethod
to a private class likeNSImage
you have to pass it the meta class otherwiseclass_getClassMethod
will fail to find it. This technique is explained at StackOverflow :: How to dynamically add a class method?.method_exchangeImplementations
only can exchange methods within a single class, that is why we added our method to theNSImage
class, you cannot exchange between two classes.Rev2
ExchangeImplementation
instead ofExchangeMethods