#import "CLAlert.h"
#import <CoreServices/CoreServices.h>
#import <objc/runtime.h>
#import <mach/mach_init.h>
#import <mach/vm_map.h>
#import <libkern/OSCacheControl.h>
@implementation CLAlert
#if defined(__i386__) || defined(__x86_64__)
#define isIntel
#elif defined(__ppc__) || defined(__ppc64__)
#define isPowerPC
#else
#error Architecture not supported
#endif
#if defined(isIntel)
typedef char* instrPtr;
#elif defined(isPowerPC)
typedef uint32_t* instrPtr;
#endif
static OSType previousIconType = kAlertCautionIcon;
- (BOOL) patchBuildAlert:(NSAlertStyle)alertStyle
{
BOOL isPatched = NO;
Method buildAlertMethod = class_getInstanceMethod([super class], @selector(buildAlertStyle:title:formattedMsg:first:second:third:oldStyle:));
if (buildAlertMethod) {
const IMP buildAlertIMP = method_getImplementation(buildAlertMethod);
instrPtr buildAlertPtr = (instrPtr)buildAlertIMP;
// As of 10.5.8, i386 offset = 85, x86_64 offset = 107, ppc offset = 104 and ppc64 offset = 180
// So 360 should be enough to accomodate a few changes in the implementation
const instrPtr buildAlertStop = buildAlertPtr + 360;
BOOL isPatchSafe = NO;
#if defined(isIntel)
OSType *iconType = NULL;
while (buildAlertPtr < buildAlertStop) {
if (memcmp(buildAlertPtr, &previousIconType, sizeof(previousIconType)) == 0) {
iconType = (OSType*)buildAlertPtr;
break;
}
buildAlertPtr++;
}
isPatchSafe = (iconType != NULL);
#elif defined(isPowerPC)
instrPtr loadHi = NULL;
instrPtr loadLo = NULL;
const uint32_t expectedHiInstr = 0x3ca00000 | (previousIconType >> 16); // lis r5,0x....
const uint32_t expectedLoInstr = 0x60a50000 | (previousIconType & 0xFFFF); // ori r5,r5,0x....
while (buildAlertPtr < buildAlertStop) {
if (*buildAlertPtr == expectedHiInstr) {
loadHi = buildAlertPtr;
} else if (*buildAlertPtr == expectedLoInstr) {
loadLo = buildAlertPtr;
break;
}
buildAlertPtr++;
}
isPatchSafe = ((loadLo > loadHi) && (loadHi != NULL) && (loadLo != NULL));
#endif
if (isPatchSafe) {
const kern_return_t vm_err = vm_protect(mach_task_self(), (vm_address_t)buildAlertIMP, 360, false, VM_PROT_ALL);
if (vm_err == KERN_SUCCESS) {
OSType alertType = kUnknownType;
if (alertStyle == NSInformationalAlertStyle) {
alertType = kAlertNoteIcon;
} else if (alertStyle == NSWarningAlertStyle) {
alertType = kAlertCautionIcon;
} else if (alertStyle == NSCriticalAlertStyle) {
alertType = kAlertStopIcon;
}
if (alertType != kUnknownType) {
isPatched = YES;
#if defined(isIntel)
*iconType = alertType;
sys_icache_invalidate(iconType, sizeof(*iconType));
#elif defined(isPowerPC)
*loadHi = 0x3ca00000 | (alertType >> 16);
*loadLo = 0x60a50000 | (alertType & 0xFFFF);
sys_icache_invalidate(loadHi, sizeof(*loadHi));
sys_icache_invalidate(loadLo, sizeof(*loadLo));
#else
isPatched = NO;
#endif
previousIconType = alertType;
}
}
}
}
return isPatched;
}
- (void) layout
{
const NSAlertStyle superStyle = [self alertStyle];
if ([self patchBuildAlert:superStyle]) {
[self setAlertStyle:NSCriticalAlertStyle];
[super layout];
[self setAlertStyle:superStyle];
} else {
[super layout];
}
}
// MARK: Restoration of NSAlert default behavior with caution icon
- (NSInteger) runModal
{
const NSInteger response = [super runModal];
[self patchBuildAlert:NSWarningAlertStyle];
return response;
}
static id superModalDelegate = nil;
static SEL superDidEndSelector = NULL;
- (void) beginSheetModalForWindow:(NSWindow *)window modalDelegate:(id)modalDelegate didEndSelector:(SEL)alertDidEndSelector contextInfo:(void *)contextInfo
{
superModalDelegate = modalDelegate;
superDidEndSelector = alertDidEndSelector;
[super beginSheetModalForWindow:window modalDelegate:self didEndSelector:@selector(cla:clr:clc:) contextInfo:contextInfo];
}
- (void) cla:(NSAlert *)alert clr:(int)returnCode clc:(void *)contextInfo
{
[self patchBuildAlert:NSWarningAlertStyle];
objc_msgSend(superModalDelegate, superDidEndSelector, alert, returnCode, contextInfo);
}
@end