]> git.saurik.com Git - winterboard.git/blobdiff - Library.mm
Support alignments other than center for text drawing.
[winterboard.git] / Library.mm
index cd2e5d07d92d93d380407fa1de61269e050e5d07..55dfeb02497f51acef549d1198a09262d76c479d 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__\
@@ -103,6 +103,8 @@ bool _itv;
 
 #include <sys/sysctl.h>
 
+#include "WBMarkup.h"
+
 extern "C" void __clear_cache (char *beg, char *end);
 
 @protocol WinterBoard
@@ -113,14 +115,19 @@ Class $MPMoviePlayerController;
 Class $MPVideoView;
 
 MSClassHook(NSBundle)
+MSClassHook(NSString)
 
 MSClassHook(UIImage)
+MSMetaClassHook(UIImage)
 MSClassHook(UINavigationBar)
 MSClassHook(UIToolbar)
 
+MSClassHook(CKBalloonView)
 MSClassHook(CKMessageCell)
 MSClassHook(CKTimestampView)
+MSClassHook(CKTranscriptCell)
 MSClassHook(CKTranscriptController)
+MSClassHook(CKTranscriptHeaderView)
 MSClassHook(CKTranscriptTableView)
 
 MSClassHook(SBApplication)
@@ -128,10 +135,12 @@ MSClassHook(SBApplicationIcon)
 MSClassHook(SBAwayView)
 MSClassHook(SBBookmarkIcon)
 MSClassHook(SBButtonBar)
+MSClassHook(SBCalendarApplicationIcon)
 MSClassHook(SBCalendarIconContentsView)
 MSClassHook(SBDockIconListView)
 MSClassHook(SBIcon)
 MSClassHook(SBIconBadge)
+MSClassHook(SBIconBadgeFactory)
 MSClassHook(SBIconController)
 MSClassHook(SBIconLabel)
 MSClassHook(SBIconList)
@@ -139,13 +148,17 @@ MSClassHook(SBIconModel)
 //MSClassHook(SBImageCache)
 MSClassHook(SBSearchView)
 MSClassHook(SBSearchTableViewCell)
+MSClassHook(SBSlidingAlertDisplay)
 MSClassHook(SBStatusBarContentsView)
 MSClassHook(SBStatusBarController)
 MSClassHook(SBStatusBarOperatorNameView)
 MSClassHook(SBStatusBarTimeView)
 MSClassHook(SBUIController)
+MSClassHook(SBWallpaperView)
 MSClassHook(SBWidgetApplicationIcon)
 
+extern "C" void WKSetCurrentGraphicsContext(CGContextRef);
+
 __attribute__((__constructor__))
 static void MSFixClass() {
     if ($SBIcon == nil)
@@ -185,6 +198,7 @@ static BOOL (*_GSFontGetUseLegacyFontMetrics)();
     (_GSFontGetUseLegacyFontMetrics == NULL ? YES : _GSFontGetUseLegacyFontMetrics())
 
 static bool Debug_ = false;
+static bool UIDebug_ = false;
 static bool Engineer_ = false;
 static bool SummerBoard_ = true;
 static bool SpringBoard_;
@@ -227,11 +241,8 @@ static unsigned $getScale$(NSString *path) {
     return [name hasSuffix:@"@2x"] ? 2 : 1;
 }
 
-static NSString *$getTheme$(NSArray *files, bool rescale = false) {
-    if (NSString *path = [Themed_ objectForKey:files])
-        return reinterpret_cast<id>(path) == [NSNull null] ? nil : path;
-
-    if (rescale && Scale_ == 0) {
+static NSArray *$useScale$(NSArray *files, bool use = true) {
+    if (use && Scale_ == 0) {
         UIScreen *screen([UIScreen mainScreen]);
         if ([screen respondsToSelector:@selector(scale)])
             Scale_ = [screen scale];
@@ -239,6 +250,58 @@ static NSString *$getTheme$(NSArray *files, bool rescale = false) {
             Scale_ = 1;
     }
 
+    if (Scale_ == 1)
+        return files;
+
+    NSString *idiom(IsWild_ ? @"ipad" : @"iphone");
+
+    NSMutableArray *scaled([NSMutableArray arrayWithCapacity:([files count] * 4)]);
+
+    for (NSString *file in files) {
+        NSString *base([file stringByDeletingPathExtension]);
+        NSString *extension([file pathExtension]);
+
+        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:@"%@~%@.%@", base, idiom, extension]];
+
+            // if (!IsWild_) <- support old themes
+            [scaled addObject:file];
+        } else if ([base hasSuffix: @"@2x"]) {
+            [scaled addObject:[NSString stringWithFormat:@"%@~iphone.%@", base, extension]];
+            [scaled addObject:file];
+
+            // 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:@"%@~iphone.%@", rest, 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]];
+        }
+    }
+
+    return scaled;
+}
+
+static NSString *$getTheme$(NSArray *files, NSArray *themes = Themes_) {
+    // 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]);
 
@@ -246,12 +309,6 @@ static NSString *$getTheme$(NSArray *files, bool rescale = false) {
 
     for (NSString *theme in Themes_)
         for (NSString *file in files) {
-            if (rescale && /*$getScale$(file) == 1 &&*/ Scale_ == 2) {
-                path = [NSString stringWithFormat:@"%@/%@@2x.%@", theme, [file stringByDeletingPathExtension], [file pathExtension]];
-                if ([Manager_ fileExistsAtPath:path])
-                    goto set;
-            }
-
             path = [NSString stringWithFormat:@"%@/%@", theme, file];
             if ([Manager_ fileExistsAtPath:path])
                 goto set;
@@ -260,7 +317,10 @@ static NSString *$getTheme$(NSArray *files, bool rescale = false) {
     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;
 }
 // }}}
@@ -271,8 +331,12 @@ 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 (ui)
         [names addObject:[NSString stringWithFormat:@"UIImages/%@", file]];
 
@@ -292,7 +356,9 @@ static NSString *$pathForFile$inBundle$(NSString *file, NSBundle *bundle, bool u
         remapResourceName(Four_ ? @"SBDockBG-old.png" : @"SBDockBG.png", @"Dock")
         remapResourceName(@"SBWeatherCelsius.png", @"Icons/Weather")
 
-    if (NSString *path = $getTheme$(names, ui))
+    [names addObject:[NSString stringWithFormat:@"Fallback/%@", file]];
+
+    if (NSString *path = $getTheme$($useScale$(names, ui)))
         return path;
 
     return nil;
@@ -610,6 +676,138 @@ MSInstanceMessageHook2(NSString *, NSBundle, pathForResource,ofType, NSString *,
 }
 // }}}
 
+static struct WBStringDrawingState {
+    WBStringDrawingState *next_;
+    unsigned count_;
+    NSString *base_;
+    NSString *info_;
+} *stringDrawingState_;
+
+MSInstanceMessageHook6(CGSize, NSString, drawAtPoint,forWidth,withFont,lineBreakMode,letterSpacing,includeEmoji, CGPoint, point, float, width, UIFont *, font, int, mode, float, spacing, BOOL, emoji) {
+    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 *info([Info_ objectForKey:state->info_]);
+    if (info == nil)
+        return MSOldCall(point, width, font, mode, spacing, emoji);
+
+    NSString *base(state->base_ ?: @"");
+    NSString *extra([NSString stringWithFormat:@"letter-spacing: %gpx", spacing]);
+    [self drawAtPoint:point withStyle:[NSString stringWithFormat:@"%@;%@;%@;%@", [font markupDescription], extra, base, info]];
+    return CGSizeZero;
+}
+
+MSInstanceMessageHook7(CGSize, NSString, _drawInRect,withFont,lineBreakMode,alignment,lineSpacing,includeEmoji,truncationRect, CGRect, rect, UIFont *, font, int, mode, int, alignment, float, spacing, BOOL, emoji, CGRect, truncation) {
+    WBStringDrawingState *state(stringDrawingState_);
+    if (state == NULL)
+        return MSOldCall(rect, font, mode, alignment, spacing, emoji, truncation);
+
+    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(rect, font, mode, alignment, spacing, emoji, truncation);
+
+    NSString *textAlign;
+    switch (alignment) {
+        default:
+        case WebTextAlignmentLeft:
+            textAlign = @"left";
+            break;
+        case WebTextAlignmentCenter:
+            textAlign = @"center";
+            break;
+        case WebTextAlignmentRight:
+            textAlign = @"right";
+            break;
+    }
+
+    NSString *base(state->base_ ?: @"");
+    NSString *extra([NSString stringWithFormat:@"text-align: %@", textAlign]);
+    [self drawInRect:rect withStyle:[NSString stringWithFormat:@"%@;%@;%@;%@", [font markupDescription], extra, base, info]];
+    return CGSizeZero;
+}
+
+MSInstanceMessageHook4(CGSize, NSString, sizeWithFont,forWidth,lineBreakMode,letterSpacing, UIFont *, font, float, width, int, mode, float, 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], extra, base, info] forWidth:width];
+}
+
+MSInstanceMessageHook1(CGSize, NSString, sizeWithFont, UIFont *, font) {
+    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], base, info] forWidth:65535];
+}
+
+MSInstanceMessageHook1(UIImage *, SBIconBadgeFactory, checkoutBadgeImageForText, NSString *, text) {
+    WBStringDrawingState badgeState = {NULL, 1, @""
+        "color: white;"
+    , @"BadgeStyle"};
+
+    stringDrawingState_ = &badgeState;
+
+    UIImage *image(MSOldCall(text));
+
+    stringDrawingState_ = NULL;
+    return image;
+}
+
+MSInstanceMessageHook1(UIImage *, SBCalendarApplicationIcon, generateIconImage, int, type) {
+    WBStringDrawingState dayState = {NULL, 2, @""
+        "color: white;"
+        // XXX: this is only correct on an iPod dock
+        "text-shadow: rgba(0, 0, 0, 0.2) -1px -1px 2px;"
+    , @"CalendarIconDayStyle"};
+
+    WBStringDrawingState sizeState = {&dayState, 7, nil, nil};
+
+    WBStringDrawingState dateState = {&sizeState, 2, @""
+        "color: #333333;"
+    , @"CalendarIconDateStyle"};
+
+    stringDrawingState_ = &dateState;
+
+    UIImage *image(MSOldCall(type));
+
+    stringDrawingState_ = NULL;
+    return image;
+}
+
 MSHook(void, SBCalendarIconContentsView$drawRect$, SBCalendarIconContentsView *self, SEL sel, CGRect rect) {
     NSBundle *bundle([NSBundle mainBundle]);
 
@@ -717,17 +915,10 @@ MSHook(void, SBStatusBarContentsView$didMoveToSuperview, UIView *self, SEL sel)
     _SBStatusBarContentsView$didMoveToSuperview(self, sel);
 }
 
