Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Noitidart/3571dbc1b75f28532bc2 to your computer and use it in GitHub Desktop.
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]
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);
});
@Noitidart
Copy link
Author

README

See Also GitHubGIST :: Noitidart / _ff-addon-snippet-ObjC_SwizzleWithSetImplementation.js
See Also GitHubGIST :: Noitidart / _ff-addon-snippet-ObjC_SetApplicationIconImage.js

Rev1

  • Works, can copy and paste and run from scratchpad, but you need a folder on your desktop called "ff-logos" with a png inside it called "beta48.png"
    • 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:

      Screenshot demonstrating the effect of the swizzle, notice the icons have the "Beta" sash

  • Quirks
    • You have to define 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 apparently
    • The callback swizzled_imageNamed must be globally declared, otherwise it will get GC'ed within seconds and Firefox will crash.
    • When using class_addMethod to a private class like NSImage you have to pass it the meta class otherwise class_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 the NSImage class, you cannot exchange between two classes.
  • The unswizzle code was not implemented, but it is just the opposite of the swizzle
  • This is cleaned up version of original work with history available here: GitHub :: Noitidart / _scratchpad - swizzle NSImage imageNamed objc ctypes.js
    • MUCH thanks to @arai-a for the help in debugging.

Rev2

  • Renamed to be ExchangeImplementation instead of ExchangeMethods

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment