]> git.saurik.com Git - winterboard.git/blobdiff - Library.mm
Support text theming of icon badge labels on iOS 6.
[winterboard.git] / Library.mm
index fa44fbb0002bdfa3c33773fdcf0b6ebe380cfacb..2744675976efd2f6f8b61088ada8297ff024d8fd 100644 (file)
@@ -47,7 +47,7 @@ bool _itv;
         _itv = true; \
         _ltv = _ctv; \
     } \
-    NSLog(@"%lu.%.6u[%f]:_trace()@%s:%u[%s]\n", \
+    NSLog(@"%lu.%.6u[%f]:WB:_trace()@%s:%u[%s]\n", \
         _ctv.tv_sec, _ctv.tv_usec, \
         (_ctv.tv_sec - _ltv.tv_sec) + (_ctv.tv_usec - _ltv.tv_usec) / 1000000.0, \
         __FILE__, __LINE__, __FUNCTION__\
@@ -107,6 +107,10 @@ bool _itv;
 
 extern "C" void __clear_cache (char *beg, char *end);
 
+static void (*$objc_setAssociatedObject)(id object, void *key, id value, objc_AssociationPolicy policy);
+static id (*$objc_getAssociatedObject)(id object, void *key);
+static void (*$objc_removeAssociatedObjects)(id object);
+
 @protocol WinterBoard
 - (void *) _node;
 @end
@@ -121,11 +125,15 @@ MSClassHook(UIImage)
 MSMetaClassHook(UIImage)
 MSClassHook(UINavigationBar)
 MSClassHook(UIToolbar)
+MSClassHook(UIStatusBarTimeItemView)
+MSClassHook(UIWebDocumentView)
 
+MSClassHook(CKBalloonView)
 MSClassHook(CKMessageCell)
 MSClassHook(CKTimestampView)
 MSClassHook(CKTranscriptCell)
 MSClassHook(CKTranscriptController)
+MSClassHook(CKTranscriptHeaderView)
 MSClassHook(CKTranscriptTableView)
 
 MSClassHook(SBApplication)
@@ -137,12 +145,21 @@ MSClassHook(SBCalendarApplicationIcon)
 MSClassHook(SBCalendarIconContentsView)
 MSClassHook(SBDockIconListView)
 MSClassHook(SBIcon)
+MSClassHook(SBIconAccessoryImage)
+MSMetaClassHook(SBIconAccessoryImage)
 MSClassHook(SBIconBadge)
 MSClassHook(SBIconBadgeFactory)
+MSClassHook(SBIconBadgeImage)
+MSClassHook(SBIconContentView)
 MSClassHook(SBIconController)
 MSClassHook(SBIconLabel)
+MSClassHook(SBIconLabelImage)
+MSMetaClassHook(SBIconLabelImage)
+MSClassHook(SBIconLabelImageParameters)
 MSClassHook(SBIconList)
 MSClassHook(SBIconModel)
+MSClassHook(SBIconView)
+MSMetaClassHook(SBIconView)
 //MSClassHook(SBImageCache)
 MSClassHook(SBSearchView)
 MSClassHook(SBSearchTableViewCell)
@@ -157,17 +174,23 @@ MSClassHook(SBWidgetApplicationIcon)
 
 extern "C" void WKSetCurrentGraphicsContext(CGContextRef);
 
-__attribute__((__constructor__))
-static void MSFixClass() {
-    if ($SBIcon == nil)
-        $SBIcon = objc_getClass("SBIconView");
+static struct MSFixClass { MSFixClass() {
+    $UIWebDocumentView = objc_getClass("UIWebBrowserView") ?: $UIWebDocumentView;
+    $SBIcon = objc_getClass("SBIconView") ?: $SBIcon;
+
+    if ($SBIconList == nil)
+        $SBIconList = objc_getClass("SBIconListView");
     if ($CKTranscriptController == nil)
         $CKTranscriptController = objc_getClass("mSMSMessageTranscriptController");
-}
+} } MSFixClass;
 
 static bool IsWild_;
 static bool Four_($SBDockIconListView != nil);
 
+@interface NSObject (wb$SBIconAccessoryImage)
++ (Class) _imageClassForIcon:(SBIcon *)icon location:(int)location;
+@end
+
 @interface NSDictionary (WinterBoard)
 - (UIColor *) wb$colorForKey:(NSString *)key;
 - (BOOL) wb$boolForKey:(NSString *)key;
@@ -198,7 +221,7 @@ static BOOL (*_GSFontGetUseLegacyFontMetrics)();
 static bool Debug_ = false;
 static bool UIDebug_ = false;
 static bool Engineer_ = false;
-static bool SummerBoard_ = true;
+static bool SummerBoard_ = false;
 static bool SpringBoard_;
 
 static UIImage *(*_UIApplicationImageWithName)(NSString *name);
@@ -240,7 +263,7 @@ static unsigned $getScale$(NSString *path) {
 }
 
 static NSArray *$useScale$(NSArray *files, bool use = true) {
-    if (Scale_ == 0) {
+    if (use && Scale_ == 0) {
         UIScreen *screen([UIScreen mainScreen]);
         if ([screen respondsToSelector:@selector(scale)])
             Scale_ = [screen scale];
@@ -262,8 +285,7 @@ static NSArray *$useScale$(NSArray *files, bool use = true) {
         if (use) {
             if (Scale_ == 2) {
                 [scaled addObject:[NSString stringWithFormat:@"%@@2x~%@.%@", base, idiom, extension]];
-                if (!IsWild_)
-                    [scaled addObject:[NSString stringWithFormat:@"%@@2x.%@", base, extension]];
+                [scaled addObject:[NSString stringWithFormat:@"%@@2x.%@", base, extension]];
             }
 
             [scaled addObject:[NSString stringWithFormat:@"%@~%@.%@", base, idiom, extension]];
@@ -271,19 +293,20 @@ static NSArray *$useScale$(NSArray *files, bool use = true) {
             // if (!IsWild_) <- support old themes
             [scaled addObject:file];
         } else if ([base hasSuffix: @"@2x"]) {
-            [scaled addObject:[NSString stringWithFormat:@"%@~iphone.%@", base, extension]];
+            [scaled addObject:[NSString stringWithFormat:@"%@~%@.%@", base, idiom, extension]];
             [scaled addObject:file];
 
-            NSString *rest([base substringWithRange:NSMakeRange(0, [base length] - 3)]);
-            [scaled addObject:[NSString stringWithFormat:@"%@~iphone.%@", rest, extension]];
-            [scaled addObject:[rest stringByAppendingPathExtension:extension]];
+            // XXX: this actually can't be used, as the person loading the file doesn't realize that the @2x changed
+            /*NSString *rest([base substringWithRange:NSMakeRange(0, [base length] - 3)]);
+            [scaled addObject:[NSString stringWithFormat:@"%@~%@.%@", rest, idiom, extension]];
+            [scaled addObject:[rest stringByAppendingPathExtension:extension]];*/
         } else {
             // XXX: this code isn't really complete
 
             [scaled addObject:file];
 
-            if ([base hasSuffix:@"~iphone"])
-                [scaled addObject:[[base substringWithRange:NSMakeRange(0, [base length] - 7)] stringByAppendingPathExtension:extension]];
+            if ([base hasSuffix:[NSString stringWithFormat:@"~%@", idiom]])
+                [scaled addObject:[[base substringWithRange:NSMakeRange(0, [base length] - 1 - [idiom length])] stringByAppendingPathExtension:extension]];
         }
     }
 
@@ -291,8 +314,13 @@ static NSArray *$useScale$(NSArray *files, bool use = true) {
 }
 
 static NSString *$getTheme$(NSArray *files, NSArray *themes = Themes_) {
-    if (NSString *path = [Themed_ objectForKey:files])
-        return reinterpret_cast<id>(path) == [NSNull null] ? nil : path;
+    // XXX: this is not reasonable; OMG
+    id key(files);
+
+    @synchronized (Themed_) {
+        if (NSString *path = [Themed_ objectForKey:key])
+            return reinterpret_cast<id>(path) == [NSNull null] ? nil : path;
+    }
 
     if (Debug_)
         NSLog(@"WB:Debug: %@", [files description]);
@@ -302,14 +330,20 @@ static NSString *$getTheme$(NSArray *files, NSArray *themes = Themes_) {
     for (NSString *theme in Themes_)
         for (NSString *file in files) {
             path = [NSString stringWithFormat:@"%@/%@", theme, file];
-            if ([Manager_ fileExistsAtPath:path])
+            if ([Manager_ fileExistsAtPath:path]) {
+                if ([[Manager_ pathContentOfSymbolicLinkAtPath:path] isEqualToString:@"/"])
+                    path = nil;
                 goto set;
+            }
         }
 
     path = nil;
   set:
 
-    [Themed_ setObject:(path == nil ? [NSNull null] : reinterpret_cast<id>(path)) forKey:files];
+    @synchronized (Themed_) {
+        [Themed_ setObject:(path == nil ? [NSNull null] : reinterpret_cast<id>(path)) forKey:key];
+    }
+
     return path;
 }
 // }}}
@@ -320,8 +354,14 @@ static NSString *$pathForFile$inBundle$(NSString *file, NSBundle *bundle, bool u
 
     if (identifier != nil)
         [names addObject:[NSString stringWithFormat:@"Bundles/%@/%@", identifier, file]];
-    if (NSString *folder = [[bundle bundlePath] lastPathComponent])
+    if (NSString *folder = [[bundle bundlePath] lastPathComponent]) {
         [names addObject:[NSString stringWithFormat:@"Folders/%@/%@", folder, file]];
+        NSString *base([folder stringByDeletingPathExtension]);
+        if ([base hasSuffix:@"~iphone"])
+            [names addObject:[NSString stringWithFormat:@"Folders/%@.%@/%@", [base substringWithRange:NSMakeRange(0, [base length] - 7)], [folder pathExtension], file]];
+        if ([base hasSuffix:@"~ipad"])
+            [names addObject:[NSString stringWithFormat:@"Folders/%@.%@/%@", [base substringWithRange:NSMakeRange(0, [base length] - 5)], [folder pathExtension], file]];
+    }
     if (ui)
         [names addObject:[NSString stringWithFormat:@"UIImages/%@", file]];
 
@@ -341,6 +381,8 @@ static NSString *$pathForFile$inBundle$(NSString *file, NSBundle *bundle, bool u
         remapResourceName(Four_ ? @"SBDockBG-old.png" : @"SBDockBG.png", @"Dock")
         remapResourceName(@"SBWeatherCelsius.png", @"Icons/Weather")
 
+    [names addObject:[NSString stringWithFormat:@"Fallback/%@", file]];
+
     if (NSString *path = $getTheme$($useScale$(names, ui)))
         return path;
 
@@ -659,45 +701,211 @@ MSInstanceMessageHook2(NSString *, NSBundle, pathForResource,ofType, NSString *,
 }
 // }}}
 
+static void $drawLabel$(NSString *label, CGRect rect, NSString *style, NSString *custom) {
+    bool ellipsis(false);
+    float max = rect.size.width - 11, width;
+  width:
+    width = [(ellipsis ? [label stringByAppendingString:@"..."] : label) sizeWithStyle:style forWidth:320].width;
+
+    if (width > max) {
+        size_t length([label length]);
+        float spacing((width - max) / (length - 1));
+
+        if (spacing > 1.25) {
+            ellipsis = true;
+            label = [label substringToIndex:(length - 1)];
+            goto width;
+        }
+
+        style = [style stringByAppendingString:[NSString stringWithFormat:@"letter-spacing: -%f; ", spacing]];
+    }
+
+    if (ellipsis)
+        label = [label stringByAppendingString:@"..."];
+
+    if (custom != nil)
+        style = [style stringByAppendingString:custom];
+
+    CGSize size = [label sizeWithStyle:style forWidth:rect.size.width];
+    [label drawAtPoint:CGPointMake((rect.size.width - size.width) / 2 + rect.origin.x, rect.origin.y) withStyle:style];
+}
+
 static struct WBStringDrawingState {
     WBStringDrawingState *next_;
+    unsigned count_;
     NSString *base_;
     NSString *info_;
 } *stringDrawingState_;
 
-MSInstanceMessageHook4(CGSize, NSString, drawAtPoint,forWidth,withFont,lineBreakMode, CGPoint, point, float, width, UIFont *, font, int, mode) {
-    if (stringDrawingState_ == NULL || stringDrawingState_->info_ == nil)
-        return MSOldCall(point, width, font, mode);
+extern "C" CGColorSpaceRef CGContextGetFillColorSpace(CGContextRef);
+extern "C" void CGContextGetFillColor(CGContextRef, CGFloat[]);
+
+static NSString *WBColorMarkup() {
+    CGContextRef context(UIGraphicsGetCurrentContext());
+    //NSLog(@"XXX:1:%p", context);
+    if (context == NULL)
+        return @"";
+
+    CGColorSpaceRef space(CGContextGetFillColorSpace(context));
+    //NSLog(@"XXX:2:%p", space);
+    if (space == NULL)
+        return @"";
+
+    size_t number(CGColorSpaceGetNumberOfComponents(space));
+    //NSLog(@"XXX:3:%u", number);
+    if (number == 0)
+        return @"";
+
+    CGFloat components[number + 1];
+    CGContextGetFillColor(context, components);
+
+    CGFloat r, g, b, a;
+
+    switch (number) {
+        case 1:
+            r = components[0];
+            g = components[0];
+            b = components[0];
+            a = components[1];
+        break;
+
+        case 3:
+            r = components[0];
+            g = components[1];
+            b = components[2];
+            a = components[3];
+        break;
+
+        default:
+            return @"";
+    }
 
-    NSString *info([Info_ objectForKey:stringDrawingState_->info_]);
-    if (info == nil)
-        return MSOldCall(point, width, font, mode);
+    return [NSString stringWithFormat:@"color: rgba(%g, %g, %g, %g)", r * 255, g * 255, b * 255, a];
+}
+
+MSInstanceMessageHook6(CGSize, NSString, drawAtPoint,forWidth,withFont,lineBreakMode,letterSpacing,includeEmoji, CGPoint, point, float, width, UIFont *, font, UILineBreakMode, mode, float, spacing, BOOL, emoji) {
+    //NSLog(@"XXX: @\"%@\" %g", self, spacing);
+
+    WBStringDrawingState *state(stringDrawingState_);
+    if (state == NULL)
+        return MSOldCall(point, width, font, mode, spacing, emoji);
+
+    if (--state->count_ == 0)
+        stringDrawingState_ = state->next_;
+    if (state->info_ == nil)
+        return MSOldCall(point, width, font, mode, spacing, emoji);
 
-    NSString *base(stringDrawingState_->base_ ?: @"");
-    stringDrawingState_ = stringDrawingState_->next_;
+    NSString *info([Info_ objectForKey:state->info_]);
+    if (info == nil)
+        return MSOldCall(point, width, font, mode, spacing, emoji);
 
-    [self drawAtPoint:point withStyle:[NSString stringWithFormat:@"%@;%@;%@", [font markupDescription], base, info]];
+    NSString *base(state->base_ ?: @"");
+    NSString *extra([NSString stringWithFormat:@"letter-spacing: %gpx", spacing]);
+    [self drawAtPoint:point withStyle:[NSString stringWithFormat:@"%@;%@;%@;%@;%@", [font markupDescription], WBColorMarkup(), extra, base, info]];
     return CGSizeZero;
 }
 
-MSInstanceMessageHook2(CGSize, NSString, drawAtPoint,withFont, CGPoint, point, UIFont *, font) {
-    if (stringDrawingState_ == NULL || stringDrawingState_->info_ == nil)
-        return MSOldCall(point, font);
+extern "C" NSString *NSStringFromCGRect(CGRect rect);
+
+MSInstanceMessageHook7(CGSize, NSString, _drawInRect,withFont,lineBreakMode,alignment,lineSpacing,includeEmoji,truncationRect, CGRect, rect, UIFont *, font, UILineBreakMode, mode, UITextAlignment, alignment, float, spacing, BOOL, emoji, CGRect, truncation) {
+    //NSLog(@"XXX: &\"%@\" %@ \"%@\" %u %u %g %u %@", self, NSStringFromCGRect(rect), font, mode, alignment, spacing, emoji, NSStringFromCGRect(truncation));
+
+    WBStringDrawingState *state(stringDrawingState_);
+    if (state == NULL)
+        return MSOldCall(rect, font, mode, alignment, spacing, emoji, truncation);
 
-    NSString *info([Info_ objectForKey:stringDrawingState_->info_]);
+    if (--state->count_ == 0)
+        stringDrawingState_ = state->next_;
+    if (state->info_ == nil)
+        return MSOldCall(rect, font, mode, alignment, spacing, emoji, truncation);
+
+    NSString *info([Info_ objectForKey:state->info_]);
     if (info == nil)
-        return MSOldCall(point, font);
+        return MSOldCall(rect, font, mode, alignment, spacing, emoji, truncation);
 
-    NSString *base(stringDrawingState_->base_ ?: @"");
-    stringDrawingState_ = stringDrawingState_->next_;
+    NSString *textAlign;
+    switch (alignment) {
+        default:
+        case UITextAlignmentLeft:
+            textAlign = @"left";
+            break;
+        case UITextAlignmentCenter:
+            textAlign = @"center";
+            break;
+        case UITextAlignmentRight:
+            textAlign = @"right";
+            break;
+    }
+
+    NSString *base(state->base_ ?: @"");
+    NSString *extra([NSString stringWithFormat:@"text-align: %@", textAlign]);
+
+    if (true)
+        $drawLabel$(self, rect, [NSString stringWithFormat:@"%@;%@;%@", [font markupDescription], WBColorMarkup(), base], info);
+    else
+        [self drawInRect:rect withStyle:[NSString stringWithFormat:@"%@;%@;%@;%@;%@", [font markupDescription], WBColorMarkup(), extra, base, info]];
 
-    [self drawAtPoint:point withStyle:[NSString stringWithFormat:@"%@;%@;%@", [font markupDescription], base, info]];
     return CGSizeZero;
 }
 
+MSInstanceMessageHook4(CGSize, NSString, sizeWithFont,forWidth,lineBreakMode,letterSpacing, UIFont *, font, float, width, UILineBreakMode, mode, float, spacing) {
+    //NSLog(@"XXX: #\"%@\" \"%@\" %g %u %g", self, font, width, mode, spacing);
+
+    WBStringDrawingState *state(stringDrawingState_);
+    if (state == NULL)
+        return MSOldCall(font, width, mode, spacing);
+
+    if (--state->count_ == 0)
+        stringDrawingState_ = state->next_;
+    if (state->info_ == nil)
+        return MSOldCall(font, width, mode, spacing);
+
+    NSString *info([Info_ objectForKey:state->info_]);
+    if (info == nil)
+        return MSOldCall(font, width, mode, spacing);
+
+    NSString *base(state->base_ ?: @"");
+    NSString *extra([NSString stringWithFormat:@"letter-spacing: %gpx", spacing]);
+    return [self sizeWithStyle:[NSString stringWithFormat:@"%@;%@;%@;%@;%@", [font markupDescription], WBColorMarkup(), extra, base, info] forWidth:width];
+}
+
+MSInstanceMessageHook1(CGSize, NSString, sizeWithFont, UIFont *, font) {
+    //NSLog(@"XXX: ?\"%@\"", self);
+
+    WBStringDrawingState *state(stringDrawingState_);
+    if (state == NULL)
+        return MSOldCall(font);
+
+    if (--state->count_ == 0)
+        stringDrawingState_ = state->next_;
+    if (state->info_ == nil)
+        return MSOldCall(font);
+
+    NSString *info([Info_ objectForKey:state->info_]);
+    if (info == nil)
+        return MSOldCall(font);
+
+    NSString *base(state->base_ ?: @"");
+    return [self sizeWithStyle:[NSString stringWithFormat:@"%@;%@;%@;%@", [font markupDescription], WBColorMarkup(), base, info] forWidth:65535];
+}
+
+MSClassMessageHook2(UIImage *, SBIconAccessoryImage, checkoutAccessoryImageForIcon,location, id, icon, int, location) {
+    if ([self _imageClassForIcon:icon location:location] != $SBIconBadgeImage)
+        return MSOldCall(icon, location);
+
+    WBStringDrawingState badgeState = {NULL, -1, @""
+    , @"BadgeStyle"};
+
+    stringDrawingState_ = &badgeState;
+
+    UIImage *image(MSOldCall(icon, location));
+
+    stringDrawingState_ = NULL;
+    return image;
+}
+
 MSInstanceMessageHook1(UIImage *, SBIconBadgeFactory, checkoutBadgeImageForText, NSString *, text) {
-    WBStringDrawingState badgeState = {NULL, @""
-        "color: white;"
+    WBStringDrawingState badgeState = {NULL, -1, @""
     , @"BadgeStyle"};
 
     stringDrawingState_ = &badgeState;
@@ -709,14 +917,14 @@ MSInstanceMessageHook1(UIImage *, SBIconBadgeFactory, checkoutBadgeImageForText,
 }
 
 MSInstanceMessageHook1(UIImage *, SBCalendarApplicationIcon, generateIconImage, int, type) {
-    WBStringDrawingState dayState = {NULL, @""
-        "color: white;"
+    WBStringDrawingState dayState = {NULL, 2, @""
         // XXX: this is only correct on an iPod dock
         "text-shadow: rgba(0, 0, 0, 0.2) -1px -1px 2px;"
     , @"CalendarIconDayStyle"};
 
-    WBStringDrawingState dateState = {&dayState, @""
-        "color: #333333;"
+    WBStringDrawingState sizeState = {&dayState, 7, nil, nil};
+
+    WBStringDrawingState dateState = {&sizeState, 2, @""
     , @"CalendarIconDateStyle"};
 
     stringDrawingState_ = &dateState;
@@ -727,6 +935,18 @@ MSInstanceMessageHook1(UIImage *, SBCalendarApplicationIcon, generateIconImage,
     return image;
 }
 
+MSInstanceMessageHook1(UIImage *, UIStatusBarTimeItemView, contentsImageForStyle, int, style) {
+    WBStringDrawingState timeState = {NULL, 0, @""
+    , @"TimeStyle"};
+
+    stringDrawingState_ = &timeState;
+
+    UIImage *image(MSOldCall(style));
+
+    stringDrawingState_ = NULL;
+    return image;
+}
+
 MSHook(void, SBCalendarIconContentsView$drawRect$, SBCalendarIconContentsView *self, SEL sel, CGRect rect) {
     NSBundle *bundle([NSBundle mainBundle]);
 
@@ -837,6 +1057,7 @@ MSHook(void, SBStatusBarContentsView$didMoveToSuperview, UIView *self, SEL sel)
 static NSArray *Wallpapers_;
 static bool Papered_;
 static bool Docked_;
+static bool SMSBackgrounded_;
 static NSString *WallpaperFile_;
 static UIImageView *WallpaperImage_;
 static UIWebDocumentView *WallpaperPage_;
@@ -910,15 +1131,6 @@ MSInstanceMessageHook0(id, SBUIController, init) {
         IsWild_ = machine != NULL && strncmp(machine, "iPad", 4) == 0;
     }
 
-    BOOL (*GSSystemHasCapability)(CFStringRef) = reinterpret_cast<BOOL (*)(CFStringRef)>(dlsym(RTLD_DEFAULT, "GSSystemHasCapability"));
-
-    if ([Info_ objectForKey:@"UndockedIconLabels"] == nil)
-        [Info_ setObject:[NSNumber numberWithBool:(
-            !(paper != nil || GSSystemHasCapability != NULL && GSSystemHasCapability(CFSTR("homescreen-wallpaper"))) ||
-            [Info_ objectForKey:@"DockedIconLabelStyle"] != nil ||
-            [Info_ objectForKey:@"UndockedIconLabelStyle"] != nil
-        )] forKey:@"UndockedIconLabels"];
-
     if (Debug_)
         NSLog(@"WB:Debug:Info = %@", [Info_ description]);
 
@@ -1027,7 +1239,7 @@ MSInstanceMessageHook0(id, SBUIController, init) {
         if (NSString *path = $getTheme$([NSArray arrayWithObject:@"Wallpaper.html"], themes)) {
             CGRect bounds = [indirect bounds];
 
-            UIWebDocumentView *view([[[UIWebDocumentView alloc] initWithFrame:bounds] autorelease]);
+            UIWebDocumentView *view([[[$UIWebDocumentView alloc] initWithFrame:bounds] autorelease]);
             [view setAutoresizes:true];
 
             WallpaperPage_ = [view retain];
@@ -1050,7 +1262,7 @@ MSInstanceMessageHook0(id, SBUIController, init) {
         if ([Manager_ fileExistsAtPath:html]) {
             CGRect bounds = [indirect bounds];
 
-            UIWebDocumentView *view([[[UIWebDocumentView alloc] initWithFrame:bounds] autorelease]);
+            UIWebDocumentView *view([[[$UIWebDocumentView alloc] initWithFrame:bounds] autorelease]);
             [view setAutoresizes:true];
 
             NSURL *url = [NSURL fileURLWithPath:html];
@@ -1081,7 +1293,7 @@ MSHook(void, SBAwayView$updateDesktopImage$, SBAwayView *self, SEL sel, UIImage
     if (path != nil) {
         CGRect bounds = [self bounds];
 
-        UIWebDocumentView *view([[[UIWebDocumentView alloc] initWithFrame:bounds] autorelease]);
+        UIWebDocumentView *view([[[$UIWebDocumentView alloc] initWithFrame:bounds] autorelease]);
         [view setAutoresizes:true];
 
         if (WallpaperPage_ != nil)
@@ -1135,7 +1347,7 @@ extern "C" NSString *UIStyleStringFromColor(CGColorRef);*/
 
 WBDelegate(time_)
 
-- (CGSize) drawAtPoint:(CGPoint)point forWidth:(float)width withFont:(UIFont *)font lineBreakMode:(int)mode {
+- (CGSize) drawAtPoint:(CGPoint)point forWidth:(float)width withFont:(UIFont *)font lineBreakMode:(UILineBreakMode)mode {
     if (NSString *custom = [Info_ objectForKey:@"TimeStyle"]) {
         BOOL &_mode(MSHookIvar<BOOL>(view_, "_mode"));;
 
@@ -1182,7 +1394,7 @@ WBDelegate(time_)
 
 WBDelegate(badge_)
 
-- (CGSize) drawAtPoint:(CGPoint)point forWidth:(float)width withFont:(UIFont *)font lineBreakMode:(int)mode {
+- (CGSize) drawAtPoint:(CGPoint)point forWidth:(float)width withFont:(UIFont *)font lineBreakMode:(UILineBreakMode)mode {
     if (NSString *custom = [Info_ objectForKey:@"BadgeStyle"]) {
         [badge_ drawAtPoint:point withStyle:[NSString stringWithFormat:@""
             "font-family: Helvetica; "
@@ -1218,8 +1430,12 @@ MSInstanceMessageHook0(id, SBIcon, initWithDefaultSize) {
         if (NSNumber *number = [Info_ objectForKey:@"IconAlpha"]) {
             // XXX: note: this is overridden above, which is silly
             float alpha([number floatValue]);
-            [self setIconImageAlpha:alpha];
-            [self setIconLabelAlpha:alpha];
+            if ([self respondsToSelector:@selector(setIconImageAlpha:)])
+                [self setIconImageAlpha:alpha];
+            if ([self respondsToSelector:@selector(setIconLabelAlpha:)])
+                [self setIconLabelAlpha:alpha];
+            if ([self respondsToSelector:@selector(setAlpha:)])
+                [self setAlpha:alpha];
         }
     } return self;
 }
@@ -1299,6 +1515,7 @@ MSHook(void, SBStatusBarTimeView$drawRect$, SBStatusBarTimeView *self, SEL sel,
 @interface UIView (WinterBoard)
 - (bool) wb$isWBImageView;
 - (void) wb$logHierarchy;
+- (void) wb$setBackgroundColor:(UIColor *)color;
 @end
 
 @implementation UIView (WinterBoard)
@@ -1311,6 +1528,12 @@ MSHook(void, SBStatusBarTimeView$drawRect$, SBStatusBarTimeView *self, SEL sel,
     WBLogHierarchy(self);
 }
 
+- (void) wb$setBackgroundColor:(UIColor *)color {
+    [self setBackgroundColor:color];
+    for (UIView *child in [self subviews])
+        [child wb$setBackgroundColor:color];
+}
+
 @end
 
 @interface WBImageView : UIImageView {
@@ -1342,39 +1565,70 @@ MSHook(void, SBStatusBarTimeView$drawRect$, SBStatusBarTimeView *self, SEL sel,
 
 @end
 
-MSHook(void, SBIconList$setFrame$, SBIconList *self, SEL sel, CGRect frame) {
+static void SBIconList$updateFrames$(SBIconList *self) {
     NSArray *subviews([self subviews]);
     WBImageView *view([subviews count] == 0 ? nil : [subviews objectAtIndex:0]);
     if (view != nil && [view wb$isWBImageView])
         [view wb$updateFrame];
+}
+
+MSHook(void, SBIconList$didMoveToSuperview, SBIconList *self, SEL sel) {
+    SBIconList$updateFrames$(self);
+    _SBIconList$didMoveToSuperview(self, sel);
+}
+
+MSHook(void, SBIconList$setFrame$, SBIconList *self, SEL sel, CGRect frame) {
+    SBIconList$updateFrames$(self);
     _SBIconList$setFrame$(self, sel, frame);
 }
 
-MSHook(void, SBIconController$noteNumberOfIconListsChanged, SBIconController *self, SEL sel) {
-    SBIconModel *&_iconModel(MSHookIvar<SBIconModel *>(self, "_iconModel"));
-    NSArray *lists([_iconModel iconLists]);
+static void $addPerPageView$(unsigned i, UIView *list) {
+    NSString *path($getTheme$([NSArray arrayWithObject:[NSString stringWithFormat:@"Page%u.png", i]]));
+    if (path == nil)
+        return;
+
+    NSArray *subviews([list subviews]);
+
+    WBImageView *view([subviews count] == 0 ? nil : [subviews objectAtIndex:0]);
+    if (view == nil || ![view wb$isWBImageView]) {
+        view = [[[WBImageView alloc] init] autorelease];
+        [list insertSubview:view atIndex:0];
+    }
+
+    UIImage *image([UIImage imageWithContentsOfFile:path]);
+
+    CGRect frame([view frame]);
+    frame.size = [image size];
+    [view setFrame:frame];
 
+    [view setImage:image];
+    [view wb$updateFrame];
+}
+
+static void $addPerPageViews$(NSArray *lists) {
     for (unsigned i(0), e([lists count]); i != e; ++i)
-        if (NSString *path = $getTheme$([NSArray arrayWithObject:[NSString stringWithFormat:@"Page%u.png", i]])) {
-            SBIconList *list([lists objectAtIndex:i]);
-            NSArray *subviews([list subviews]);
-
-            WBImageView *view([subviews count] == 0 ? nil : [subviews objectAtIndex:0]);
-            if (view == nil || ![view wb$isWBImageView]) {
-                view = [[[WBImageView alloc] init] autorelease];
-                [list insertSubview:view atIndex:0];
-            }
+        $addPerPageView$(i, [lists objectAtIndex:i]);
+}
 
-            UIImage *image([UIImage imageWithContentsOfFile:path]);
+MSInstanceMessageHook0(void, SBIconController, updateNumberOfRootIconLists) {
+    NSArray *&_rootIconLists(MSHookIvar<NSArray *>(self, "_rootIconLists"));
+    $addPerPageViews$(_rootIconLists);
+    return MSOldCall();
+}
 
-            CGRect frame([view frame]);
-            frame.size = [image size];
-            [view setFrame:frame];
+MSInstanceMessageHook0(void, SBIconContentView, layoutSubviews) {
+    MSOldCall();
 
-            [view setImage:image];
-            [view wb$updateFrame];
-        }
+    if (SBIconController *controller = [$SBIconController sharedInstance]) {
+        UIView *&_dockContainerView(MSHookIvar<UIView *>(controller, "_dockContainerView"));
+        if (&_dockContainerView != NULL)
+            [[_dockContainerView superview] bringSubviewToFront:_dockContainerView];
+    }
+}
 
+MSHook(void, SBIconController$noteNumberOfIconListsChanged, SBIconController *self, SEL sel) {
+    SBIconModel *&_iconModel(MSHookIvar<SBIconModel *>(self, "_iconModel"));
+    $addPerPageViews$([_iconModel iconLists]);
     return _SBIconController$noteNumberOfIconListsChanged(self, sel);
 }
 
@@ -1386,6 +1640,20 @@ MSHook(id, SBIconLabel$initWithSize$label$, SBIconLabel *self, SEL sel, CGSize s
 }
 
 MSHook(void, SBIconLabel$setInDock$, SBIconLabel *self, SEL sel, BOOL docked) {
+    static bool gssc(false);
+    if (!gssc) {
+        BOOL (*GSSystemHasCapability)(CFStringRef) = reinterpret_cast<BOOL (*)(CFStringRef)>(dlsym(RTLD_DEFAULT, "GSSystemHasCapability"));
+        Papered_ |= GSSystemHasCapability != NULL && GSSystemHasCapability(CFSTR("homescreen-wallpaper"));
+        gssc = true;
+
+        if ([Info_ objectForKey:@"UndockedIconLabels"] == nil)
+            [Info_ setObject:[NSNumber numberWithBool:(
+                !Papered_ ||
+                [Info_ objectForKey:@"DockedIconLabelStyle"] != nil ||
+                [Info_ objectForKey:@"UndockedIconLabelStyle"] != nil
+            )] forKey:@"UndockedIconLabels"];
+    }
+
     id &_label(MSHookIvar<id>(self, "_label"));
     if (![Info_ wb$boolForKey:@"UndockedIconLabels"])
         docked = true;
@@ -1444,9 +1712,7 @@ MSInstanceMessageHook2(CGSize, WebCoreFrameBridge, renderedSizeOfNode,constraine
 }
 // }}}
 
-MSInstanceMessageHook1(void, SBIconLabel, drawRect, CGRect, rect) {
-    CGRect bounds = [self bounds];
-
+MSInstanceMessage1(void, SBIconLabel, drawRect, CGRect, rect) {
     static Ivar drawMoreLegibly = object_getInstanceVariable(self, "_drawMoreLegibly", NULL);
 
     int docked;
@@ -1469,35 +1735,74 @@ MSInstanceMessageHook1(void, SBIconLabel, drawRect, CGRect, rect) {
     else if (docked)
         style = [style stringByAppendingString:@"text-shadow: rgba(0, 0, 0, 0.5) 0px -1px 0px; "];
 
-    bool ellipsis(false);
-    float max = [self frame].size.width - 11, width;
-  width:
-    width = [(ellipsis ? [label stringByAppendingString:@"..."] : label) sizeWithStyle:style forWidth:320].width;
+    NSString *custom([Info_ objectForKey:(docked ? @"DockedIconLabelStyle" : @"UndockedIconLabelStyle")]);
 
-    if (width > max) {
-        size_t length([label length]);
-        float spacing((width - max) / (length - 1));
+    $drawLabel$(label, [self bounds], style, custom);
+}
 
-        if (spacing > 1.25) {
-            ellipsis = true;
-            label = [label substringToIndex:(length - 1)];
-            goto width;
-        }
+MSInstanceMessage0(CGImageRef, SBIconLabel, buildLabelImage) {
+    bool docked((MSHookIvar<unsigned>(self, "_inDock") & 0x2) != 0);
 
-        style = [style stringByAppendingString:[NSString stringWithFormat:@"letter-spacing: -%f; ", spacing]];
-    }
+    WBStringDrawingState labelState = {NULL, 0, @""
+    , docked ? @"DockedIconLabelStyle" : @"UndockedIconLabelStyle"};
 
-    if (ellipsis)
-        label = [label stringByAppendingString:@"..."];
+    stringDrawingState_ = &labelState;
 
-    if (NSString *custom = [Info_ objectForKey:(docked ? @"DockedIconLabelStyle" : @"UndockedIconLabelStyle")])
-        style = [style stringByAppendingString:custom];
+    //NSLog(@"XXX: +");
+    CGImageRef image(MSOldCall());
+    //NSLog(@"XXX: -");
+
+    stringDrawingState_ = NULL;
+    return image;
+}
+
+static bool wb$inDock(id parameters) {
+    return [$objc_getAssociatedObject(parameters, @selector(wb$inDock)) boolValue];
+}
+
+MSInstanceMessage0(NSUInteger, SBIconLabelImageParameters, hash) {
+    return MSOldCall() + (wb$inDock(self) ? 0xdeadbeef : 0xd15ea5e);
+}
 
-    CGSize size = [label sizeWithStyle:style forWidth:bounds.size.width];
-    [label drawAtPoint:CGPointMake((bounds.size.width - size.width) / 2, 0) withStyle:style];
+MSClassMessage2(id, SBIconView, _labelImageParametersForIcon,location, id, icon, int, location) {
+    if (id parameters = MSOldCall(icon, location)) {
+        $objc_setAssociatedObject(parameters, @selector(wb$inDock), [NSNumber numberWithBool:(location == 1)], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+        return parameters;
+    } return nil;
+}
+
+MSClassMessage1(UIImage *, SBIconLabelImage, _drawLabelImageForParameters, id, parameters) {
+    bool docked(wb$inDock(parameters));
+
+    WBStringDrawingState labelState = {NULL, 0, @""
+    , docked ? @"DockedIconLabelStyle" : @"UndockedIconLabelStyle"};
+
+    stringDrawingState_ = &labelState;
+
+    //NSLog(@"XXX: +");
+    UIImage *image(MSOldCall(parameters));
+    //NSLog(@"XXX: -");
+
+    stringDrawingState_ = NULL;
+    return image;
 }
 
 // ChatKit {{{
+MSInstanceMessageHook2(id, CKBalloonView, initWithFrame,delegate, CGRect, frame, id, delegate) {
+    if ((self = MSOldCall(frame, delegate)) != nil) {
+        [self setBackgroundColor:[UIColor clearColor]];
+    } return self;
+}
+
+MSInstanceMessageHook0(BOOL, CKBalloonView, _canUseLayerBackedBalloon) {
+    return SMSBackgrounded_ ? NO : MSOldCall();
+}
+
+MSInstanceMessageHook0(void, CKTranscriptHeaderView, layoutSubviews) {
+    [self wb$setBackgroundColor:[UIColor clearColor]];
+    return MSOldCall();
+}
+
 MSInstanceMessageHook1(void, CKMessageCell, addBalloonView, CKBalloonView *, balloon) {
     MSOldCall(balloon);
     [balloon setBackgroundColor:[UIColor clearColor]];
@@ -1505,15 +1810,18 @@ MSInstanceMessageHook1(void, CKMessageCell, addBalloonView, CKBalloonView *, bal
 
 MSInstanceMessageHook1(void, CKTranscriptCell, setBackgroundColor, UIColor *, color) {
     MSOldCall([UIColor clearColor]);
+    [[self contentView] wb$setBackgroundColor:[UIColor clearColor]];
 }
 
+// iOS >= 5.0
 MSInstanceMessageHook2(id, CKTranscriptCell, initWithStyle,reuseIdentifier, int, style, NSString *, reuse) {
     if ((self = MSOldCall(style, reuse)) != nil) {
         [self setBackgroundColor:[UIColor clearColor]];
-        [[self contentView] setBackgroundColor:[UIColor clearColor]];
+        [[self contentView] wb$setBackgroundColor:[UIColor clearColor]];
     } return self;
 }
 
+// iOS << 5.0
 MSInstanceMessageHook2(id, CKMessageCell, initWithStyle,reuseIdentifier, int, style, NSString *, reuse) {
     if ((self = MSOldCall(style, reuse)) != nil) {
         [self setBackgroundColor:[UIColor clearColor]];
@@ -1543,6 +1851,8 @@ MSInstanceMessageHook0(void, CKTranscriptController, loadView) {
 
     if (NSString *path = $getTheme$($useScale$([NSArray arrayWithObjects:@"SMSBackground.png", @"SMSBackground.jpg", nil])))
         if (UIImage *image = $getImage$(path)) {
+            SMSBackgrounded_ = true;
+
             UIView *&_transcriptTable(MSHookIvar<UIView *>(self, "_transcriptTable"));
             UIView *&_transcriptLayer(MSHookIvar<UIView *>(self, "_transcriptLayer"));
             UIView *table;
@@ -1661,7 +1971,7 @@ MSHook(bool, _Z24GetFileNameForThisActionmPcRb, unsigned long a0, char *a1, bool
                 NSString *path([NSString stringWithFormat:@"%@/UISounds/%@", theme, file]);
                 if ([Manager_ fileExistsAtPath:path]) {
                     strcpy(a1, [path UTF8String]);
-                    continue;
+                    break;
                 }
             }
         }
@@ -1694,7 +2004,7 @@ static void ChangeWallpaper(
 }
 
 #define WBRename(name, sel, imp) \
-    _ ## name ## $ ## imp = MSHookMessage($ ## name, @selector(sel), &$ ## name ## $ ## imp)
+    MSHookMessage($ ## name, @selector(sel), &$ ## name ## $ ## imp, &_ ## name ## $ ## imp)
 
 template <typename Type_>
 static void msset(Type_ &function, MSImageRef image, const char *name) {
@@ -1724,25 +2034,53 @@ MSHook(void *, CGImageReadCreateWithFile, NSString *path, int flag) {
     [pool release];
     return value;
 }
+
+MSHook(void *, CGImageSourceCreateWithFile, NSString *path, NSDictionary *options) {
+    if (Debug_)
+        NSLog(@"WB:Debug: CGImageSourceCreateWithFile(%@, %@)", path, options);
+    NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
+    void *value(_CGImageSourceCreateWithFile([path wb$themedPath], options));
+    [pool release];
+    return value;
+}
+
+MSHook(void *, CGImageSourceCreateWithURL, NSURL *url, NSDictionary *options) {
+    if (Debug_)
+        NSLog(@"WB:Debug: CGImageSourceCreateWithURL(%@, %@)", url, options);
+    NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
+    if ([url isFileURL])
+        url = [NSURL fileURLWithPath:[[url path] wb$themedPath]];
+    void *value(_CGImageSourceCreateWithURL(url, options));
+    [pool release];
+    return value;
+}
 // }}}
 
 static void NSString$drawAtPoint$withStyle$(NSString *self, SEL _cmd, CGPoint point, NSString *style) {
     WKSetCurrentGraphicsContext(UIGraphicsGetCurrentContext());
     if (style == nil || [style length] == 0)
         style = @"font-family: Helvetica; font-size: 12px";
-    return [[WBMarkup sharedMarkup] drawString:self atPoint:point withStyle:style];
+    //NSLog(@"XXX:drawP(%@ | %@)", self, [style stringByReplacingOccurrencesOfString:@"\n" withString:@" "]);
+    [[WBMarkup sharedMarkup] drawString:self atPoint:point withStyle:style];
+}
+
+static void NSString$drawInRect$withStyle$(NSString *self, SEL _cmd, CGRect rect, NSString *style) {
+    WKSetCurrentGraphicsContext(UIGraphicsGetCurrentContext());
+    if (style == nil || [style length] == 0)
+        style = @"font-family: Helvetica; font-size: 12px";
+    //NSLog(@"XXX:drawR(%@ | %@)", self, [style stringByReplacingOccurrencesOfString:@"\n" withString:@" "]);
+    return [[WBMarkup sharedMarkup] drawString:self inRect:rect withStyle:style];
 }
 
 static CGSize NSString$sizeWithStyle$forWidth$(NSString *self, SEL _cmd, NSString *style, float width) {
     if (style == nil || [style length] == 0)
         style = @"font-family: Helvetica; font-size: 12px";
-    return [[WBMarkup sharedMarkup] sizeOfString:self withStyle:style forWidth:width];
+    CGSize size([[WBMarkup sharedMarkup] sizeOfString:self withStyle:style forWidth:width]);
+    //NSLog(@"XXX:size(%@ | %@) = [%g %g]", self, [style stringByReplacingOccurrencesOfString:@"\n" withString:@" "], size.width, size.height);
+    return size;
 }
 
 static void SBInitialize() {
-    class_addMethod($NSString, @selector(drawAtPoint:withStyle:), (IMP) &NSString$drawAtPoint$withStyle$, "v20@0:4{CGPoint=ff}8@16");
-    class_addMethod($NSString, @selector(sizeWithStyle:forWidth:), (IMP) &NSString$sizeWithStyle$forWidth$, "{CGSize=ff}16@0:4@8f12");
-
     if (SummerBoard_) {
         WBRename(SBApplication, pathForIcon, pathForIcon);
         WBRename(SBApplicationIcon, icon, icon);
@@ -1760,9 +2098,20 @@ static void SBInitialize() {
     WBRename(SBDockIconListView, setFrame:, setFrame$);
     MSHookMessage(object_getClass($SBDockIconListView), @selector(shouldShowNewDock), &$SBDockIconListView$shouldShowNewDock, &_SBDockIconListView$shouldShowNewDock);
 
+    if (kCFCoreFoundationVersionNumber < 600 || SummerBoard_)
+        WBRename(SBIconLabel, drawRect:, drawRect$);
+    else if (kCFCoreFoundationVersionNumber < 700) {
+        WBRename(SBIconLabel, buildLabelImage, buildLabelImage);
+    } else {
+        WBRename(SBIconLabelImageParameters, hash, hash);
+        WBRename($SBIconView, _labelImageParametersForIcon:location:, _labelImageParametersForIcon$location$);
+        WBRename($SBIconLabelImage, _drawLabelImageForParameters:, _drawLabelImageForParameters$);
+    }
+
     WBRename(SBIconLabel, initWithSize:label:, initWithSize$label$);
     WBRename(SBIconLabel, setInDock:, setInDock$);
 
+    WBRename(SBIconList, didMoveToSuperview, didMoveToSuperview);
     WBRename(SBIconList, setFrame:, setFrame$);
 
     WBRename(SBIconModel, cacheImageForIcon:, cacheImageForIcon$);
@@ -1791,6 +2140,10 @@ static void SBInitialize() {
 }
 
 MSInitialize {
+    $objc_setAssociatedObject = reinterpret_cast<void (*)(id, void *, id value, objc_AssociationPolicy)>(dlsym(RTLD_DEFAULT, "objc_setAssociatedObject"));
+    $objc_getAssociatedObject = reinterpret_cast<id (*)(id, void *)>(dlsym(RTLD_DEFAULT, "objc_getAssociatedObject"));
+    $objc_removeAssociatedObjects = reinterpret_cast<void (*)(id)>(dlsym(RTLD_DEFAULT, "objc_removeAssociatedObjects"));
+
     NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
 
     NSString *identifier([[NSBundle mainBundle] bundleIdentifier]);
@@ -1805,8 +2158,13 @@ MSInitialize {
     if (NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:[NSString stringWithFormat:@"/User/Library/Preferences/com.saurik.WinterBoard.plist"]]) {
         if (NSNumber *value = [settings objectForKey:@"SummerBoard"])
             SummerBoard_ = [value boolValue];
+        else
+            SummerBoard_ = true;
+
         if (NSNumber *value = [settings objectForKey:@"Debug"])
             Debug_ = [value boolValue];
+        if (NSNumber *value = [settings objectForKey:@"RecordUI"])
+            UIDebug_ = [value boolValue];
 
         NSArray *themes([settings objectForKey:@"Themes"]);
         if (themes == nil)
@@ -1870,12 +2228,23 @@ MSInitialize {
         void *(*CGImageReadCreateWithFile)(NSString *, int);
         msset(CGImageReadCreateWithFile, image, "_CGImageReadCreateWithFile");
         MSHookFunction(CGImageReadCreateWithFile, MSHake(CGImageReadCreateWithFile));
+
+        if (CGImageReadCreateWithFile == NULL) {
+            void *(*CGImageSourceCreateWithFile)(NSString *, NSDictionary *);
+            msset(CGImageSourceCreateWithFile, image, "_CGImageSourceCreateWithFile");
+            MSHookFunction(CGImageSourceCreateWithFile, MSHake(CGImageSourceCreateWithFile));
+
+            void *(*CGImageSourceCreateWithURL)(NSURL *, NSDictionary *);
+            msset(CGImageSourceCreateWithURL, image, "_CGImageSourceCreateWithURL");
+            MSHookFunction(CGImageSourceCreateWithURL, MSHake(CGImageSourceCreateWithURL));
+        }
     }
     // }}}
     // SpringBoard {{{
     if (SpringBoard_) {
         Wallpapers_ = [[NSArray arrayWithObjects:@"Wallpaper.mp4", @"Wallpaper@2x.png", @"Wallpaper@2x.jpg", @"Wallpaper.png", @"Wallpaper.jpg", @"Wallpaper.html", nil] retain];
-        Docked_ = $getTheme$([NSArray arrayWithObjects:@"Dock.png", nil]);
+        Papered_ = $getTheme$(Wallpapers_) != nil;
+        Docked_ = $getTheme$([NSArray arrayWithObjects:@"Dock.png", nil]) != nil;
 
         CFNotificationCenterAddObserver(
             CFNotificationCenterGetDarwinNotifyCenter(),
@@ -1896,6 +2265,10 @@ MSInitialize {
     // }}}
     // UIKit {{{
     if ([NSBundle bundleWithIdentifier:@"com.apple.UIKit"] != nil) {
+        class_addMethod($NSString, @selector(drawAtPoint:withStyle:), (IMP) &NSString$drawAtPoint$withStyle$, "v20@0:4{CGPoint=ff}8@16");
+        class_addMethod($NSString, @selector(drawInRect:withStyle:), (IMP) &NSString$drawInRect$withStyle$, "v28@0:4{CGRect={CGSize=ff}{CGSize=ff}}8@24");
+        class_addMethod($NSString, @selector(sizeWithStyle:forWidth:), (IMP) &NSString$sizeWithStyle$forWidth$, "{CGSize=ff}16@0:4@8f12");
+
         struct nlist nl[6];
         memset(nl, 0, sizeof(nl));
         nl[0].n_un.n_name = (char *) "__UIApplicationImageWithName";
@@ -1917,8 +2290,13 @@ MSInitialize {
     }
     // }}}
 
-    if (Debug_ && [Manager_ fileExistsAtPath:@"/tmp/UIImages"])
-        UIDebug_ = true;
+    if (UIDebug_ && ![Manager_ fileExistsAtPath:@"/tmp/UIImages"]) {
+        NSError *error(nil);
+        if (![Manager_ createDirectoryAtPath:@"/tmp/UIImages" withIntermediateDirectories:NO attributes:[NSDictionary dictionaryWithObjectsAndKeys:
+            [NSNumber numberWithShort:0777], NSFilePosixPermissions,
+        nil] error:&error])
+            NSLog(@"WB:Error: cannot create /tmp/UIImages (%@)", error);
+    }
 
     [pool release];
 }