-MSHook(UIImage *, UIImage$defaultDesktopImage, UIImage *self, SEL sel) {
-    if (Debug_)
-        NSLog(@"WB:Debug:DefaultDesktopImage");
-    if (NSString *path = $getTheme$([NSArray arrayWithObjects:@"LockBackground.png", @"LockBackground.jpg", nil]))
-        return [UIImage imageWithContentsOfFile:path];
-    return _UIImage$defaultDesktopImage(self, sel);
-}
-
 static NSArray *Wallpapers_;
 static bool Papered_;
 static bool Docked_;
+static bool SMSBackgrounded_;
 static NSString *WallpaperFile_;
 static UIImageView *WallpaperImage_;
 static UIWebDocumentView *WallpaperPage_;
@@ -749,13 +940,43 @@ static UIImage *$getImage$(NSString *path) {
     return image;
 }
 
+static UIImage *$getDefaultDesktopImage$() {
+    if (NSString *path = $getTheme$($useScale$([NSArray arrayWithObjects:@"LockBackground.png", @"LockBackground.jpg", nil])))
+        return $getImage$(path);
+    return nil;
+}
+
+MSClassMessageHook0(UIImage *, UIImage, defaultDesktopImage) {
+    return $getDefaultDesktopImage$() ?: MSOldCall();
+}
+
+MSInstanceMessageHook0(UIImage *, SBSlidingAlertDisplay, _defaultDesktopImage) {
+    return $getDefaultDesktopImage$() ?: MSOldCall();
+}
+
+MSInstanceMessageHook0(void, SBWallpaperView, resetCurrentImageToWallpaper) {
+    for (UIView *parent([self superview]); parent != nil; parent = [parent superview])
+        if ([parent isKindOfClass:$SBSlidingAlertDisplay]) {
+            if (UIImage *image = $getDefaultDesktopImage$()) {
+                [self setImage:image];
+                return;
+            }
+
+            break;
+        }
+
+    MSOldCall();
+}
+
 // %hook -[SBUIController init] {{{
 MSInstanceMessageHook0(id, SBUIController, init) {
     self = MSOldCall();
     if (self == nil)
         return nil;
 
-    NSString *paper($getTheme$(Wallpapers_, true));
+    NSString *paper($getTheme$(Wallpapers_));
+    if (paper != nil)
+        paper = [paper stringByDeletingLastPathComponent];
 
     {
         size_t size;
@@ -771,15 +992,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]);
 
@@ -837,8 +1049,10 @@ MSInstanceMessageHook0(id, SBUIController, init) {
     _release(WallpaperPage_);
     _release(WallpaperURL_);
 
-    if (NSString *path = paper) {
-        if ([path hasSuffix:@".mp4"]) {
+    if (paper != nil) {
+        NSArray *themes([NSArray arrayWithObject:paper]);
+
+        if (NSString *path = $getTheme$([NSArray arrayWithObject:@"Wallpaper.mp4"], themes)) {
 #if UseAVController
             NSError *error;
 
@@ -873,7 +1087,7 @@ MSInstanceMessageHook0(id, SBUIController, init) {
             [indirect addSubview:video];
         }
 
-        if ([path hasSuffix:@".png"] || [path hasSuffix:@".jpg"]) {
+        if (NSString *path = $getTheme$($useScale$([NSArray arrayWithObjects:@"Wallpaper.png", @"Wallpaper.jpg", nil]), themes)) {
             if (UIImage *image = $getImage$(path)) {
                 WallpaperFile_ = [path retain];
                 WallpaperImage_ = [[UIImageView alloc] initWithImage:image];
@@ -883,7 +1097,7 @@ MSInstanceMessageHook0(id, SBUIController, init) {
             }
         }
 
-        if ([path hasSuffix:@".html"]) {
+        if (NSString *path = $getTheme$([NSArray arrayWithObject:@"Wallpaper.html"], themes)) {
             CGRect bounds = [indirect bounds];
 
             UIWebDocumentView *view([[[UIWebDocumentView alloc] initWithFrame:bounds] autorelease]);
@@ -1158,6 +1372,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)
@@ -1170,6 +1385,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 {
@@ -1245,12 +1466,29 @@ 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;
+
     if (_label != nil && [_label respondsToSelector:@selector(setInDock:)])
         [_label setInDock:docked];
-    return _SBIconLabel$setInDock$(self, sel, docked);
+
+    _SBIconLabel$setInDock$(self, sel, docked);
+    [self setNeedsDisplay];
 }
 
 MSHook(BOOL, SBDockIconListView$shouldShowNewDock, id self, SEL sel) {
@@ -1300,7 +1538,7 @@ MSInstanceMessageHook2(CGSize, WebCoreFrameBridge, renderedSizeOfNode,constraine
 }
 // }}}
 
-MSHook(void, SBIconLabel$drawRect$, SBIconLabel *self, SEL sel, CGRect rect) {
+MSInstanceMessage1(void, SBIconLabel, drawRect, CGRect, rect) {
     CGRect bounds = [self bounds];
 
     static Ivar drawMoreLegibly = object_getInstanceVariable(self, "_drawMoreLegibly", NULL);
@@ -1326,7 +1564,7 @@ MSHook(void, SBIconLabel$drawRect$, SBIconLabel *self, SEL sel, CGRect rect) {
         style = [style stringByAppendingString:@"text-shadow: rgba(0, 0, 0, 0.5) 0px -1px 0px; "];
 
     bool ellipsis(false);
-    float max = 75, width;
+    float max = [self frame].size.width - 11, width;
   width:
     width = [(ellipsis ? [label stringByAppendingString:@"..."] : label) sizeWithStyle:style forWidth:320].width;
 
@@ -1353,14 +1591,61 @@ MSHook(void, SBIconLabel$drawRect$, SBIconLabel *self, SEL sel, CGRect rect) {
     [label drawAtPoint:CGPointMake((bounds.size.width - size.width) / 2, 0) withStyle:style];
 }
 
+MSInstanceMessage0(CGImageRef, SBIconLabel, buildLabelImage) {
+    bool docked((MSHookIvar<unsigned>(self, "_inDock") & 0x2) != 0);
+
+    WBStringDrawingState labelState = {NULL, 0, [NSString stringWithFormat:@""
+        "color: %@;"
+    ,
+        (docked || !SummerBoard_ ? @"white" : @"#b3b3b3")
+    ], docked ? @"DockedIconLabelStyle" : @"UndockedIconLabelStyle"};
+
+    stringDrawingState_ = &labelState;
+
+    CGImageRef image(MSOldCall());
+
+    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]];
 }
 
+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] 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]];
         [[self contentView] setBackgroundColor:[UIColor clearColor]];
     } return self;
 }
@@ -1385,9 +1670,9 @@ MSInstanceMessageHook2(id, CKTranscriptTableView, initWithFrame,style, CGRect, f
 MSInstanceMessageHook0(void, CKTranscriptController, loadView) {
     MSOldCall();
 
-    if (NSString *path = $getTheme$([NSArray arrayWithObjects:@"SMSBackground.png", @"SMSBackground.jpg", nil]))
-        if (UIImage *image = [[UIImage alloc] initWithContentsOfFile:path]) {
-            [image autorelease];
+    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"));
@@ -1442,13 +1727,21 @@ MSHook(UIImage *, _UIImageWithName, NSString *name) {
         UIImage *image([UIImages_ objectForKey:key]);
         if (image != nil)
             return reinterpret_cast<id>(image) == [NSNull null] ? __UIImageWithName(name) : image;
-        if (NSString *path = $pathForFile$inBundle$(name, _UIKitBundle(), true)) {
-            image = [[UIImage alloc] initWithContentsOfFile:path cache:true];
-            if (image != nil)
-                [image autorelease];
-        }
+        if (NSString *path = $pathForFile$inBundle$(name, _UIKitBundle(), true))
+            image = $getImage$(path);
         [UIImages_ setObject:(image == nil ? [NSNull null] : reinterpret_cast<id>(image)) forKey:key];
-        return image == nil ? __UIImageWithName(name) : image;
+        if (image != nil)
+            return image;
+
+        image = __UIImageWithName(name);
+
+        if (UIDebug_) {
+            NSString *path([@"/tmp/UIImages/" stringByAppendingString:name]);
+            if (![Manager_ fileExistsAtPath:path])
+                [UIImagePNGRepresentation(image) writeToFile:path atomically:YES];
+        }
+
+        return image;
     }
 }
 // }}}
@@ -1460,11 +1753,8 @@ MSHook(UIImage *, _UIImageWithNameInDomain, NSString *name, NSString *domain) {
         return reinterpret_cast<id>(image) == [NSNull null] ? __UIImageWithNameInDomain(name, domain) : image;
     if (Debug_)
         NSLog(@"WB:Debug: UIImageWithNameInDomain(\"%@\", \"%@\")", name, domain);
-    if (NSString *path = $getTheme$([NSArray arrayWithObject:[NSString stringWithFormat:@"Domains/%@/%@", domain, name]], true)) {
-        image = [[UIImage alloc] initWithContentsOfFile:path];
-        if (image != nil)
-            [image autorelease];
-    }
+    if (NSString *path = $getTheme$($useScale$([NSArray arrayWithObject:[NSString stringWithFormat:@"Domains/%@/%@", domain, name]])))
+        image = $getImage$(path);
     [PathImages_ setObject:(image == nil ? [NSNull null] : reinterpret_cast<id>(image)) forKey:key];
     return image == nil ? __UIImageWithNameInDomain(name, domain) : image;
 }
@@ -1567,10 +1857,30 @@ MSHook(void *, CGImageReadCreateWithFile, NSString *path, int flag) {
 }
 // }}}
 
-static void SBInitialize() {
-    _UIImage$defaultDesktopImage = MSHookMessage(object_getClass($UIImage), @selector(defaultDesktopImage), &$UIImage$defaultDesktopImage);
+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];
+}
 
-    bool olden(dlsym(RTLD_DEFAULT, "GSLibraryCopyGenerationInfoValueForKey") == NULL);
+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";
+    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];
+}
+
+static void SBInitialize() {
+    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");
 
     if (SummerBoard_) {
         WBRename(SBApplication, pathForIcon, pathForIcon);
@@ -1589,8 +1899,10 @@ static void SBInitialize() {
     WBRename(SBDockIconListView, setFrame:, setFrame$);
     MSHookMessage(object_getClass($SBDockIconListView), @selector(shouldShowNewDock), &$SBDockIconListView$shouldShowNewDock, &_SBDockIconListView$shouldShowNewDock);
 
-    if (olden)
+    if (kCFCoreFoundationVersionNumber < 600)
         WBRename(SBIconLabel, drawRect:, drawRect$);
+    else
+        WBRename(SBIconLabel, buildLabelImage, buildLabelImage);
 
     WBRename(SBIconLabel, initWithSize:label:, initWithSize$label$);
     WBRename(SBIconLabel, setInDock:, setInDock$);
@@ -1639,6 +1951,8 @@ MSInitialize {
             SummerBoard_ = [value boolValue];
         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)
@@ -1706,8 +2020,9 @@ MSInitialize {
     // }}}
     // SpringBoard {{{
     if (SpringBoard_) {
-        Wallpapers_ = [[NSArray arrayWithObjects:@"Wallpaper.mp4", @"Wallpaper.png", @"Wallpaper.jpg", @"Wallpaper.html", nil] retain];
-        Docked_ = $getTheme$([NSArray arrayWithObjects:@"Dock.png", nil]);
+        Wallpapers_ = [[NSArray arrayWithObjects:@"Wallpaper.mp4", @"Wallpaper@2x.png", @"Wallpaper@2x.jpg", @"Wallpaper.png", @"Wallpaper.jpg", @"Wallpaper.html", nil] retain];
+        Papered_ = $getTheme$(Wallpapers_) != nil;
+        Docked_ = $getTheme$([NSArray arrayWithObjects:@"Dock.png", nil]) != nil;
 
         CFNotificationCenterAddObserver(
             CFNotificationCenterGetDarwinNotifyCenter(),
@@ -1749,5 +2064,13 @@ MSInitialize {
     }
     // }}}
 
+    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];
 }