]> git.saurik.com Git - cydia.git/blobdiff - MobileCydia.mm
Super-charge the Reload button to actually reload.
[cydia.git] / MobileCydia.mm
index 4277e574c394aaf47329695c6e7f728bed0530fb..b14cda12640c0573612b67af4bf900305514dc77 100644 (file)
@@ -1,5 +1,5 @@
 /* Cydia - iPhone UIKit Front-End for Debian APT
- * Copyright (C) 2008-2010  Jay Freeman (saurik)
+ * Copyright (C) 2008-2011  Jay Freeman (saurik)
 */
 
 /* Modified BSD License {{{ */
@@ -217,14 +217,6 @@ union SplitHash {
 
 static const NSUInteger UIViewAutoresizingFlexibleBoth(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
 
-void NSLogPoint(const char *fix, const CGPoint &point) {
-    NSLog(@"%s(%g,%g)", fix, point.x, point.y);
-}
-
-void NSLogRect(const char *fix, const CGRect &rect) {
-    NSLog(@"%s(%g,%g)+(%g,%g)", fix, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
-}
-
 static _finline NSString *CydiaURL(NSString *path) {
     char page[25];
     page[0] = 'h'; page[1] = 't'; page[2] = 't'; page[3] = 'p'; page[4] = ':';
@@ -309,18 +301,19 @@ static _finline void UpdateExternalStatus(uint64_t newStatus) {
 @end
 /* }}} */
 
-/* Cydia Action Sheet {{{ */
-@interface CYActionSheet : UIAlertView {
+/* Cydia Alert View {{{ */
+@interface CYAlertView : UIAlertView {
     unsigned button_;
 }
 
 - (int) yieldToPopupAlertAnimated:(BOOL)animated;
+
 @end
 
-@implementation CYActionSheet
+@implementation CYAlertView
 
 - (id) initWithTitle:(NSString *)title buttons:(NSArray *)buttons defaultButtonIndex:(int)index {
-    if ((self = [super init])) {
+    if ((self = [super init]) != nil) {
         [self setTitle:title];
         [self setDelegate:self];
         for (NSString *button in buttons) [self addButtonWithTitle:button];
@@ -363,32 +356,6 @@ static const NSStringCompareOptions MatchCompareOptions_ = NSLiteralSearch | NSC
 static const NSStringCompareOptions LaxCompareOptions_ = NSNumericSearch | NSDiacriticInsensitiveSearch | NSWidthInsensitiveSearch | NSCaseInsensitiveSearch;
 static const CFStringCompareFlags LaxCompareFlags_ = kCFCompareCaseInsensitive | kCFCompareNonliteral | kCFCompareLocalized | kCFCompareNumerically | kCFCompareWidthInsensitive | kCFCompareForcedOrdering;
 
-/* Information Dictionaries {{{ */
-@interface NSMutableArray (Cydia)
-- (void) addInfoDictionary:(NSDictionary *)info;
-@end
-
-@implementation NSMutableArray (Cydia)
-
-- (void) addInfoDictionary:(NSDictionary *)info {
-    [self addObject:info];
-}
-
-@end
-
-@interface NSMutableDictionary (Cydia)
-- (void) addInfoDictionary:(NSDictionary *)info;
-@end
-
-@implementation NSMutableDictionary (Cydia)
-
-- (void) addInfoDictionary:(NSDictionary *)info {
-    [self setObject:info forKey:[info objectForKey:@"CFBundleIdentifier"]];
-}
-
-@end
-/* }}} */
-
 #define lprintf(args...) fprintf(stderr, args)
 
 #define ForRelease 1
@@ -400,7 +367,6 @@ static const CFStringCompareFlags LaxCompareFlags_ = kCFCompareCaseInsensitive |
 #define TrackResize (0 && !ForRelease)
 #define ManualRefresh (1 && !ForRelease)
 #define ShowInternals (0 && !ForRelease)
-#define IgnoreInstall (0 && !ForRelease)
 #define AlwaysReload (0 && !ForRelease)
 #define TryIndexedCollation (0 && !ForRelease)
 
@@ -906,6 +872,7 @@ class Pcre {
 
 + (Address *) addressWithString:(NSString *)string;
 - (Address *) initWithString:(NSString *)string;
+
 @end
 
 @implementation Address
@@ -939,7 +906,10 @@ class Pcre {
 }
 
 + (NSArray *) _attributeKeys {
-    return [NSArray arrayWithObjects:@"address", @"name", nil];
+    return [NSArray arrayWithObjects:
+        @"address",
+        @"name",
+    nil];
 }
 
 - (NSArray *) attributeKeys {
@@ -1014,6 +984,8 @@ class CYColor {
 /* Random Global Variables {{{ */
 static const int PulseInterval_ = 50000;
 
+static const NSString *UI_;
+
 static int Finish_;
 static NSArray *Finishes_;
 
@@ -1073,6 +1045,7 @@ static bool Changed_;
 static time_t now_;
 
 bool IsWildcat_;
+static CGFloat ScreenScale_;
 /* }}} */
 
 /* Display Helpers {{{ */
@@ -1080,23 +1053,6 @@ inline float Interpolate(float begin, float end, float fraction) {
     return (end - begin) * fraction + begin;
 }
 
-/* XXX: localize this! */
-NSString *SizeString(double size) {
-    bool negative = size < 0;
-    if (negative)
-        size = -size;
-
-    unsigned power = 0;
-    while (size > 1024) {
-        size /= 1024;
-        ++power;
-    }
-
-    static const char *powers_[] = {"B", "kB", "MB", "GB"};
-
-    return [NSString stringWithFormat:@"%s%.1f %s", (negative ? "-" : ""), size, powers_[power]];
-}
-
 static _finline const char *StripVersion_(const char *version) {
     const char *colon(strchr(version, ':'));
     return colon == NULL ? version : colon + 1;
@@ -1152,7 +1108,7 @@ NSString *GetLastUpdate() {
 }
 
 bool isSectionVisible(NSString *section) {
-    NSDictionary *metadata([Sections_ objectForKey:section]);
+    NSDictionary *metadata([Sections_ objectForKey:(section ?: @"")]);
     NSNumber *hidden(metadata == nil ? nil : [metadata objectForKey:@"Hidden"]);
     return hidden == nil || ![hidden boolValue];
 }
@@ -1200,6 +1156,7 @@ bool isSectionVisible(NSString *section) {
 - (void) removeProgressHUD:(UIProgressHUD *)hud;
 - (CYViewController *) pageForPackage:(NSString *)name;
 - (void) showActionSheet:(UIActionSheet *)sheet fromItem:(UIBarButtonItem *)item;
+- (void) reloadDataWithInvocation:(NSInvocation *)invocation;
 @end
 
 static id<CydiaDelegate> CydiaApp;
@@ -1378,7 +1335,8 @@ typedef std::map< unsigned long, _H<Source> > SourceMap;
 - (pkgSourceList &) list;
 - (NSArray *) packages;
 - (NSArray *) sources;
-- (void) reloadData;
+- (Source *) sourceWithKey:(NSString *)key;
+- (void) reloadDataWithInvocation:(NSInvocation *)invocation;
 
 - (void) configure;
 - (bool) prepare;
@@ -1461,7 +1419,7 @@ struct MetaValue :
 static Cytore::File<MetaValue> MetaFile_;
 // }}}
 // Cytore Helper Functions {{{
-static PackageValue *PackageFind(const char *name, size_t length) {
+static PackageValue *PackageFind(const char *name, size_t length, bool *fail = NULL) {
     SplitHash nhash = { hashlittle(name, length) };
 
     PackageValue *metadata;
@@ -1471,6 +1429,14 @@ static PackageValue *PackageFind(const char *name, size_t length) {
         *offset = MetaFile_.New<PackageValue>(length + 1);
         metadata = &MetaFile_.Get(*offset);
 
+        if (metadata == NULL) {
+            if (fail != NULL)
+                *fail = true;
+
+            metadata = new PackageValue();
+            memset(metadata, 0, sizeof(*metadata));
+        }
+
         memcpy(metadata->name_, name, length + 1);
         metadata->nhash_ = nhash.u16[1];
     } else {
@@ -1486,13 +1452,15 @@ static PackageValue *PackageFind(const char *name, size_t length) {
 }
 
 static void PackageImport(const void *key, const void *value, void *context) {
+    bool &fail(*reinterpret_cast<bool *>(context));
+
     char buffer[1024];
     if (!CFStringGetCString((CFStringRef) key, buffer, sizeof(buffer), kCFStringEncodingUTF8)) {
         NSLog(@"failed to import package %@", key);
         return;
     }
 
-    PackageValue *metadata(PackageFind(buffer, strlen(buffer)));
+    PackageValue *metadata(PackageFind(buffer, strlen(buffer), &fail));
     NSDictionary *package((NSDictionary *) value);
 
     if (NSNumber *subscribed = [package objectForKey:@"IsSubscribed"])
@@ -1613,7 +1581,19 @@ static void PackageImport(const void *key, const void *value, void *context) {
 }
 
 + (NSArray *) _attributeKeys {
-    return [NSArray arrayWithObjects:@"description", @"distribution", @"host", @"key", @"label", @"name", @"origin", @"trusted", @"type", @"uri", @"version", nil];
+    return [NSArray arrayWithObjects:
+        @"description",
+        @"distribution",
+        @"host",
+        @"key",
+        @"label",
+        @"name",
+        @"origin",
+        @"trusted",
+        @"type",
+        @"uri",
+        @"version",
+    nil];
 }
 
 - (NSArray *) attributeKeys {
@@ -1778,37 +1758,176 @@ static void PackageImport(const void *key, const void *value, void *context) {
 
 @end
 /* }}} */
-/* Relationship Class {{{ */
-@interface Relationship : NSObject {
-    NSString *type_;
-    NSString *id_;
+/* CydiaOperation Class {{{ */
+@interface CydiaOperation : NSObject {
+    NSString *operator_;
+    NSString *value_;
 }
 
-- (NSString *) type;
-- (NSString *) id;
-- (NSString *) name;
+- (NSString *) operator;
+- (NSString *) value;
 
 @end
 
-@implementation Relationship
+@implementation CydiaOperation
 
 - (void) dealloc {
-    [type_ release];
-    [id_ release];
+    [operator_ release];
+    [value_ release];
     [super dealloc];
 }
 
-- (NSString *) type {
-    return type_;
+- (id) initWithOperator:(const char *)_operator value:(const char *)value {
+    if ((self = [super init]) != nil) {
+        operator_ = [[NSString alloc] initWithUTF8String:_operator];
+        value_ = [[NSString alloc] initWithUTF8String:value];
+    } return self;
 }
 
-- (NSString *) id {
-    return id_;
++ (NSArray *) _attributeKeys {
+    return [NSArray arrayWithObjects:
+        @"operator",
+        @"value",
+    nil];
 }
 
-- (NSString *) name {
-    _assert(false);
-    return nil;
+- (NSArray *) attributeKeys {
+    return [[self class] _attributeKeys];
+}
+
++ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
+    return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
+}
+
+- (NSString *) operator {
+    return operator_;
+}
+
+- (NSString *) value {
+    return value_;
+}
+
+@end
+/* }}} */
+/* CydiaClause Class {{{ */
+@interface CydiaClause : NSObject {
+    NSString *package_;
+    CydiaOperation *version_;
+}
+
+- (NSString *) package;
+- (CydiaOperation *) version;
+
+@end
+
+@implementation CydiaClause
+
+- (void) dealloc {
+    [package_ release];
+    [version_ release];
+    [super dealloc];
+}
+
+- (id) initWithIterator:(pkgCache::DepIterator &)dep {
+    if ((self = [super init]) != nil) {
+        package_ = [[NSString alloc] initWithUTF8String:dep.TargetPkg().Name()];
+
+        if (const char *version = dep.TargetVer())
+            version_ = [[CydiaOperation alloc] initWithOperator:dep.CompType() value:version];
+        else
+            version_ = [[NSNull null] retain];
+    } return self;
+}
+
++ (NSArray *) _attributeKeys {
+    return [NSArray arrayWithObjects:
+        @"package",
+        @"version",
+    nil];
+}
+
+- (NSArray *) attributeKeys {
+    return [[self class] _attributeKeys];
+}
+
++ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
+    return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
+}
+
+- (NSString *) package {
+    return package_;
+}
+
+- (CydiaOperation *) version {
+    return version_;
+}
+
+@end
+/* }}} */
+/* CydiaRelation Class {{{ */
+@interface CydiaRelation : NSObject {
+    NSString *relationship_;
+    NSMutableArray *clauses_;
+}
+
+- (NSString *) relationship;
+- (NSArray *) clauses;
+
+@end
+
+@implementation CydiaRelation
+
+- (void) dealloc {
+    [relationship_ release];
+    [clauses_ release];
+    [super dealloc];
+}
+
+- (id) initWithIterator:(pkgCache::DepIterator &)dep {
+    if ((self = [super init]) != nil) {
+        relationship_ = [[NSString alloc] initWithUTF8String:dep.DepType()];
+        clauses_ = [[NSMutableArray alloc] initWithCapacity:8];
+
+        pkgCache::DepIterator start;
+        pkgCache::DepIterator end;
+        dep.GlobOr(start, end); // ++dep
+
+        _forever {
+            [clauses_ addObject:[[[CydiaClause alloc] initWithIterator:start] autorelease]];
+
+            // yes, seriously. (wtf?)
+            if (start == end)
+                break;
+            ++start;
+        }
+    } return self;
+}
+
++ (NSArray *) _attributeKeys {
+    return [NSArray arrayWithObjects:
+        @"clauses",
+        @"relationship",
+    nil];
+}
+
+- (NSArray *) attributeKeys {
+    return [[self class] _attributeKeys];
+}
+
++ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
+    return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
+}
+
+- (NSString *) relationship {
+    return relationship_;
+}
+
+- (NSArray *) clauses {
+    return clauses_;
+}
+
+- (void) addClause:(CydiaClause *)clause {
+    [clauses_ addObject:clause];
 }
 
 @end
@@ -2088,8 +2207,15 @@ struct PackageNameOrdering :
 }
 
 + (NSString *) webScriptNameForSelector:(SEL)selector {
-    if (selector == @selector(hasTag:))
+    if (false);
+    else if (selector == @selector(clear))
+        return @"clear";
+    else if (selector == @selector(hasTag:))
         return @"hasTag";
+    else if (selector == @selector(install))
+        return @"install";
+    else if (selector == @selector(remove))
+        return @"remove";
     else
         return nil;
 }
@@ -2099,7 +2225,33 @@ struct PackageNameOrdering :
 }
 
 + (NSArray *) _attributeKeys {
-    return [NSArray arrayWithObjects:@"applications", @"author", @"depiction", @"longDescription", @"essential", @"homepage", @"icon", @"id", @"installed", @"latest", @"longSection", @"maintainer", @"mode", @"name", @"purposes", @"section", @"shortDescription", @"shortSection", @"simpleSection", @"size", @"source", @"sponsor", @"support", @"warnings", nil];
+    return [NSArray arrayWithObjects:
+        @"applications",
+        @"author",
+        @"depiction",
+        @"essential",
+        @"homepage",
+        @"icon",
+        @"id",
+        @"installed",
+        @"latest",
+        @"longDescription",
+        @"longSection",
+        @"maintainer",
+        @"mode",
+        @"name",
+        @"purposes",
+        @"relations",
+        @"section",
+        @"shortDescription",
+        @"shortSection",
+        @"simpleSection",
+        @"size",
+        @"source",
+        @"sponsor",
+        @"support",
+        @"warnings",
+    nil];
 }
 
 - (NSArray *) attributeKeys {
@@ -2110,6 +2262,14 @@ struct PackageNameOrdering :
     return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
 }
 
+- (NSArray *) relations {
+@synchronized (database_) {
+    NSMutableArray *relations([NSMutableArray arrayWithCapacity:16]);
+    for (pkgCache::DepIterator dep(version_.DependsList()); !dep.end(); ++dep)
+        [relations addObject:[[[CydiaRelation alloc] initWithIterator:dep] autorelease]];
+    return relations;
+} }
+
 - (void) parse {
     if (parsed_ != NULL)
         return;
@@ -2501,7 +2661,7 @@ struct PackageNameOrdering :
     _end
 
     _profile(Package$visible$isSectionVisible)
-        if (section != nil && !isSectionVisible(section))
+        if (!isSectionVisible(section))
             return false;
     _end
 
@@ -3043,7 +3203,7 @@ static NSString *Warning_;
     // XXX: actually implement this thing
     _assert(false);
     if (deadSources_)
-           CFRelease(deadSources_);
+        CFRelease(deadSources_);
     [self releasePackages];
     apr_pool_destroy(pool_);
     NSRecycleZone(zone_);
@@ -3240,70 +3400,11 @@ static NSString *Warning_;
     return sources;
 }
 
-- (NSArray *) issues {
-    if (cache_->BrokenCount() == 0)
-        return nil;
-
-    NSMutableArray *issues([NSMutableArray arrayWithCapacity:4]);
-
-    for (Package *package in [self packages]) {
-        if (![package broken])
-            continue;
-        pkgCache::PkgIterator pkg([package iterator]);
-
-        NSMutableArray *entry([NSMutableArray arrayWithCapacity:4]);
-        [entry addObject:[package name]];
-        [issues addObject:entry];
-
-        pkgCache::VerIterator ver(cache_[pkg].InstVerIter(cache_));
-        if (ver.end())
-            continue;
-
-        for (pkgCache::DepIterator dep(ver.DependsList()); !dep.end(); ) {
-            pkgCache::DepIterator start;
-            pkgCache::DepIterator end;
-            dep.GlobOr(start, end); // ++dep
-
-            if (!cache_->IsImportantDep(end))
-                continue;
-            if ((cache_[end] & pkgDepCache::DepGInstall) != 0)
-                continue;
-
-            NSMutableArray *failure([NSMutableArray arrayWithCapacity:4]);
-            [entry addObject:failure];
-            [failure addObject:[NSString stringWithUTF8String:start.DepType()]];
-
-            NSString *name([NSString stringWithUTF8String:start.TargetPkg().Name()]);
-            if (Package *package = [self packageWithName:name])
-                name = [package name];
-            [failure addObject:name];
-
-            pkgCache::PkgIterator target(start.TargetPkg());
-            if (target->ProvidesList != 0)
-                [failure addObject:@"?"];
-            else {
-                pkgCache::VerIterator ver(cache_[target].InstVerIter(cache_));
-                if (!ver.end())
-                    [failure addObject:[NSString stringWithUTF8String:ver.VerStr()]];
-                else if (!cache_[target].CandidateVerIter(cache_).end())
-                    [failure addObject:@"-"];
-                else if (target->ProvidesList == 0)
-                    [failure addObject:@"!"];
-                else
-                    [failure addObject:@"%"];
-            }
-
-            _forever {
-                if (start.TargetVer() != 0)
-                    [failure addObject:[NSString stringWithFormat:@"%s %s", start.CompType(), start.TargetVer()]];
-                if (start == end)
-                    break;
-                ++start;
-            }
-        }
-    }
-
-    return issues;
+- (Source *) sourceWithKey:(NSString *)key {
+    for (Source *source in [self sources]) {
+        if ([[source key] isEqualToString:key])
+            return source;
+    } return nil;
 }
 
 - (bool) popErrorWithTitle:(NSString *)title {
@@ -3338,7 +3439,7 @@ static NSString *Warning_;
     return [self popErrorWithTitle:title] || !success;
 }
 
-- (void) reloadData { CYPoolStart() {
+- (void) reloadDataWithInvocation:(NSInvocation *)invocation { CYPoolStart() {
 @synchronized (self) {
     ++era_;
 
@@ -3374,10 +3475,13 @@ static NSString *Warning_;
     if (chk != -1)
         close(chk);
 
+    if (invocation != nil)
+        [invocation invoke];
+
     NSString *title(UCLocalize("DATABASE"));
 
     _trace();
-    if (!cache_.Open(progress_, true)) { pop:
+    while (!cache_.Open(progress_, true)) { pop:
         std::string error;
         bool warning(!_error->PopMessage(error));
         lprintf("cache_.Open():[%s]\n", error.c_str());
@@ -3387,15 +3491,17 @@ static NSString *Warning_;
         else if (error == "The package lists or status file could not be parsed or opened.")
             [delegate_ repairWithSelector:@selector(update)];
         // else if (error == "Could not open lock file /var/lib/dpkg/lock - open (13 Permission denied)")
-        // else if (error == "Could not get lock /var/lib/dpkg/lock - open (35 Resource temporarily unavailable)")
+        else if (error == "Could not get lock /var/lib/dpkg/lock - open (35 Resource temporarily unavailable)")
+            [delegate_ _setProgressError:[NSString stringWithUTF8String:error.c_str()] withTitle:[NSString stringWithFormat:Colon_, Error_, title]];
         // else if (error == "The list of sources could not be read.")
-        else
+        else {
             [delegate_ _setProgressError:[NSString stringWithUTF8String:error.c_str()] withTitle:[NSString stringWithFormat:Colon_, warning ? Warning_ : Error_, title]];
+            return;
+        }
 
         if (warning)
             goto pop;
         _error->Discard();
-        return;
     }
     _trace();
 
@@ -3508,12 +3614,11 @@ static NSString *Warning_;
     delete resolver_;
     resolver_ = new pkgProblemResolver(cache_);
 
-    for (pkgCache::PkgIterator iterator(cache_->PkgBegin()); !iterator.end(); ++iterator) {
-        if (!cache_[iterator].Keep()) {
+    for (pkgCache::PkgIterator iterator(cache_->PkgBegin()); !iterator.end(); ++iterator)
+        if (!cache_[iterator].Keep())
             cache_->MarkKeep(iterator, false);
+        else if ((cache_[iterator].iFlags & pkgDepCache::ReInstall) != 0)
             cache_->SetReInstall(iterator, false);
-        }
-    }
 } }
 
 - (void) configure {
@@ -3738,6 +3843,7 @@ static NSString *Warning_;
 }
 
 - (id) initWithDelegate:(IndirectDelegate *)indirect;
+
 @end
 
 @implementation CydiaObject
@@ -3758,7 +3864,13 @@ static NSString *Warning_;
 }
 
 + (NSArray *) _attributeKeys {
-    return [NSArray arrayWithObjects:@"device", @"firewire", @"imei", @"mac", @"serial", nil];
+    return [NSArray arrayWithObjects:
+        @"device",
+        @"firewire",
+        @"imei",
+        @"mac",
+        @"serial",
+    nil];
 }
 
 - (NSArray *) attributeKeys {
@@ -3796,14 +3908,21 @@ static NSString *Warning_;
 #endif
 
 + (NSString *) webScriptNameForSelector:(SEL)selector {
-    if (selector == @selector(close))
+    if (false);
+    else if (selector == @selector(close))
         return @"close";
+    else if (selector == @selector(du:))
+        return @"du";
+    else if (selector == @selector(stringWithFormat:arguments:))
+        return @"format";
     else if (selector == @selector(getInstalledPackages))
         return @"getInstalledPackages";
     else if (selector == @selector(getPackageById:))
         return @"getPackageById";
     else if (selector == @selector(installPackages:))
         return @"installPackages";
+    else if (selector == @selector(localizedStringForKey:value:table:))
+        return @"localize";
     else if (selector == @selector(setButtonImage:withStyle:toFunction:))
         return @"setButtonImage";
     else if (selector == @selector(setButtonTitle:withStyle:toFunction:))
@@ -3816,16 +3935,10 @@ static NSString *Warning_;
         return @"setToken";
     else if (selector == @selector(setViewportWidth:))
         return @"setViewportWidth";
-    else if (selector == @selector(supports:))
-        return @"supports";
-    else if (selector == @selector(stringWithFormat:arguments:))
-        return @"format";
-    else if (selector == @selector(localizedStringForKey:value:table:))
-        return @"localize";
-    else if (selector == @selector(du:))
-        return @"du";
     else if (selector == @selector(statfs:))
         return @"statfs";
+    else if (selector == @selector(supports:))
+        return @"supports";
     else
         return nil;
 }
@@ -3953,7 +4066,7 @@ static NSString *Warning_;
     id values[count];
     for (unsigned i(0); i != count; ++i)
         values[i] = [arguments objectAtIndex:i];
-    return [[[NSString alloc] initWithFormat:format arguments:*(reinterpret_cast<va_list *>(&values))] autorelease];
+    return [[[NSString alloc] initWithFormat:format arguments:reinterpret_cast<va_list>(values)] autorelease];
 }
 
 - (NSString *) localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)table {
@@ -3981,8 +4094,8 @@ static NSString *Warning_;
 
 @implementation CYLoadingIndicator
 
-- (id)initWithFrame:(CGRect)frame {
-    if ((self = [super initWithFrame:frame])) {
+- (id) initWithFrame:(CGRect)frame {
+    if ((self = [super initWithFrame:frame]) != nil) {
         container_ = [[[UIView alloc] init] autorelease];
         [container_ setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin];
 
@@ -4021,49 +4134,104 @@ static NSString *Warning_;
         [spinner_ setFrame:spinrect];
         [label_ setFrame:textrect];
         [self addSubview:container_];
-    }
+    } return self;
+}
 
-    return self;
+- (UILabel *) label {
+    return label_;
 }
 
-- (UILabel *)label { return label_; }
-- (UIActivityIndicatorView *)activityIndicatorView { return spinner_; }
+- (UIActivityIndicatorView *) activityIndicatorView {
+    return spinner_;
+}
 
 @end
 /* }}} */
 /* Emulated Loading Controller {{{ */
-@interface CYEmulatedLoadingController : CYViewController {
+@interface CYEmulatedLoadingController : CYViewController <
+    ProgressDelegate,
+    ConfigurationDelegate
+> {
+    _transient Database *database_;
     CYLoadingIndicator *indicator_;
     UITabBar *tabbar_;
     UINavigationBar *navbar_;
 }
+
 @end
 
 @implementation CYEmulatedLoadingController
 
-- (CYEmulatedLoadingController *) init {
-    if ((self = [super init])) {
-        [[self view] setBackgroundColor:[UIColor pinStripeColor]];
-
-        indicator_ = [[CYLoadingIndicator alloc] initWithFrame:[[self view] bounds]];
-        [indicator_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [[self view] addSubview:indicator_];
-        [indicator_ release];
-
-        tabbar_ = [[UITabBar alloc] initWithFrame:CGRectMake(0, 0, 0, 49.0f)];
-        [tabbar_ setFrame:CGRectMake(0.0f, [[self view] bounds].size.height - [tabbar_ bounds].size.height, [[self view] bounds].size.width, [tabbar_ bounds].size.height)];
-        [tabbar_ setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth];
-        [[self view] addSubview:tabbar_];
-        [tabbar_ release];
-
-        navbar_ = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, 0, 44.0f)];
-        [navbar_ setFrame:CGRectMake(0.0f, 0.0f, [[self view] bounds].size.width, [navbar_ bounds].size.height)];
-        [navbar_ setAutoresizingMask:UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth];
-        [[self view] addSubview:navbar_];
-        [navbar_ release];
+- (void) dealloc {
+    [self releaseSubviews];
+    [database_ setDelegate:nil];
+
+    [super dealloc];
+}
+
+- (void) setProgressError:(NSString *)error withTitle:(NSString *)title {
+    CYAlertView *sheet([[[CYAlertView alloc]
+        initWithTitle:title
+        buttons:[NSArray arrayWithObjects:UCLocalize("OKAY"), nil]
+        defaultButtonIndex:0
+    ] autorelease]);
+
+    [sheet setMessage:error];
+    [sheet yieldToPopupAlertAnimated:YES];
+    [sheet dismiss];
+}
+
+- (void) setProgressTitle:(NSString *)title { }
+- (void) setProgressPercent:(float)percent { }
+- (void) startProgress { }
+- (void) addProgressOutput:(NSString *)output { }
+- (bool) isCancelling:(size_t)received { return NO; }
+- (void) setConfigurationData:(NSString *)data { }
+
+- (void) repairWithSelector:(SEL)selector {
+    [[indicator_ label] performSelectorOnMainThread:@selector(setText:) withObject:[NSString stringWithFormat:Elision_, UCLocalize("REPAIRING"), nil] waitUntilDone:YES];
+    [database_ performSelector:selector];
+    sleep(10);
+    [[indicator_ label] performSelectorOnMainThread:@selector(setText:) withObject:[NSString stringWithFormat:Elision_, UCLocalize("LOADING"), nil] waitUntilDone:YES];
+}
+
+- (id) initWithDatabase:(Database *)database {
+    if ((self = [super init]) != nil) {
+        database_ = database;
+        [database_ setDelegate:self];
     } return self;
 }
 
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
+    [[self view] setBackgroundColor:[UIColor pinStripeColor]];
+
+    indicator_ = [[CYLoadingIndicator alloc] initWithFrame:[[self view] bounds]];
+    [indicator_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [[self view] addSubview:indicator_];
+
+    tabbar_ = [[UITabBar alloc] initWithFrame:CGRectMake(0, 0, 0, 49.0f)];
+    [tabbar_ setFrame:CGRectMake(0.0f, [[self view] bounds].size.height - [tabbar_ bounds].size.height, [[self view] bounds].size.width, [tabbar_ bounds].size.height)];
+    [tabbar_ setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth];
+    [[self view] addSubview:tabbar_];
+
+    navbar_ = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, 0, 44.0f)];
+    [navbar_ setFrame:CGRectMake(0.0f, 0.0f, [[self view] bounds].size.width, [navbar_ bounds].size.height)];
+    [navbar_ setAutoresizingMask:UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth];
+    [[self view] addSubview:navbar_];
+}
+
+- (void) releaseSubviews {
+    [indicator_ release];
+    indicator_ = nil;
+
+    [tabbar_ release];
+    tabbar_ = nil;
+
+    [navbar_ release];
+    navbar_ = nil;
+}
+
 @end
 /* }}} */
 
@@ -4081,6 +4249,10 @@ static NSString *Warning_;
     [super dealloc];
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://url/%@", [[[webview_ request] URL] absoluteString]]];
+}
+
 - (void) setHeaders:(NSDictionary *)headers forHost:(NSString *)host {
 }
 
@@ -4157,18 +4329,54 @@ static NSString *Warning_;
 @end
 /* }}} */
 
-/* Confirmation Controller {{{ */
-bool DepSubstrate(const pkgCache::VerIterator &iterator) {
-    if (!iterator.end())
-        for (pkgCache::DepIterator dep(iterator.DependsList()); !dep.end(); ++dep) {
-            if (dep->Type != pkgCache::Dep::Depends && dep->Type != pkgCache::Dep::PreDepends)
-                continue;
-            pkgCache::PkgIterator package(dep.TargetPkg());
-            if (package.end())
-                continue;
-            if (strcmp(package.Name(), "mobilesubstrate") == 0)
-                return true;
-        }
+// CydiaScript {{{
+@interface NSObject (CydiaScript)
+- (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context;
+@end
+
+@implementation NSObject (CydiaScript)
+
+- (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context {
+    return self;
+}
+
+@end
+
+@implementation NSArray (CydiaScript)
+
+- (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context {
+    WebScriptObject *object([context evaluateWebScript:@"[]"]);
+    for (size_t i(0), e([self count]); i != e; ++i)
+        [object setWebScriptValueAtIndex:i value:[[self objectAtIndex:i] Cydia$webScriptObjectInContext:context]];
+    return object;
+}
+
+@end
+
+@implementation NSDictionary (CydiaScript)
+
+- (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context {
+    WebScriptObject *object([context evaluateWebScript:@"({})"]);
+    for (id i in self)
+        [object setValue:[[self objectForKey:i] Cydia$webScriptObjectInContext:context] forKey:i];
+    return object;
+}
+
+@end
+// }}}
+
+/* Confirmation Controller {{{ */
+bool DepSubstrate(const pkgCache::VerIterator &iterator) {
+    if (!iterator.end())
+        for (pkgCache::DepIterator dep(iterator.DependsList()); !dep.end(); ++dep) {
+            if (dep->Type != pkgCache::Dep::Depends && dep->Type != pkgCache::Dep::PreDepends)
+                continue;
+            pkgCache::PkgIterator package(dep.TargetPkg());
+            if (package.end())
+                continue;
+            if (strcmp(package.Name(), "mobilesubstrate") == 0)
+                return true;
+        }
 
     return false;
 }
@@ -4181,10 +4389,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @interface ConfirmationController : CYBrowserController {
     _transient Database *database_;
+
     UIAlertView *essential_;
-    NSArray *changes_;
-    NSArray *issues_;
-    NSArray *sizes_;
+
+    NSDictionary *changes_;
+    NSMutableArray *issues_;
+    NSDictionary *sizes_;
+
     BOOL substrate_;
 }
 
@@ -4196,11 +4407,12 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 - (void) dealloc {
     [changes_ release];
-    if (issues_ != nil)
-        [issues_ release];
+    [issues_ release];
     [sizes_ release];
+
     if (essential_ != nil)
         [essential_ release];
+
     [super dealloc];
 }
 
@@ -4237,9 +4449,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 - (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
     [super webView:view didClearWindowObject:window forFrame:frame];
-    [window setValue:changes_ forKey:@"changes"];
-    [window setValue:issues_ forKey:@"issues"];
-    [window setValue:sizes_ forKey:@"sizes"];
+
+    [window setValue:[changes_ Cydia$webScriptObjectInContext:window] forKey:@"changes"];
+    [window setValue:[issues_ Cydia$webScriptObjectInContext:window] forKey:@"issues"];
+    [window setValue:[sizes_ Cydia$webScriptObjectInContext:window] forKey:@"sizes"];
+
     [window setValue:self forKey:@"queue"];
 }
 
@@ -4247,39 +4461,127 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     if ((self = [super init]) != nil) {
         database_ = database;
 
-        [[self navigationItem] setTitle:UCLocalize("CONFIRM")];
-
-        NSMutableArray *installing = [NSMutableArray arrayWithCapacity:16];
-        NSMutableArray *reinstalling = [NSMutableArray arrayWithCapacity:16];
-        NSMutableArray *upgrading = [NSMutableArray arrayWithCapacity:16];
-        NSMutableArray *downgrading = [NSMutableArray arrayWithCapacity:16];
-        NSMutableArray *removing = [NSMutableArray arrayWithCapacity:16];
+        NSMutableArray *installs([NSMutableArray arrayWithCapacity:16]);
+        NSMutableArray *reinstalls([NSMutableArray arrayWithCapacity:16]);
+        NSMutableArray *upgrades([NSMutableArray arrayWithCapacity:16]);
+        NSMutableArray *downgrades([NSMutableArray arrayWithCapacity:16]);
+        NSMutableArray *removes([NSMutableArray arrayWithCapacity:16]);
 
         bool remove(false);
 
+        pkgCacheFile &cache([database_ cache]);
+        NSArray *packages([database_ packages]);
         pkgDepCache::Policy *policy([database_ policy]);
 
-        pkgCacheFile &cache([database_ cache]);
-        NSArray *packages = [database_ packages];
+        issues_ = [[NSMutableArray arrayWithCapacity:4] retain];
+
         for (Package *package in packages) {
-            pkgCache::PkgIterator iterator = [package iterator];
+            pkgCache::PkgIterator iterator([package iterator]);
+            NSString *name([package id]);
+
+            if ([package broken]) {
+                NSMutableArray *reasons([NSMutableArray arrayWithCapacity:4]);
+
+                [issues_ addObject:[NSDictionary dictionaryWithObjectsAndKeys:
+                    name, @"package",
+                    reasons, @"reasons",
+                nil]];
+
+                pkgCache::VerIterator ver(cache[iterator].InstVerIter(cache));
+                if (ver.end())
+                    continue;
+
+                for (pkgCache::DepIterator dep(ver.DependsList()); !dep.end(); ) {
+                    pkgCache::DepIterator start;
+                    pkgCache::DepIterator end;
+                    dep.GlobOr(start, end); // ++dep
+
+                    if (!cache->IsImportantDep(end))
+                        continue;
+                    if ((cache[end] & pkgDepCache::DepGInstall) != 0)
+                        continue;
+
+                    NSMutableArray *clauses([NSMutableArray arrayWithCapacity:4]);
+
+                    [reasons addObject:[NSDictionary dictionaryWithObjectsAndKeys:
+                        [NSString stringWithUTF8String:start.DepType()], @"relationship",
+                        clauses, @"clauses",
+                    nil]];
+
+                    _forever {
+                        NSString *reason, *installed((NSString *) [WebUndefined undefined]);
+
+                        pkgCache::PkgIterator target(start.TargetPkg());
+                        if (target->ProvidesList != 0)
+                            reason = @"missing";
+                        else {
+                            pkgCache::VerIterator ver(cache[target].InstVerIter(cache));
+                            if (!ver.end()) {
+                                reason = @"installed";
+                                installed = [NSString stringWithUTF8String:ver.VerStr()];
+                            } else if (!cache[target].CandidateVerIter(cache).end())
+                                reason = @"uninstalled";
+                            else if (target->ProvidesList == 0)
+                                reason = @"uninstallable";
+                            else
+                                reason = @"virtual";
+                        }
+
+                        NSDictionary *version(start.TargetVer() == 0 ? [NSNull null] : [NSDictionary dictionaryWithObjectsAndKeys:
+                            [NSString stringWithUTF8String:start.CompType()], @"operator",
+                            [NSString stringWithUTF8String:start.TargetVer()], @"value",
+                        nil]);
+
+                        [clauses addObject:[NSDictionary dictionaryWithObjectsAndKeys:
+                            [NSString stringWithUTF8String:start.TargetPkg().Name()], @"package",
+                            version, @"version",
+                            reason, @"reason",
+                            installed, @"installed",
+                        nil]];
+
+                        // yes, seriously. (wtf?)
+                        if (start == end)
+                            break;
+                        ++start;
+                    }
+                }
+            }
+
             pkgDepCache::StateCache &state(cache[iterator]);
 
-            NSString *name([package name]);
+            static Pcre special_r("^(firmware$|gsc\\.|cy\\+)");
 
             if (state.NewInstall())
-                [installing addObject:name];
+                [installs addObject:name];
             else if (!state.Delete() && (state.iFlags & pkgDepCache::ReInstall) == pkgDepCache::ReInstall)
-                [reinstalling addObject:name];
+                [reinstalls addObject:name];
             else if (state.Upgrade())
-                [upgrading addObject:name];
+                [upgrades addObject:name];
             else if (state.Downgrade())
-                [downgrading addObject:name];
-            else if (state.Delete()) {
+                [downgrades addObject:name];
+            else if (!state.Delete())
+                continue;
+            else if (special_r(name))
+                [issues_ addObject:[NSDictionary dictionaryWithObjectsAndKeys:
+                    [NSNull null], @"package",
+                    [NSArray arrayWithObjects:
+                        [NSDictionary dictionaryWithObjectsAndKeys:
+                            @"Conflicts", @"relationship",
+                            [NSArray arrayWithObjects:
+                                [NSDictionary dictionaryWithObjectsAndKeys:
+                                    name, @"package",
+                                    [NSNull null], @"version",
+                                    @"installed", @"reason",
+                                nil],
+                            nil], @"clauses",
+                        nil],
+                    nil], @"reasons",
+                nil]];
+            else {
                 if ([package essential])
                     remove = true;
-                [removing addObject:name];
-            } else continue;
+                [removes addObject:name];
+            }
 
             substrate_ |= DepSubstrate(policy->GetCandidateVer(iterator));
             substrate_ |= DepSubstrate(iterator.CurrentVer());
@@ -4295,7 +4597,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                 message:UCLocalize("REMOVING_ESSENTIALS_EX")
                 delegate:self
                 cancelButtonTitle:[NSString stringWithFormat:parenthetical, UCLocalize("CANCEL_OPERATION"), UCLocalize("SAFE")]
-                otherButtonTitles:[NSString stringWithFormat:parenthetical, UCLocalize("FORCE_REMOVAL"), UCLocalize("UNSAFE")], nil
+                otherButtonTitles:
+                    [NSString stringWithFormat:parenthetical, UCLocalize("FORCE_REMOVAL"), UCLocalize("UNSAFE")],
+                nil
             ];
 
             [essential_ setContext:@"remove"];
@@ -4311,28 +4615,23 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             [essential_ setContext:@"unable"];
         }
 
-        changes_ = [[NSArray alloc] initWithObjects:
-            installing,
-            reinstalling,
-            upgrading,
-            downgrading,
-            removing,
+        changes_ = [[NSDictionary alloc] initWithObjectsAndKeys:
+            installs, @"installs",
+            reinstalls, @"reinstalls",
+            upgrades, @"upgrades",
+            downgrades, @"downgrades",
+            removes, @"removes",
         nil];
 
-        issues_ = [database_ issues];
-        if (issues_ != nil)
-            issues_ = [issues_ retain];
-
-        sizes_ = [[NSArray alloc] initWithObjects:
-            SizeString([database_ fetcher].FetchNeeded()),
-            SizeString([database_ fetcher].PartialPresent()),
+        sizes_ = [[NSDictionary alloc] initWithObjectsAndKeys:
+            [NSNumber numberWithInteger:[database_ fetcher].FetchNeeded()], @"downloading",
+            [NSNumber numberWithInteger:[database_ fetcher].PartialPresent()], @"resuming",
         nil];
 
-        [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"confirm" ofType:@"html"]]];
+        [self loadURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/confirm/", UI_]]];
 
         [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
             initWithTitle:UCLocalize("CANCEL")
-            // OLD: [NSString stringWithFormat:UCLocalize("SLASH_DELIMITED"), UCLocalize("CANCEL"), UCLocalize("QUEUE")]
             style:UIBarButtonItemStylePlain
             target:self
             action:@selector(cancelButtonClicked)
@@ -4340,21 +4639,19 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     } return self;
 }
 
+#if !AlwaysReload
 - (void) applyRightButton {
-#if !AlwaysReload && !IgnoreInstall
-    if (issues_ == nil && ![self isLoading])
+    if ([issues_ count] == 0 && ![self isLoading])
         [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
             initWithTitle:UCLocalize("CONFIRM")
-            style:UIBarButtonItemStylePlain
+            style:UIBarButtonItemStyleDone
             target:self
             action:@selector(confirmButtonClicked)
         ] autorelease]];
     else
-        [super applyRightButton];
-#else
-    [[self navigationItem] setRightBarButtonItem:nil];
-#endif
+        [[self navigationItem] setRightBarButtonItem:nil];
 }
+#endif
 
 - (void) cancelButtonClicked {
     [self dismissModalViewControllerAnimated:YES];
@@ -4363,9 +4660,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 #if !AlwaysReload
 - (void) confirmButtonClicked {
-#if IgnoreInstall
-    return;
-#endif
     if (essential_ != nil)
         [essential_ show];
     else {
@@ -4392,6 +4686,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 - (SEL) selector;
 - (id) target;
 - (id) object;
+
 @end
 
 @implementation ProgressData
@@ -4544,7 +4839,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [self positionViews];
 }
 
-- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
+- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
     [self positionViews];
 }
 
@@ -4736,7 +5031,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) setProgressError:(NSString *)error withTitle:(NSString *)title {
-    CYActionSheet *sheet([[[CYActionSheet alloc]
+    CYAlertView *sheet([[[CYAlertView alloc]
         initWithTitle:title
         buttons:[NSArray arrayWithObjects:UCLocalize("OKAY"), nil]
         defaultButtonIndex:0
@@ -4794,8 +5089,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         message:[NSString stringWithFormat:@"%@\n\n%@", UCLocalize("CONFIGURATION_UPGRADE_EX"), ofile]
         delegate:self
         cancelButtonTitle:UCLocalize("KEEP_OLD_COPY")
-        otherButtonTitles:UCLocalize("ACCEPT_NEW_COPY"),
-        // XXX: UCLocalize("SEE_WHAT_CHANGED"),
+        otherButtonTitles:
+            UCLocalize("ACCEPT_NEW_COPY"),
+            // XXX: UCLocalize("SEE_WHAT_CHANGED"),
         nil
     ] autorelease];
 
@@ -4980,18 +5276,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     } return self;
 }
 
-- (void) _setBackgroundColor {
-    UIColor *color;
-    if (NSString *mode = [package_ mode]) {
-        bool remove([mode isEqualToString:@"REMOVE"] || [mode isEqualToString:@"PURGE"]);
-        color = remove ? RemovingColor_ : InstallingColor_;
-    } else
-        color = [UIColor whiteColor];
-
-    [content_ setBackgroundColor:color];
-    [self setNeedsDisplay];
-}
-
 - (NSString *) accessibilityLabel {
     return [NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), name_, description_];
 }
@@ -5042,11 +5326,36 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         if ((badge_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/Purposes/%@.png", App_, purpose]]) != nil)
             badge_ = [badge_ retain];
 
-    if ([package installed] != nil)
-        if ((placard_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/installed.png", App_]]) != nil)
+    UIColor *color;
+    NSString *placard;
+
+    if (NSString *mode = [package_ mode]) {
+        if ([mode isEqualToString:@"REMOVE"] || [mode isEqualToString:@"PURGE"]) {
+            color = RemovingColor_;
+            //placard = @"removing";
+        } else {
+            color = InstallingColor_;
+            //placard = @"installing";
+        }
+
+        // XXX: the removing/installing placards are not @2x
+        placard = nil;
+    } else {
+        color = [UIColor whiteColor];
+
+        if ([package installed] != nil)
+            placard = @"installed";
+        else
+            placard = nil;
+    }
+
+    [content_ setBackgroundColor:color];
+
+    if (placard != nil)
+        if ((placard_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/%@.png", App_, placard]]) != nil)
             placard_ = [placard_ retain];
 
-    [self _setBackgroundColor];
+    [self setNeedsDisplay];
     [content_ setNeedsDisplay];
 }
 
@@ -5274,12 +5583,12 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @implementation FileTable
 
 - (void) dealloc {
-    if (package_ != nil)
-        [package_ release];
-    if (name_ != nil)
-        [name_ release];
+    [self releaseSubviews];
+
+    [package_ release];
+    [name_ release];
     [files_ release];
-    [list_ release];
+
     [super dealloc];
 }
 
@@ -5305,21 +5614,35 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return cell;
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@/files", [package_ id]]];
+}
+
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
+
+    list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]];
+    [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [list_ setRowHeight:24.0f];
+    [list_ setDataSource:self];
+    [list_ setDelegate:self];
+    [[self view] addSubview:list_];
+}
+
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("INSTALLED_FILES")];
+}
+
+- (void) releaseSubviews {
+    [list_ release];
+    list_ = nil;
+}
+
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super init]) != nil) {
         database_ = database;
 
-        [[self navigationItem] setTitle:UCLocalize("INSTALLED_FILES")];
-
         files_ = [[NSMutableArray arrayWithCapacity:32] retain];
-
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [list_ setRowHeight:24.0f];
-        [[self view] addSubview:list_];
-
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
     } return self;
 }
 
@@ -5369,6 +5692,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) reloadData {
+    [super reloadData];
+
     [self setPackage:[database_ packageWithName:name_]];
 }
 
@@ -5386,8 +5711,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     UIBarButtonItem *button_;
 }
 
-- (id) initWithDatabase:(Database *)database;
-- (void) setPackage:(Package *)package;
+- (id) initWithDatabase:(Database *)database forPackage:(NSString *)name;
 
 @end
 
@@ -5407,8 +5731,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [super dealloc];
 }
 
-- (void) release {
-    [super release];
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@", name_]];
 }
 
 /* XXX: this is not safe at all... localization of /fail/ */
@@ -5439,11 +5763,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     }
 }
 
-- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
-    [super webView:view didClearWindowObject:window forFrame:frame];
-    [window setValue:package_ forKey:@"package"];
-}
-
 - (bool) _allowJavaScriptPanel {
     return commercial_;
 }
@@ -5504,33 +5823,27 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 #endif
 
-- (id) initWithDatabase:(Database *)database {
+- (id) initWithDatabase:(Database *)database forPackage:(NSString *)name {
     if ((self = [super init]) != nil) {
         database_ = database;
         buttons_ = [[NSMutableArray alloc] initWithCapacity:4];
-        [self loadURL:[NSURL URLWithString:CydiaURL(@"ui/package/")]];
+        name_ = [[NSString alloc] initWithString:name];
+        [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/package/#!/%@", UI_, name_]]];
     } return self;
 }
 
-- (void) setPackage:(Package *)package {
-    if (package_ != nil) {
+- (void) reloadData {
+    if (package_ != nil)
         [package_ autorelease];
-        package_ = nil;
-    }
-
-    if (name_ != nil) {
-        [name_ release];
-        name_ = nil;
-    }
+    package_ = [database_ packageWithName:name_];
 
     [buttons_ removeAllObjects];
 
-    if (package != nil) {
-        [package parse];
+    if (package_ != nil) {
+        [package_ parse];
 
-        package_ = [package retain];
-        name_ = [[package id] retain];
-        commercial_ = [package isCommercial];
+        package_ = [package_ retain];
+        commercial_ = [package_ isCommercial];
 
         if ([package_ mode] != nil)
             [buttons_ addObject:UCLocalize("CLEAR")];
@@ -5562,22 +5875,18 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         action:@selector(customButtonClicked)
     ];
 
-    [self reloadURL];
+    [super reloadData];
 }
 
 - (bool) isLoading {
     return commercial_ ? [super isLoading] : false;
 }
 
-- (void) reloadData {
-    [self setPackage:[database_ packageWithName:name_]];
-}
-
 @end
 /* }}} */
 
-/* Package Table {{{ */
-@interface PackageTable : UIView <
+/* Package List Controller {{{ */
+@interface PackageListController : CYViewController <
     UITableViewDataSource,
     UITableViewDelegate
 > {
@@ -5588,29 +5897,16 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     UITableView *list_;
     NSMutableArray *index_;
     NSMutableDictionary *indices_;
-    // XXX: this target_ seems to be delegate_. :(
-    _transient id target_;
-    SEL action_;
-    // XXX: why do we even have this delegate_?
-    _transient id delegate_;
+    NSString *title_;
 }
 
-- (id) initWithFrame:(CGRect)frame database:(Database *)database target:(id)target action:(SEL)action;
-
+- (id) initWithDatabase:(Database *)database title:(NSString *)title;
 - (void) setDelegate:(id)delegate;
-
-- (void) reloadData;
 - (void) resetCursor;
 
-- (UITableView *) list;
-
-- (void) setShouldHideHeaderInShortLists:(BOOL)hide;
-
-- (void) deselectWithAnimation:(BOOL)animated;
-
 @end
 
-@implementation PackageTable
+@implementation PackageListController
 
 - (void) dealloc {
     [packages_ release];
@@ -5618,10 +5914,92 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [list_ release];
     [index_ release];
     [indices_ release];
+    [title_ release];
 
     [super dealloc];
 }
 
+- (void) deselectWithAnimation:(BOOL)animated {
+    [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
+}
+
+- (void) resizeForKeyboardBounds:(CGRect)bounds duration:(NSTimeInterval)duration curve:(UIViewAnimationCurve)curve {
+    CGRect base = [[self view] bounds];
+    base.size.height -= bounds.size.height;
+    base.origin = [list_ frame].origin;
+
+    [UIView beginAnimations:nil context:NULL];
+    [UIView setAnimationBeginsFromCurrentState:YES];
+    [UIView setAnimationCurve:curve];
+    [UIView setAnimationDuration:duration];
+    [list_ setFrame:base];
+    [UIView commitAnimations];
+}
+
+- (void) resizeForKeyboardBounds:(CGRect)bounds duration:(NSTimeInterval)duration {
+    [self resizeForKeyboardBounds:bounds duration:duration curve:UIViewAnimationCurveLinear];
+}
+
+- (void) resizeForKeyboardBounds:(CGRect)bounds {
+    [self resizeForKeyboardBounds:bounds duration:0];
+}
+
+- (void) keyboardWillShow:(NSNotification *)notification {
+    CGRect bounds;
+    CGPoint center;
+    NSTimeInterval duration;
+    UIViewAnimationCurve curve;
+    [[[notification userInfo] objectForKey:UIKeyboardBoundsUserInfoKey] getValue:&bounds];
+    [[[notification userInfo] objectForKey:UIKeyboardCenterEndUserInfoKey] getValue:&center];
+    [[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&curve];
+    [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&duration];
+
+    CGRect kbframe = CGRectMake(round(center.x - bounds.size.width / 2.0), round(center.y - bounds.size.height / 2.0), bounds.size.width, bounds.size.height);
+    UIViewController *base = self;
+    while ([base parentViewController] != nil)
+        base = [base parentViewController];
+    CGRect viewframe = [[base view] convertRect:[list_ frame] fromView:[list_ superview]];
+    CGRect intersection = CGRectIntersection(viewframe, kbframe);
+
+    [self resizeForKeyboardBounds:intersection duration:duration curve:curve];
+}
+
+- (void) keyboardWillHide:(NSNotification *)notification {
+    NSTimeInterval duration;
+    UIViewAnimationCurve curve;
+    [[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&curve];
+    [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&duration];
+
+    [self resizeForKeyboardBounds:CGRectZero duration:duration curve:curve];
+}
+
+- (void) viewWillAppear:(BOOL)animated {
+    [super viewWillAppear:animated];
+
+    [self resizeForKeyboardBounds:CGRectZero];
+    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
+    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
+}
+
+- (void) viewWillDisappear:(BOOL)animated {
+    [super viewWillDisappear:animated];
+
+    [self resizeForKeyboardBounds:CGRectZero];
+    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
+    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
+}
+
+- (void) viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
+    [self deselectWithAnimation:animated];
+}
+
+- (void) didSelectPackage:(Package *)package {
+    CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_ forPackage:[package id]] autorelease]);
+    [view setDelegate:delegate_];
+    [[self navigationController] pushViewController:view animated:YES];
+}
+
 #if TryIndexedCollation
 + (BOOL) hasIndexedCollation {
     return NO; // XXX: objc_getClass("UILocalizedIndexedCollation") != nil;
@@ -5664,15 +6042,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return cell;
 }
 
-- (void) deselectWithAnimation:(BOOL)animated {
-    [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
-}
-
-- (NSIndexPath *) tableView:(UITableView *)table willSelectRowAtIndexPath:(NSIndexPath *)path {
+- (void) tableView:(UITableView *)table didSelectRowAtIndexPath:(NSIndexPath *)path {
     Package *package([self packageAtIndexPath:path]);
     package = [database_ packageWithName:[package id]];
-    [target_ performSelector:action_ withObject:package];
-    return path;
+    [self didSelectPackage:package];
 }
 
 - (NSArray *) sectionIndexTitlesForTableView:(UITableView *)tableView {
@@ -5690,12 +6063,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return index;
 }
 
-- (id) initWithFrame:(CGRect)frame database:(Database *)database target:(id)target action:(SEL)action {
-    if ((self = [super initWithFrame:frame]) != nil) {
+- (id) initWithDatabase:(Database *)database title:(NSString *)title {
+    if ((self = [super init]) != nil) {
         database_ = database;
-
-        target_ = target;
-        action_ = action;
+        title_ = [title copy];
+        [[self navigationItem] setTitle:title_];
 
 #if TryIndexedCollation
         if ([[self class] hasIndexedCollation])
@@ -5709,10 +6081,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         packages_ = [[NSMutableArray arrayWithCapacity:16] retain];
         sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
 
-        list_ = [[UITableView alloc] initWithFrame:[self bounds] style:UITableViewStylePlain];
+        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
         [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
         [list_ setRowHeight:73];
-        [self addSubview:list_];
+        [[self view] addSubview:list_];
 
         [list_ setDataSource:self];
         [list_ setDelegate:self];
@@ -5728,6 +6100,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) reloadData {
+    [super reloadData];
+
     era_ = [database_ era];
     NSArray *packages = [database_ packages];
 
@@ -5817,18 +6191,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [list_ scrollRectToVisible:CGRectMake(0, 0, 0, 0) animated:NO];
 }
 
-- (UITableView *) list {
-    return list_;
-}
-
-- (void) setShouldHideHeaderInShortLists:(BOOL)hide {
-    //XXX:[list_ setShouldHideHeaderInShortLists:hide];
-}
-
 @end
 /* }}} */
-/* Filtered Package Table {{{ */
-@interface FilteredPackageTable : PackageTable {
+/* Filtered Package List Controller {{{ */
+@interface FilteredPackageListController : PackageListController {
     SEL filter_;
     IMP imp_;
     id object_;
@@ -5837,11 +6203,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 - (void) setObject:(id)object;
 - (void) setObject:(id)object forFilter:(SEL)filter;
 
-- (id) initWithFrame:(CGRect)frame database:(Database *)database target:(id)target action:(SEL)action filter:(SEL)filter with:(id)object;
+- (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object;
 
 @end
 
-@implementation FilteredPackageTable
+@implementation FilteredPackageListController
 
 - (void) dealloc {
     if (object_ != nil)
@@ -5879,94 +6245,33 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     _end
 }
 
-- (id) initWithFrame:(CGRect)frame database:(Database *)database target:(id)target action:(SEL)action filter:(SEL)filter with:(id)object {
-    if ((self = [super initWithFrame:frame database:database target:target action:action]) != nil) {
+- (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object {
+    if ((self = [super initWithDatabase:database title:title]) != nil) {
         [self setFilter:filter];
-        object_ = [object retain];
+        [self setObject:object];
         [self reloadData];
     } return self;
 }
 
 @end
-/* }}} */
-/* Filtered Package Controller {{{ */
-@interface FilteredPackageController : CYViewController {
-    _transient Database *database_;
-    FilteredPackageTable *packages_;
-    NSString *title_;
-}
-
-- (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object;
-
-@end
-
-@implementation FilteredPackageController
-
-- (void) dealloc {
-    [packages_ release];
-    [title_ release];
-
-    [super dealloc];
-}
-
-- (void) viewDidAppear:(BOOL)animated {
-    [super viewDidAppear:animated];
-    [packages_ deselectWithAnimation:animated];
-}
-
-- (void) didSelectPackage:(Package *)package {
-    CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_] autorelease]);
-    [view setPackage:package];
-    [view setDelegate:delegate_];
-    [[self navigationController] pushViewController:view animated:YES];
-}
-
-- (NSString *) title { return title_; }
-
-- (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object {
-    if ((self = [super init]) != nil) {
-        database_ = database;
-        title_ = [title copy];
-        [[self navigationItem] setTitle:title_];
-
-        packages_ = [[FilteredPackageTable alloc]
-            initWithFrame:[[self view] bounds]
-            database:database
-            target:self
-            action:@selector(didSelectPackage:)
-            filter:filter
-            with:object
-        ];
-
-        [packages_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [[self view] addSubview:packages_];
-    } return self;
-}
-
-- (void) reloadData {
-    [packages_ reloadData];
-}
-
-- (void) setDelegate:(id)delegate {
-    [super setDelegate:delegate];
-    [packages_ setDelegate:delegate];
-}
-
-@end
-
 /* }}} */
 
 /* Home Controller {{{ */
 @interface HomeController : CYBrowserController {
 }
+
 @end
 
 @implementation HomeController
 
-+ (BOOL)shouldHideNavigationBar {
++ (BOOL) shouldHideNavigationBar {
     return NO;
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://home"];
+}
+
 - (void) _setMoreHeaders:(NSMutableURLRequest *)request {
     [super _setMoreHeaders:request];
 
@@ -5986,7 +6291,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [alert setCancelButtonIndex:0];
 
     [alert setMessage:
-        @"Copyright (C) 2008-2010\n"
+        @"Copyright (C) 2008-2011\n"
         "Jay Freeman (saurik)\n"
         "saurik@saurik.com\n"
         "http://www.saurik.com/"
@@ -5995,13 +6300,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [alert show];
 }
 
-- (void) viewWillAppear:(BOOL)animated {
-    [super viewWillAppear:animated];
-
-    if ([[self class] shouldHideNavigationBar])
-        [[self navigationController] setNavigationBarHidden:YES animated:animated];
-}
-
 - (void) viewWillDisappear:(BOOL)animated {
     [super viewWillDisappear:animated];
 
@@ -6009,17 +6307,23 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         [[self navigationController] setNavigationBarHidden:NO animated:animated];
 }
 
-- (id) init {
-    if ((self = [super init]) != nil) {
-        [self loadURL:[NSURL URLWithString:CydiaURL(@"ui/home/")]];
+- (void) viewWillAppear:(BOOL)animated {
+    if (![self hasLoaded])
+        [self loadURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/home/", UI_]]];
 
-        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:UCLocalize("ABOUT")
-            style:UIBarButtonItemStylePlain
-            target:self
-            action:@selector(aboutButtonClicked)
-        ] autorelease]];
-    } return self;
+    [super viewWillAppear:animated];
+
+    if ([[self class] shouldHideNavigationBar])
+        [[self navigationController] setNavigationBarHidden:YES animated:animated];
+}
+
+- (void) viewDidLoad {
+    [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+        initWithTitle:UCLocalize("ABOUT")
+        style:UIBarButtonItemStylePlain
+        target:self
+        action:@selector(aboutButtonClicked)
+    ] autorelease]];
 }
 
 @end
@@ -6029,25 +6333,31 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) queueStatusDidChange;
+
 @end
 
 @implementation ManageController
 
-- (id) init {
-    if ((self = [super init]) != nil) {
-        [[self navigationItem] setTitle:UCLocalize("MANAGE")];
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://manage"];
+}
 
-        [self loadURL:[NSURL URLWithString:CydiaURL(@"ui/manage/")]];
+- (void) viewWillAppear:(BOOL)animated {
+    if (![self hasLoaded])
+        [self loadURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/manage/", UI_]]];
 
-        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:UCLocalize("SETTINGS")
-            style:UIBarButtonItemStylePlain
-            target:self
-            action:@selector(settingsButtonClicked)
-        ] autorelease]];
+    [super viewWillAppear:animated];
+}
 
-        [self queueStatusDidChange];
-    } return self;
+- (void) viewDidLoad {
+    [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+        initWithTitle:UCLocalize("SETTINGS")
+        style:UIBarButtonItemStylePlain
+        target:self
+        action:@selector(settingsButtonClicked)
+    ] autorelease]];
+
+    [self queueStatusDidChange];
 }
 
 - (void) settingsButtonClicked {
@@ -6060,11 +6370,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) applyLoadingTitle {
-    // No "Loading" title.
+    // Disable "Loading" title.
 }
 
 - (void) applyRightButton {
-    // No right button.
+    // Disable right button.
 }
 #endif
 
@@ -6084,6 +6394,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (bool) isLoading {
+    // Never show as loading.
     return false;
 }
 
@@ -6137,14 +6448,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [prompt_ setFrame:prmrect];
 }
 
-- (void)setFrame:(CGRect)frame {
+- (void) setFrame:(CGRect)frame {
     [super setFrame:frame];
-
     [self positionViews];
 }
 
 - (id) initWithFrame:(CGRect)frame delegate:(id)delegate {
-    if ((self = [super initWithFrame:frame])) {
+    if ((self = [super initWithFrame:frame]) != nil) {
         [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
 
         [self setBarStyle:UIBarStyleBlack];
@@ -6210,6 +6520,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 /* Cydia Tab Bar Controller {{{ */
 @interface CYTabBarController : UITabBarController <
+    UITabBarControllerDelegate,
     ProgressDelegate
 > {
     _transient Database *database_;
@@ -6221,8 +6532,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     _transient NSObject<CydiaDelegate> *updatedelegate_;
 
     id root_;
+    UIViewController *remembered_;
+    _transient UIViewController *transient_;
 }
 
+- (NSArray *) navigationURLCollection;
 - (void) dropBar:(BOOL)animated;
 - (void) beginUpdate;
 - (void) raiseBar:(BOOL)animated;
@@ -6232,19 +6546,68 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @implementation CYTabBarController
 
-- (void) reloadData {
-    size_t count([[self viewControllers] count]);
-    for (size_t i(0); i != count; ++i) {
-        CYNavigationController *page([[self viewControllers] objectAtIndex:(count - i - 1)]);
-        [page reloadData];
+- (void) setUnselectedViewController:(UIViewController *)transient {
+    NSMutableArray *controllers = [[self viewControllers] mutableCopy];
+    if (transient != nil) {
+        if (transient_ == nil)
+            remembered_ = [[controllers objectAtIndex:0] retain];
+        transient_ = transient;
+        [transient_ setTabBarItem:[remembered_ tabBarItem]];
+        [controllers replaceObjectAtIndex:0 withObject:transient_];
+        [self setSelectedIndex:0];
+        [self setViewControllers:controllers];
+        [self concealTabBarSelection];
+    } else if (remembered_ != nil) {
+        [remembered_ setTabBarItem:[transient_ tabBarItem]];
+        transient_ = transient;
+        [controllers replaceObjectAtIndex:0 withObject:remembered_];
+        [remembered_ release];
+        remembered_ = nil;
+        [self setViewControllers:controllers];
+        [self revealTabBarSelection];
     }
+}
+
+- (UIViewController *) unselectedViewController {
+    return transient_;
+}
+
+- (void) tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
+    if ([self unselectedViewController])
+        [self setUnselectedViewController:nil];
+}
+
+- (NSArray *) navigationURLCollection {
+    NSMutableArray *items([NSMutableArray array]);
+
+    // XXX: Should this deal with transient view controllers?
+    for (id navigation in [self viewControllers]) {
+        NSArray *stack = [navigation performSelector:@selector(navigationURLCollection)];
+        if (stack != nil)
+            [items addObject:stack];
+    }
+
+    return items;
+}
+
+- (void) reloadData {
+    for (CYViewController *controller in [self viewControllers])
+        [controller reloadData];
 
-    [(CYNavigationController *)[self transientViewController] reloadData];
+    [(CYNavigationController *)[self unselectedViewController] reloadData];
+}
+
+- (void) dealloc {
+    [refreshbar_ release];
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+    [super dealloc];
 }
 
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super init]) != nil) {
         database_ = database;
+        [self setDelegate:self];
 
         [[self view] setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
@@ -6466,12 +6829,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     }
 }
 
-- (void) dealloc {
-    [refreshbar_ release];
-    [[NSNotificationCenter defaultCenter] removeObserver:self];
-    [super dealloc];
-}
-
 @end
 /* }}} */
 /* Cydia Navigation Controller {{{ */
@@ -6480,6 +6837,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     _transient id<UINavigationControllerDelegate> delegate_;
 }
 
+- (NSArray *) navigationURLCollection;
 - (id) initWithDatabase:(Database *)database;
 - (void) reloadData;
 
@@ -6488,15 +6846,25 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @implementation CYNavigationController
 
-- (void) dealloc {
-    [super dealloc];
+- (NSArray *) navigationURLCollection {
+    NSMutableArray *stack([NSMutableArray array]);
+
+    for (CYViewController *controller in [self viewControllers]) {
+        NSString *url = [[controller navigationURL] absoluteString];
+        if (url != nil)
+            [stack addObject:url];
+    }
+
+    return stack;
 }
 
 - (void) reloadData {
-    size_t count([[self viewControllers] count]);
-    for (size_t i(0); i != count; ++i) {
-        CYViewController *page([[self viewControllers] objectAtIndex:(count - i - 1)]);
-        [page reloadData];
+    for (CYViewController *page in [self viewControllers]) {
+        // Only reload controllers that have already loaded.
+        // This prevents a page from accidentally loading too
+        // early if it hasn't been shown on the screen yet.
+        if ([page hasLoaded])
+            [page reloadData];
     }
 }
 
@@ -6615,7 +6983,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 /* }}} */
 
 /* Section Controller {{{ */
-@interface SectionController : FilteredPackageController {
+@interface SectionController : FilteredPackageListController {
+    NSString *section_;
 }
 
 - (id) initWithDatabase:(Database *)database section:(NSString *)section;
@@ -6624,18 +6993,25 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @implementation SectionController
 
+- (NSURL *) navigationURL {
+    NSString *name = section_;
+    if (name == nil)
+        name = @"all";
+
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://sections/%@", name]];
+}
+
 - (id) initWithDatabase:(Database *)database section:(NSString *)name {
     NSString *title;
-
-    if (name == nil) {
+    if (name == nil)
         title = UCLocalize("ALL_PACKAGES");
-    } else if (![name isEqual:@""]) {
+    else if (![name isEqual:@""])
         title = [[NSBundle mainBundle] localizedStringForKey:Simplify(name) value:nil table:@"Sections"];
-    } else {
+    else
         title = UCLocalize("NO_SECTION");
-    }
 
     if ((self = [super initWithDatabase:database title:title filter:@selector(isVisibleInSection:) with:name]) != nil) {
+        section_ = name;
     } return self;
 }
 
@@ -6654,9 +7030,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (id) initWithDatabase:(Database *)database;
-- (void) reloadData;
-- (void) resetView;
-
 - (void) editButtonClicked;
 
 @end
@@ -6664,15 +7037,17 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @implementation SectionsController
 
 - (void) dealloc {
-    [list_ setDataSource:nil];
-    [list_ setDelegate:nil];
-
+    [self releaseSubviews];
     [sections_ release];
     [filtered_ release];
-    [list_ release];
+
     [super dealloc];
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://sections"];
+}
+
 - (void) updateNavigationItem {
     [[self navigationItem] setTitle:editing_ ? UCLocalize("SECTION_VISIBILITY") : UCLocalize("SECTIONS")];
     if ([sections_ count] == 0) {
@@ -6686,6 +7061,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     }
 }
 
+- (BOOL) isEditing {
+    return editing_;
+}
+
 - (void) setEditing:(BOOL)editing {
     if ((editing_ = editing))
         [list_ reloadData];
@@ -6721,7 +7100,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 - (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     static NSString *reuseIdentifier = @"SectionCell";
 
-    SectionCell *cell = (SectionCell *) [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
+    SectionCell *cell = (SectionCell *)[tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
     if (cell == nil)
         cell = [[[SectionCell alloc] initWithFrame:CGRectZero reuseIdentifier:reuseIdentifier] autorelease];
 
@@ -6745,28 +7124,38 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [[self navigationController] pushViewController:controller animated:YES];
 }
 
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
+
+    list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]];
+    [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [list_ setRowHeight:45.0f];
+    [list_ setDataSource:self];
+    [list_ setDelegate:self];
+    [[self view] addSubview:list_];
+}
+
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("SECTIONS")];
+}
+
+- (void) releaseSubviews {
+    [list_ release];
+    list_ = nil;
+}
+
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super init]) != nil) {
         database_ = database;
 
-        [[self navigationItem] setTitle:UCLocalize("SECTIONS")];
-
         sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
         filtered_ = [[NSMutableArray arrayWithCapacity:16] retain];
-
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [list_ setRowHeight:45.0f];
-        [[self view] addSubview:list_];
-
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
-
-        [self reloadData];
     } return self;
 }
 
 - (void) reloadData {
+    [super reloadData];
+
     NSArray *packages = [database_ packages];
 
     [sections_ removeAllObjects];
@@ -6785,7 +7174,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             section = [sections objectForKey:key];
             if (section == nil) {
                 _profile(SectionsView$reloadData$Section$Allocate)
-                    section = [[[Section alloc] initWithName:name localize:YES] autorelease];
+                    section = [[[Section alloc] initWithName:key localize:YES] autorelease];
                     [sections setObject:section forKey:key];
                 _end
             }
@@ -6821,13 +7210,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     _trace();
 }
 
-- (void) resetView {
-    if (editing_)
-        [self editButtonClicked];
-}
-
-- (void)editButtonClicked {
-    [self setEditing:!editing_];
+- (void) editButtonClicked {
+    [self setEditing:(!editing_)];
 }
 
 @end
@@ -6854,18 +7238,26 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @implementation ChangesController
 
 - (void) dealloc {
-    [list_ setDelegate:nil];
-    [list_ setDataSource:nil];
-
+    [self releaseSubviews];
     CFRelease(packages_);
-
     [sections_ release];
-    [list_ release];
+
     [super dealloc];
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://changes"];
+}
+
+- (void) viewWillAppear:(BOOL)animated {
+    // Loads after it appears, so don't load beforehand.
+    loaded_ = YES;
+    [super viewWillAppear:animated];
+}
+
 - (void) viewDidAppear:(BOOL)animated {
     [super viewDidAppear:animated];
+
     if (!hasSentFirstLoad_) {
         hasSentFirstLoad_ = YES;
         [self performSelector:@selector(reloadData) withObject:nil afterDelay:0.0];
@@ -6918,9 +7310,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 - (NSIndexPath *) tableView:(UITableView *)table willSelectRowAtIndexPath:(NSIndexPath *)path {
     Package *package([self packageAtIndexPath:path]);
-    CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_] autorelease]);
+    CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_ forPackage:[package id]] autorelease]);
     [view setDelegate:delegate_];
-    [view setPackage:package];
     [[self navigationController] pushViewController:view animated:YES];
     return path;
 }
@@ -6934,23 +7325,32 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [delegate_ distUpgrade];
 }
 
-- (NSString *) title { return UCLocalize("CHANGES"); }
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
+
+    list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
+    [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [list_ setRowHeight:73];
+    [list_ setDataSource:self];
+    [list_ setDelegate:self];
+    [[self view] addSubview:list_];
+}
+
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("CHANGES")];
+}
+
+- (void) releaseSubviews {
+    [list_ release];
+    list_ = nil;
+}
 
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super init]) != nil) {
         database_ = database;
-        [[self navigationItem] setTitle:UCLocalize("CHANGES")];
 
         packages_ = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
         sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
-
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [list_ setRowHeight:73];
-        [[self view] addSubview:list_];
-
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
     } return self;
 }
 
@@ -7072,10 +7472,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @end
 /* }}} */
 /* Search Controller {{{ */
-@interface SearchController : FilteredPackageController <
+@interface SearchController : FilteredPackageListController <
     UISearchBarDelegate
 > {
     UISearchBar *search_;
+    BOOL searchloaded_;
 }
 
 - (id) initWithDatabase:(Database *)database;
@@ -7091,29 +7492,41 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [super dealloc];
 }
 
+- (NSURL *) navigationURL {
+    if ([search_ text] == nil || [[search_ text] isEqualToString:@""])
+        return [NSURL URLWithString:@"cydia://search"];
+    else
+        return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://search/%@", [search_ text]]];
+}
+
 - (void) setSearchTerm:(NSString *)searchTerm {
     [search_ setText:searchTerm];
+    [self reloadData];
 }
 
 - (void) searchBarSearchButtonClicked:(UISearchBar *)searchBar {
-    [packages_ setObject:[search_ text] forFilter:@selector(isUnfilteredAndSearchedForBy:)];
+    [self setObject:[search_ text] forFilter:@selector(isUnfilteredAndSearchedForBy:)];
     [search_ resignFirstResponder];
     [self reloadData];
 }
 
 - (void) searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)text {
-    [packages_ setObject:text forFilter:@selector(isUnfilteredAndSelectedForBy:)];
+    [self setObject:text forFilter:@selector(isUnfilteredAndSelectedForBy:)];
     [self reloadData];
 }
 
 - (id) initWithDatabase:(Database *)database {
-    return [super initWithDatabase:database title:UCLocalize("SEARCH") filter:@selector(isUnfilteredAndSearchedForBy:) with:nil];
+    if ((self = [super initWithDatabase:database title:UCLocalize("SEARCH") filter:@selector(isUnfilteredAndSearchedForBy:) with:nil])) {
+        search_ = [[UISearchBar alloc] init];
+    } return self;
 }
 
-- (void)viewDidAppear:(BOOL)animated {
+- (void) viewDidAppear:(BOOL)animated {
     [super viewDidAppear:animated];
-    if (!search_) {
-        search_ = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, [[self view] bounds].size.width, 44.0f)];
+
+    if (!searchloaded_) {
+        searchloaded_ = YES;
+        [search_ setFrame:CGRectMake(0, 0, [[self view] bounds].size.width, 44.0f)];
         [search_ layoutSubviews];
         [search_ setPlaceholder:UCLocalize("SEARCH_EX")];
 
@@ -7130,15 +7543,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     }
 }
 
-- (void) _reloadData {
-}
-
 - (void) reloadData {
-    _profile(SearchController$reloadData)
-        [packages_ reloadData];
-    _end
-    PrintTimes();
-    [packages_ resetCursor];
+    [self setObject:[search_ text]];
+    [super reloadData];
+    [self resetCursor];
 }
 
 - (void) didSelectPackage:(Package *)package {
@@ -7170,38 +7578,44 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @implementation PackageSettingsController
 
 - (void) dealloc {
+    [self releaseSubviews];
     [name_ release];
-    if (package_ != nil)
-        [package_ release];
-    [table_ release];
-    [subscribedSwitch_ release];
-    [ignoredSwitch_ release];
-    [subscribedCell_ release];
-    [ignoredCell_ release];
+    [package_ release];
 
     [super dealloc];
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@/settings", [package_ id]]];
+}
+
 - (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
     if (package_ == nil)
         return 0;
 
-    return 1;
+    if ([package_ installed] == nil)
+        return 1;
+    else
+        return 2;
 }
 
 - (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
     if (package_ == nil)
         return 0;
 
-    return 2;
+    // both sections contain just one item right now.
+    return 1;
 }
 
 - (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
-    return UCLocalize("CHANGE_PACKAGE_SETTINGS");
+    return nil;
 }
 
 - (NSString *) tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
-    return UCLocalize("SHOW_ALL_CHANGES_EX");
+    if (section == 0)
+        return UCLocalize("SHOW_ALL_CHANGES_EX");
+    else
+        return UCLocalize("IGNORE_UPGRADES_EX");
 }
 
 - (void) onSubscribed:(id)control {
@@ -7212,15 +7626,50 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         [delegate_ updateData];
 }
 
+- (void) _updateIgnored {
+    const char *package([name_ UTF8String]);
+    bool on([ignoredSwitch_ isOn]);
+
+    pid_t pid(ExecFork());
+    if (pid == 0) {
+        FILE *dpkg(popen("dpkg --set-selections", "w"));
+        fwrite(package, strlen(package), 1, dpkg);
+
+        if (on)
+            fwrite(" hold\n", 6, 1, dpkg);
+        else
+            fwrite(" install\n", 9, 1, dpkg);
+
+        pclose(dpkg);
+
+        exit(0);
+        _assert(false);
+    }
+
+    _forever {
+        int status;
+        int result(waitpid(pid, &status, 0));
+
+        if (result != -1) {
+            _assert(result == pid);
+            break;
+        }
+    }
+}
+
 - (void) onIgnored:(id)control {
-    // TODO: set Held state - possibly call out to dpkg, etc.
+    NSInvocation *invocation([NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:@selector(_updateIgnored)]]);
+    [invocation setTarget:self];
+    [invocation setSelector:@selector(_updateIgnored)];
+
+    [delegate_ reloadDataWithInvocation:invocation];
 }
 
 - (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     if (package_ == nil)
         return nil;
 
-    switch ([indexPath row]) {
+    switch ([indexPath section]) {
         case 0: return subscribedCell_;
         case 1: return ignoredCell_;
 
@@ -7230,101 +7679,83 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return nil;
 }
 
-- (NSString *) title { return UCLocalize("SETTINGS"); }
-
-- (id) initWithDatabase:(Database *)database package:(NSString *)package {
-    if ((self = [super init])) {
-        database_ = database;
-        name_ = [package retain];
-
-        [[self navigationItem] setTitle:UCLocalize("SETTINGS")];
-
-        table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped];
-        [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [[self view] addSubview:table_];
-
-        subscribedSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
-        [subscribedSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
-        [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:UIControlEventValueChanged];
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
 
-        ignoredSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
-        [ignoredSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
-        [ignoredSwitch_ addTarget:self action:@selector(onIgnored:) forEvents:UIControlEventValueChanged];
-        // Disable this switch, since it only reflects (not modifies) the ignored state.
-        [ignoredSwitch_ setUserInteractionEnabled:NO];
+    table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped];
+    [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [table_ setDataSource:self];
+    [table_ setDelegate:self];
+    [[self view] addSubview:table_];
 
-        subscribedCell_ = [[UITableViewCell alloc] init];
-        [subscribedCell_ setText:UCLocalize("SHOW_ALL_CHANGES")];
-        [subscribedCell_ setAccessoryView:subscribedSwitch_];
-        [subscribedCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
+    subscribedSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
+    [subscribedSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
+    [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:UIControlEventValueChanged];
 
-        ignoredCell_ = [[UITableViewCell alloc] init];
-        [ignoredCell_ setText:UCLocalize("IGNORE_UPGRADES")];
-        [ignoredCell_ setAccessoryView:ignoredSwitch_];
-        [ignoredCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
-        // FIXME: Ignored state is not saved.
-        [ignoredCell_ setUserInteractionEnabled:NO];
-
-        [table_ setDataSource:self];
-        [table_ setDelegate:self];
-        [self reloadData];
-    } return self;
-}
+    ignoredSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
+    [ignoredSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
+    [ignoredSwitch_ addTarget:self action:@selector(onIgnored:) forEvents:UIControlEventValueChanged];
 
-- (void) reloadData {
-    if (package_ != nil)
-        [package_ autorelease];
-    package_ = [database_ packageWithName:name_];
-    if (package_ != nil) {
-        [package_ retain];
-        [subscribedSwitch_ setOn:([package_ subscribed] ? 1 : 0) animated:NO];
-        [ignoredSwitch_ setOn:([package_ ignored] ? 1 : 0) animated:NO];
-    }
+    subscribedCell_ = [[UITableViewCell alloc] init];
+    [subscribedCell_ setText:UCLocalize("SHOW_ALL_CHANGES")];
+    [subscribedCell_ setAccessoryView:subscribedSwitch_];
+    [subscribedCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
 
-    [table_ reloadData];
+    ignoredCell_ = [[UITableViewCell alloc] init];
+    [ignoredCell_ setText:UCLocalize("IGNORE_UPGRADES")];
+    [ignoredCell_ setAccessoryView:ignoredSwitch_];
+    [ignoredCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
 }
 
-@end
-/* }}} */
-/* Signature Controller {{{ */
-@interface SignatureController : CYBrowserController {
-    _transient Database *database_;
-    NSString *package_;
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("SETTINGS")];
 }
 
-- (id) initWithDatabase:(Database *)database package:(NSString *)package;
+- (void) releaseSubviews {
+    [ignoredCell_ release];
+    ignoredCell_ = nil;
 
-@end
+    [subscribedCell_ release];
+    subscribedCell_ = nil;
 
-@implementation SignatureController
+    [table_ release];
+    table_ = nil;
 
-- (void) dealloc {
-    [package_ release];
-    [super dealloc];
-}
+    [ignoredSwitch_ release];
+    ignoredSwitch_ = nil;
 
-- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
-    // XXX: dude!
-    [super webView:view didClearWindowObject:window forFrame:frame];
+    [subscribedSwitch_ release];
+    subscribedSwitch_ = nil;
 }
 
 - (id) initWithDatabase:(Database *)database package:(NSString *)package {
     if ((self = [super init]) != nil) {
         database_ = database;
-        package_ = [package retain];
-        [self reloadData];
+        name_ = [package retain];
     } return self;
 }
 
 - (void) reloadData {
-    [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"signature" ofType:@"html"]]];
+    [super reloadData];
+
+    if (package_ != nil)
+        [package_ autorelease];
+    package_ = [database_ packageWithName:name_];
+
+    if (package_ != nil) {
+        package_ = [package_ retain];
+        [subscribedSwitch_ setOn:([package_ subscribed] ? 1 : 0) animated:NO];
+        [ignoredSwitch_ setOn:([package_ ignored] ? 1 : 0) animated:NO];
+    } // XXX: what now, G?
+
+    [table_ reloadData];
 }
 
 @end
 /* }}} */
 
 /* Installed Controller {{{ */
-@interface InstalledController : FilteredPackageController {
+@interface InstalledController : FilteredPackageListController {
     BOOL expert_;
 }
 
@@ -7341,7 +7772,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [super dealloc];
 }
 
-- (NSString *) title { return UCLocalize("INSTALLED"); }
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://installed"];
+}
 
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super initWithDatabase:database title:UCLocalize("INSTALLED") filter:@selector(isInstalledAndUnfiltered:) with:[NSNumber numberWithBool:YES]]) != nil) {
@@ -7373,10 +7806,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 #endif
 }
 
-- (void) reloadData {
-    [packages_ reloadData];
-}
-
 - (void) updateRoleButton {
     if (Role_ != nil && ![Role_ isEqualToString:@"Developer"])
         [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
@@ -7388,18 +7817,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) roleButtonClicked {
-    [packages_ setObject:[NSNumber numberWithBool:expert_]];
-    [packages_ reloadData];
+    [self setObject:[NSNumber numberWithBool:expert_]];
+    [self reloadData];
     expert_ = !expert_;
 
     [self updateRoleButton];
 }
 
-- (void) setDelegate:(id)delegate {
-    [super setDelegate:delegate];
-    [packages_ setDelegate:delegate];
-}
-
 @end
 /* }}} */
 
@@ -7489,7 +7913,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @end
 /* }}} */
 /* Source Controller {{{ */
-@interface SourceController : FilteredPackageController {
+@interface SourceController : FilteredPackageListController {
+    _transient Source *source_;
+    NSString *key_;
 }
 
 - (id) initWithDatabase:(Database *)database source:(Source *)source;
@@ -7498,11 +7924,27 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @implementation SourceController
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://sources/%@", [source_ name]]];
+}
+
 - (id) initWithDatabase:(Database *)database source:(Source *)source {
     if ((self = [super initWithDatabase:database title:[source label] filter:@selector(isVisibleInSource:) with:source]) != nil) {
+        source_ = source;
+        key_ = [[source key] retain];
     } return self;
 }
 
+- (void) reloadData {
+    source_ = [database_ sourceWithKey:key_];
+    [key_ release];
+    key_ = [[source_ key] retain];
+    [self setObject:source_];
+    [[self navigationItem] setTitle:[source_ label]];
+
+    [super reloadData];
+}
+
 @end
 /* }}} */
 /* Sources Controller {{{ */
@@ -7529,7 +7971,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (id) initWithDatabase:(Database *)database;
-
 - (void) updateButtonsForEditingStatus:(BOOL)editing animated:(BOOL)animated;
 
 @end
@@ -7545,12 +7986,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) dealloc {
-    if (href_ != nil)
-        [href_ release];
-    if (hud_ != nil)
-        [hud_ release];
-    if (error_ != nil)
-        [error_ release];
+    [self releaseSubviews];
+
+    [href_ release];
+    [hud_ release];
+    [error_ release];
 
     //[self _releaseConnection:installer_];
     [self _releaseConnection:trivial_];
@@ -7559,10 +7999,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     //[self _releaseConnection:automatic_];
 
     [sources_ release];
-    [list_ release];
     [super dealloc];
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://sources"];
+}
+
 - (void) viewDidAppear:(BOOL)animated {
     [super viewDidAppear:animated];
     [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
@@ -7626,12 +8069,12 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [[self navigationController] pushViewController:controller animated:YES];
 }
 
-- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
+- (BOOL) tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
     Source *source = [self sourceAtIndexPath:indexPath];
     return [source record] != nil;
 }
 
-- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
+- (void) tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
     Source *source = [self sourceAtIndexPath:indexPath];
     [Sources_ removeObjectForKey:[source key]];
     [delegate_ syncData];
@@ -7696,7 +8139,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                     message:warning
                     delegate:self
                     cancelButtonTitle:UCLocalize("CANCEL")
-                    otherButtonTitles:UCLocalize("ADD_ANYWAY"), nil
+                    otherButtonTitles:
+                        UCLocalize("ADD_ANYWAY"),
+                    nil
                 ] autorelease];
 
                 [alert setContext:@"warning"];
@@ -7764,8 +8209,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [self _endConnection:connection];
 }
 
-- (NSString *) title { return UCLocalize("SOURCES"); }
-
 - (NSURLConnection *) _requestHRef:(NSString *)href method:(NSString *)method {
     NSMutableURLRequest *request = [NSMutableURLRequest
         requestWithURL:[NSURL URLWithString:href]
@@ -7785,7 +8228,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
 }
 
-- (void)alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
+- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
     NSString *context([alert context]);
 
     if ([context isEqualToString:@"source"]) {
@@ -7843,27 +8286,37 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     }
 }
 
-- (id) initWithDatabase:(Database *)database {
-    if ((self = [super init]) != nil) {
-        [[self navigationItem] setTitle:UCLocalize("SOURCES")];
-        [self updateButtonsForEditingStatus:NO animated:NO];
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
 
-        database_ = database;
-        sources_ = [[NSMutableArray arrayWithCapacity:16] retain];
+    list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
+    [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [list_ setRowHeight:56];
+    [list_ setDataSource:self];
+    [list_ setDelegate:self];
+    [[self view] addSubview:list_];
+}
 
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [list_ setRowHeight:56];
-        [[self view] addSubview:list_];
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("SOURCES")];
+    [self updateButtonsForEditingStatus:NO animated:NO];
+}
 
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
+- (void) releaseSubviews {
+    [list_ release];
+    list_ = nil;
+}
 
-        [self reloadData];
+- (id) initWithDatabase:(Database *)database {
+    if ((self = [super init]) != nil) {
+        database_ = database;
+        sources_ = [[NSMutableArray arrayWithCapacity:16] retain];
     } return self;
 }
 
 - (void) reloadData {
+    [super reloadData];
+
     pkgSourceList list;
     if (!list.ReadMainList())
         return;
@@ -7893,7 +8346,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         message:nil
         delegate:self
         cancelButtonTitle:UCLocalize("CANCEL")
-        otherButtonTitles:UCLocalize("ADD_SOURCE"), nil
+        otherButtonTitles:
+            UCLocalize("ADD_SOURCE"),
+        nil
     ] autorelease];
 
     [alert setContext:@"source"];
@@ -7972,48 +8427,63 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @end
 
 @implementation SettingsController
+
 - (void) dealloc {
-    [table_ release];
-    [segment_ release];
-    [container_ release];
+    [self releaseSubviews];
 
     [super dealloc];
 }
 
-- (id) initWithDatabase:(Database *)database delegate:(id)delegate {
-    if ((self = [super init])) {
-        database_ = database;
-        roledelegate_ = delegate;
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
 
-        [[self navigationItem] setTitle:UCLocalize("WHO_ARE_YOU")];
+    table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped];
+    [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [table_ setDelegate:self];
+    [table_ setDataSource:self];
+    [[self view] addSubview:table_];
 
-        NSArray *items = [NSArray arrayWithObjects:
-            UCLocalize("USER"),
-            UCLocalize("HACKER"),
-            UCLocalize("DEVELOPER"),
-        nil];
-        segment_ = [[UISegmentedControl alloc] initWithItems:items];
-        container_ = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [[self view] frame].size.width, 44.0f)];
-        [container_ addSubview:segment_];
-
-        int index = -1;
-        if ([Role_ isEqualToString:@"User"]) index = 0;
-        if ([Role_ isEqualToString:@"Hacker"]) index = 1;
-        if ([Role_ isEqualToString:@"Developer"]) index = 2;
-        if (index != -1) {
-            [segment_ setSelectedSegmentIndex:index];
-            [self showDoneButton];
-        }
+    NSArray *items = [NSArray arrayWithObjects:
+        UCLocalize("USER"),
+        UCLocalize("HACKER"),
+        UCLocalize("DEVELOPER"),
+    nil];
+    segment_ = [[UISegmentedControl alloc] initWithItems:items];
+    container_ = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [[self view] frame].size.width, 44.0f)];
+    [container_ addSubview:segment_];
+}
+
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("WHO_ARE_YOU")];
 
-        [segment_ addTarget:self action:@selector(segmentChanged:) forControlEvents:UIControlEventValueChanged];
-        [self resizeSegmentedControl];
+    int index = -1;
+    if ([Role_ isEqualToString:@"User"]) index = 0;
+    if ([Role_ isEqualToString:@"Hacker"]) index = 1;
+    if ([Role_ isEqualToString:@"Developer"]) index = 2;
+    if (index != -1) {
+        [segment_ setSelectedSegmentIndex:index];
+        [self showDoneButton];
+    }
+
+    [segment_ addTarget:self action:@selector(segmentChanged:) forControlEvents:UIControlEventValueChanged];
+    [self resizeSegmentedControl];
+}
+
+- (void) releaseSubviews {
+    [table_ release];
+    table_ = nil;
+
+    [segment_ release];
+    segment_ = nil;
 
-        table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped];
-        [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [table_ setDelegate:self];
-        [table_ setDataSource:self];
-        [[self view] addSubview:table_];
-        [table_ reloadData];
+    [container_ release];
+    container_ = nil;
+}
+
+- (id) initWithDatabase:(Database *)database delegate:(id)delegate {
+    if ((self = [super init]) != nil) {
+        database_ = database;
+        roledelegate_ = delegate;
     } return self;
 }
 
@@ -8103,7 +8573,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return 0; // :(
 }
 
-- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     return nil; // This method is required by the protocol.
 }
 
@@ -8128,61 +8598,83 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return section == 3 ? container_ : nil;
 }
 
+- (void) reloadData {
+    [super reloadData];
+    [table_ reloadData];
+}
+
 @end
 /* }}} */
 /* Stash Controller {{{ */
 @interface StashController : CYViewController {
-    // XXX: just delete these things
-    _transient UIActivityIndicatorView *spinner_;
-    _transient UILabel *status_;
-    _transient UILabel *caption_;
+    UIActivityIndicatorView *spinner_;
+    UILabel *status_;
+    UILabel *caption_;
 }
+
 @end
 
 @implementation StashController
-- (id) init {
-    if ((self = [super init])) {
-        [[self view] setBackgroundColor:[UIColor viewFlipsideBackgroundColor]];
 
-        spinner_ = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge] autorelease];
-        CGRect spinrect = [spinner_ frame];
-        spinrect.origin.x = ([[self view] frame].size.width / 2) - (spinrect.size.width / 2);
-        spinrect.origin.y = [[self view] frame].size.height - 80.0f;
-        [spinner_ setFrame:spinrect];
-        [spinner_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin];
-        [[self view] addSubview:spinner_];
-        [spinner_ startAnimating];
+- (void) dealloc {
+    [self releaseSubviews];
 
-        CGRect captrect;
-        captrect.size.width = [[self view] frame].size.width;
-        captrect.size.height = 40.0f;
-        captrect.origin.x = 0;
-        captrect.origin.y = ([[self view] frame].size.height / 2) - (captrect.size.height * 2);
-        caption_ = [[[UILabel alloc] initWithFrame:captrect] autorelease];
-        [caption_ setText:UCLocalize("PREPARING_FILESYSTEM")];
-        [caption_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
-        [caption_ setFont:[UIFont boldSystemFontOfSize:28.0f]];
-        [caption_ setTextColor:[UIColor whiteColor]];
-        [caption_ setBackgroundColor:[UIColor clearColor]];
-        [caption_ setShadowColor:[UIColor blackColor]];
-        [caption_ setTextAlignment:UITextAlignmentCenter];
-        [[self view] addSubview:caption_];
-
-        CGRect statusrect;
-        statusrect.size.width = [[self view] frame].size.width;
-        statusrect.size.height = 30.0f;
-        statusrect.origin.x = 0;
-        statusrect.origin.y = ([[self view] frame].size.height / 2) - statusrect.size.height;
-        status_ = [[[UILabel alloc] initWithFrame:statusrect] autorelease];
-        [status_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
-        [status_ setText:UCLocalize("EXIT_WHEN_COMPLETE")];
-        [status_ setFont:[UIFont systemFontOfSize:16.0f]];
-        [status_ setTextColor:[UIColor whiteColor]];
-        [status_ setBackgroundColor:[UIColor clearColor]];
-        [status_ setShadowColor:[UIColor blackColor]];
-        [status_ setTextAlignment:UITextAlignmentCenter];
-        [[self view] addSubview:status_];
-    } return self;
+    [super dealloc];
+}
+
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
+    [[self view] setBackgroundColor:[UIColor viewFlipsideBackgroundColor]];
+
+    spinner_ = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge] autorelease];
+    CGRect spinrect = [spinner_ frame];
+    spinrect.origin.x = ([[self view] frame].size.width / 2) - (spinrect.size.width / 2);
+    spinrect.origin.y = [[self view] frame].size.height - 80.0f;
+    [spinner_ setFrame:spinrect];
+    [spinner_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin];
+    [[self view] addSubview:spinner_];
+    [spinner_ startAnimating];
+
+    CGRect captrect;
+    captrect.size.width = [[self view] frame].size.width;
+    captrect.size.height = 40.0f;
+    captrect.origin.x = 0;
+    captrect.origin.y = ([[self view] frame].size.height / 2) - (captrect.size.height * 2);
+    caption_ = [[[UILabel alloc] initWithFrame:captrect] autorelease];
+    [caption_ setText:UCLocalize("PREPARING_FILESYSTEM")];
+    [caption_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
+    [caption_ setFont:[UIFont boldSystemFontOfSize:28.0f]];
+    [caption_ setTextColor:[UIColor whiteColor]];
+    [caption_ setBackgroundColor:[UIColor clearColor]];
+    [caption_ setShadowColor:[UIColor blackColor]];
+    [caption_ setTextAlignment:UITextAlignmentCenter];
+    [[self view] addSubview:caption_];
+
+    CGRect statusrect;
+    statusrect.size.width = [[self view] frame].size.width;
+    statusrect.size.height = 30.0f;
+    statusrect.origin.x = 0;
+    statusrect.origin.y = ([[self view] frame].size.height / 2) - statusrect.size.height;
+    status_ = [[[UILabel alloc] initWithFrame:statusrect] autorelease];
+    [status_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
+    [status_ setText:UCLocalize("EXIT_WHEN_COMPLETE")];
+    [status_ setFont:[UIFont systemFontOfSize:16.0f]];
+    [status_ setTextColor:[UIColor whiteColor]];
+    [status_ setBackgroundColor:[UIColor clearColor]];
+    [status_ setShadowColor:[UIColor blackColor]];
+    [status_ setTextAlignment:UITextAlignmentCenter];
+    [[self view] addSubview:status_];
+}
+
+- (void) releaseSubviews {
+    [spinner_ release];
+    spinner_ = nil;
+
+    [status_ release];
+    status_ = nil;
+
+    [caption_ release];
+    caption_ = nil;
 }
 
 @end
@@ -8238,7 +8730,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             message:UCLocalize("HALFINSTALLED_PACKAGE_EX")
             delegate:self
             cancelButtonTitle:UCLocalize("FORCIBLY_CLEAR")
-            otherButtonTitles:UCLocalize("TEMPORARY_IGNORE"), nil
+            otherButtonTitles:
+                UCLocalize("TEMPORARY_IGNORE"),
+            nil
         ] autorelease];
 
         [alert setContext:@"fixhalf"];
@@ -8251,7 +8745,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             message:UCLocalize("ESSENTIAL_UPGRADE_EX")
             delegate:self
             cancelButtonTitle:UCLocalize("TEMPORARY_IGNORE")
-            otherButtonTitles:UCLocalize("UPGRADE_ESSENTIAL"), UCLocalize("COMPLETE_UPGRADE"), nil
+            otherButtonTitles:
+                UCLocalize("UPGRADE_ESSENTIAL"),
+                UCLocalize("COMPLETE_UPGRADE"),
+            nil
         ] autorelease];
 
         [alert setContext:@"upgrade"];
@@ -8320,8 +8817,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     if (recently || loaded_ || ManualRefresh) {
         [self performSelectorOnMainThread:@selector(_loaded) withObject:nil waitUntilDone:NO];
 
-        // If we are cancelling due to ManualRefresh or a recent refresh
-        // we need to make sure it knows it's already loaded.
+        // If we are cancelling, we need to make sure it knows it's already loaded.
         loaded_ = true;
         return;
     } else {
@@ -8358,11 +8854,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [NSThread detachNewThreadSelector:@selector(_refreshIfPossible) toTarget:self withObject:nil];
 }
 
-- (void) _reloadData {
+- (void) _reloadDataWithInvocation:(NSInvocation *)invocation {
     UIProgressHUD *hud(loaded_ ? [self addProgressHUD] : nil);
     [hud setText:UCLocalize("RELOADING_DATA")];
 
-    [database_ yieldToSelector:@selector(reloadData) withObject:nil];
+    [database_ yieldToSelector:@selector(reloadDataWithInvocation:) withObject:invocation];
 
     if (hud != nil)
         [self removeProgressHUD:hud];
@@ -8444,12 +8940,16 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     ];
 }
 
-- (void) reloadData {
+- (void) reloadDataWithInvocation:(NSInvocation *)invocation {
     @synchronized (self) {
-        [self _reloadData];
+        [self _reloadDataWithInvocation:invocation];
     }
 }
 
+- (void) reloadData {
+    [self reloadDataWithInvocation:nil];
+}
+
 - (void) resolve {
     pkgProblemResolver *resolver = [database_ resolver];
 
@@ -8459,6 +8959,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (bool) perform {
+    // XXX: this is a really crappy way of doing this.
+    // like, seriously: this state machine is still broken, and cancelling this here doesn't really /fix/ that.
+    // for one, the user can still /start/ a reloading data event while they have a queue, which is stupid
+    // for two, this just means there is a race condition between the refresh completing and the confirmation controller appearing.
+    if ([tabbar_ updating])
+        [tabbar_ cancelUpdate];
+
     if (![database_ prepare])
         return false;
 
@@ -8523,7 +9030,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 - (void) complete {
     @synchronized (self) {
-        [self _reloadData];
+        [self _reloadDataWithInvocation:nil];
     }
 }
 
@@ -8556,41 +9063,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [self complete];
 }
 
-- (void) tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
-    CYNavigationController *controller = (CYNavigationController *) viewController;
-
-    if ([[controller viewControllers] count] == 0) {
-        int index = [[tabbar_ viewControllers] indexOfObjectIdenticalTo:controller];
-        CYViewController *root = nil;
-
-        if (index == 0)
-            root = [[[HomeController alloc] init] autorelease];
-        else if (index == 1)
-            root = [[[SectionsController alloc] initWithDatabase:database_] autorelease];
-        else if (index == 2)
-            root = [[[ChangesController alloc] initWithDatabase:database_] autorelease];
-
-        if (IsWildcat_) {
-            if (index == 3)
-                root = [[[InstalledController alloc] initWithDatabase:database_] autorelease];
-            else if (index == 4)
-                root = [[[SourcesController alloc] initWithDatabase:database_] autorelease];
-            else if (index == 5)
-                root = [[[SearchController alloc] initWithDatabase:database_] autorelease];
-        } else {
-            if (index == 3)
-                root = [[[ManageController alloc] init] autorelease];
-            else if (index == 4)
-                root = [[[SearchController alloc] initWithDatabase:database_] autorelease];
-        }
-
-        [root setDelegate:self];
-
-        if (root != nil)
-            [controller setViewControllers:[NSArray arrayWithObject:root]];
-    }
-}
-
 - (void) showSettings {
     SettingsController *role = [[[SettingsController alloc] initWithDatabase:database_ delegate:self] autorelease];
     CYNavigationController *nav = [[[CYNavigationController alloc] initWithRootViewController:role] autorelease];
@@ -8626,7 +9098,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     NSString *context([alert context]);
 
     if ([context isEqualToString:@"fixhalf"]) {
-        if (button == [alert firstOtherButtonIndex]) {
+        if (button == [alert cancelButtonIndex]) {
             @synchronized (self) {
                 for (Package *broken in broken_) {
                     [broken remove];
@@ -8641,7 +9113,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                 [self resolve];
                 [self perform];
             }
-        } else if (button == [alert cancelButtonIndex]) {
+        } else if (button == [alert firstOtherButtonIndex]) {
             [broken_ removeAllObjects];
             [self _loaded];
         }
@@ -8729,20 +9201,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (CYViewController *) pageForPackage:(NSString *)name {
-    if (Package *package = [database_ packageWithName:name]) {
-        CYPackageController *view = [[[CYPackageController alloc] initWithDatabase:database_] autorelease];
-        [view setPackage:package];
-        return view;
-    } else {
-        NSURL *url([NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"unknown" ofType:@"html"]]);
-        url = [NSURL URLWithString:[[url absoluteString] stringByAppendingString:[NSString stringWithFormat:@"?%@", name]]];
-        CYBrowserController *browser = [[[CYBrowserController alloc] init] autorelease];
-        [browser loadURL:url];
-        return browser;
-    }
+    return [[[CYPackageController alloc] initWithDatabase:database_ forPackage:name] autorelease];
 }
 
-- (CYViewController *) pageForURL:(NSURL *)url {
+- (CYViewController *) pageForURL:(NSURL *)url forExternal:(BOOL)external {
     NSString *scheme([[url scheme] lowercaseString]);
     if ([[url absoluteString] length] <= [scheme length] + 3)
         return nil;
@@ -8762,9 +9224,12 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     if ([base isEqualToString:@"url"]) {
         // This kind of URL can contain slashes in the argument, so we can't parse them below.
         NSString *destination = [[url absoluteString] substringFromIndex:([scheme length] + [@"://" length] + [base length] + [@"/" length])];
-        controller = [[[CYBrowserController alloc] init] autorelease];
-        [(CYBrowserController *)controller loadURL:[NSURL URLWithString:destination]];
-    } else if ([components count] == 1) {
+        controller = [[[CYBrowserController alloc] initWithURL:[NSURL URLWithString:destination]] autorelease];
+    } else if (!external && [components count] == 1) {
+        if ([base isEqualToString:@"manage"]) {
+            controller = [[[ManageController alloc] init] autorelease];
+        }
+
         if ([base isEqualToString:@"sources"]) {
             controller = [[[SourcesController alloc] initWithDatabase:database_] autorelease];
         }
@@ -8795,45 +9260,38 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             controller = [self pageForPackage:argument];
         }
 
-        if ([base isEqualToString:@"search"]) {
+        if (!external && [base isEqualToString:@"search"]) {
             controller = [[[SearchController alloc] initWithDatabase:database_] autorelease];
             [(SearchController *)controller setSearchTerm:argument];
         }
 
-        if ([base isEqualToString:@"sections"]) {
+        if (!external && [base isEqualToString:@"sections"]) {
             if ([argument isEqualToString:@"all"])
                 argument = nil;
             controller = [[[SectionController alloc] initWithDatabase:database_ section:argument] autorelease];
         }
 
-        if ([base isEqualToString:@"sources"]) {
+        if (!external && [base isEqualToString:@"sources"]) {
             if ([argument isEqualToString:@"add"]) {
                 controller = [[[SourcesController alloc] initWithDatabase:database_] autorelease];
                 [(SourcesController *)controller showAddSourcePrompt];
             } else {
-                NSArray *sources = [database_ sources];
-                for (Source *source in sources) {
-                    if ([[source name] caseInsensitiveCompare:argument] == NSOrderedSame) {
-                        controller = [[[SourceController alloc] initWithDatabase:database_ source:source] autorelease];
-                        break;
-                    }
-                }
+                Source *source = [database_ sourceWithKey:argument];
+                controller = [[[SourceController alloc] initWithDatabase:database_ source:source] autorelease];
             }
         }
 
-        if ([base isEqualToString:@"launch"]) {
+        if (!external && [base isEqualToString:@"launch"]) {
             [self launchApplicationWithIdentifier:argument suspended:NO];
             return nil;
         }
-    } else if ([components count] == 3) {
+    } else if (!external && [components count] == 3) {
         NSString *arg1 = [components objectAtIndex:1];
         NSString *arg2 = [components objectAtIndex:2];
 
         if ([base isEqualToString:@"package"]) {
             if ([arg2 isEqualToString:@"settings"]) {
                 controller = [[[PackageSettingsController alloc] initWithDatabase:database_ package:arg1] autorelease];
-            } else if ([arg2 isEqualToString:@"signature"]) {
-                controller = [[[SignatureController alloc] initWithDatabase:database_ package:arg1] autorelease];
             } else if ([arg2 isEqualToString:@"files"]) {
                 if (Package *package = [database_ packageWithName:arg1]) {
                     controller = [[[FileTable alloc] initWithDatabase:database_] autorelease];
@@ -8847,13 +9305,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return controller;
 }
 
-- (BOOL) openCydiaURL:(NSURL *)url {
-    CYViewController *page([self pageForURL:url]);
+- (BOOL) openCydiaURL:(NSURL *)url forExternal:(BOOL)external {
+    CYViewController *page([self pageForURL:url forExternal:external]);
 
     if (page != nil) {
         CYNavigationController *nav = [[[CYNavigationController alloc] init] autorelease];
         [nav setViewControllers:[NSArray arrayWithObject:page]];
-        [tabbar_ setTransientViewController:nav];
+        [tabbar_ setUnselectedViewController:nav];
     }
 
     return page != nil;
@@ -8863,7 +9321,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [super applicationOpenURL:url];
 
     if (!loaded_) starturl_ = [url retain];
-    else [self openCydiaURL:url];
+    else [self openCydiaURL:url forExternal:YES];
 }
 
 - (void) applicationWillResignActive:(UIApplication *)application {
@@ -8875,6 +9333,15 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         [super applicationWillResignActive:application];
 }
 
+- (void) applicationWillTerminate:(UIApplication *)application {
+    Changed_ = true;
+    [Metadata_ setObject:[tabbar_ navigationURLCollection] forKey:@"InterfaceState"];
+    [Metadata_ setObject:[NSDate date] forKey:@"LastClosed"];
+    [Metadata_ setObject:[NSNumber numberWithInt:[tabbar_ selectedIndex]] forKey:@"InterfaceIndex"];
+
+    [self _saveConfig];
+}
+
 - (void) addStashController {
     ++locked_;
     stash_ = [[StashController alloc] init];
@@ -8909,7 +9376,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 - (void) setupViewControllers {
     tabbar_ = [[CYTabBarController alloc] initWithDatabase:database_];
-    [tabbar_ setDelegate:self];
 
     NSMutableArray *items([NSMutableArray arrayWithObjects:
         [[[UITabBarItem alloc] initWithTitle:@"Cydia" image:[UIImage applicationImageNamed:@"home.png"] tag:0] autorelease],
@@ -8936,12 +9402,17 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [tabbar_ setUpdateDelegate:self];
 }
 
-- (CYEmulatedLoadingController *)showEmulatedLoadingControllerInView:(UIView *)view {
-    static CYEmulatedLoadingController *fake = [[CYEmulatedLoadingController alloc] init];
+- (CYEmulatedLoadingController *) showEmulatedLoadingControllerInView:(UIView *)view {
+    static CYEmulatedLoadingController *fake = nil;
+
     if (view != nil) {
+        if (fake == nil)
+            fake = [[CYEmulatedLoadingController alloc] initWithDatabase:database_];
         [view addSubview:[fake view]];
     } else {
         [[fake view] removeFromSuperview];
+        [fake release];
+        fake = nil;
     }
 
     return fake;
@@ -9033,13 +9504,61 @@ _trace();
     [self showEmulatedLoadingControllerInView:nil];
     [window_ setUserInteractionEnabled:YES];
 
-    // Show the home page.
-    CYNavigationController *navigation = [[tabbar_ viewControllers] objectAtIndex:0];
-    [navigation setViewControllers:[NSArray arrayWithObject:[self pageForURL:[NSURL URLWithString:@"cydia://home"]]]];
+    int selectedIndex = 0;
+    NSMutableArray *items = nil;
+
+    bool recently = false;
+    NSDate *closed([Metadata_ objectForKey:@"LastClosed"]);
+    if (closed != nil) {
+        NSTimeInterval interval([closed timeIntervalSinceNow]);
+        // XXX: Is 15 minutes the optimal time here?
+        if (interval <= 0 && interval > -(15*60))
+            recently = true;
+    }
+
+    items = [[Metadata_ objectForKey:@"InterfaceState"] mutableCopy];
+    selectedIndex = [[Metadata_ objectForKey:@"InterfaceIndex"] intValue];
+
+    BOOL enough = YES;
+    for (NSArray *entry in items)
+        if ([entry count] <= 0)
+            enough = NO;
+
+    if (!recently || !items || !enough) {
+        selectedIndex = 0;
+        items = [NSMutableArray array];
+        [items addObject:[NSArray arrayWithObject:@"cydia://home"]];
+        [items addObject:[NSArray arrayWithObject:@"cydia://sections"]];
+        [items addObject:[NSArray arrayWithObject:@"cydia://changes"]];
+        if (!IsWildcat_) {
+            [items addObject:[NSArray arrayWithObject:@"cydia://manage"]];
+        } else {
+            [items addObject:[NSArray arrayWithObject:@"cydia://installed"]];
+            [items addObject:[NSArray arrayWithObject:@"cydia://sources"]];
+        }
+        [items addObject:[NSArray arrayWithObject:@"cydia://search"]];
+    }
+
+    [tabbar_ setSelectedIndex:selectedIndex];
+    for (unsigned int tab = 0; tab < [[tabbar_ viewControllers] count]; tab++) {
+        NSArray *stack = [items objectAtIndex:tab];
+        CYNavigationController *navigation = [[tabbar_ viewControllers] objectAtIndex:tab];
+        NSMutableArray *current = [NSMutableArray array];
+
+        for (unsigned int nav = 0; nav < [stack count]; nav++) {
+            NSString *addr = [stack objectAtIndex:nav];
+            NSURL *url = [NSURL URLWithString:addr];
+            CYViewController *page = [self pageForURL:url forExternal:NO];
+            if (page != nil)
+                [current addObject:page];
+        }
+
+        [navigation setViewControllers:current];
+    }
 
     // (Try to) show the startup URL.
     if (starturl_ != nil) {
-        [self openCydiaURL:starturl_];
+        [self openCydiaURL:starturl_ forExternal:NO];
         [starturl_ release];
         starturl_ = nil;
     }
@@ -9134,6 +9653,18 @@ int main(int argc, char *argv[]) { _pooled
     } else
         IsWildcat_ = false;
 
+    UIScreen *screen([UIScreen mainScreen]);
+    if ([screen respondsToSelector:@selector(scale)])
+        ScreenScale_ = [screen scale];
+    else
+        ScreenScale_ = 1;
+
+    NSMutableArray *parts([NSMutableArray arrayWithCapacity:2]);
+    if (ScreenScale_ > 1)
+        [parts addObject:@"@2x"];
+    [parts addObject:(IsWildcat_ ? @"~ipad" : @"~iphone")];
+    UI_ = CydiaURL([NSString stringWithFormat:@"ui/ios%@", [parts componentsJoinedByString:@""]]);
+
     PackageName = reinterpret_cast<CYString &(*)(Package *, SEL)>(method_getImplementation(class_getInstanceMethod([Package class], @selector(cyname))));
 
     /* Library Hacks {{{ */
@@ -9332,19 +9863,28 @@ int main(int argc, char *argv[]) { _pooled
     _trace();
 
     if (Packages_ != nil) {
-        CFDictionaryApplyFunction((CFDictionaryRef) Packages_, &PackageImport, NULL);
+        bool fail(false);
+        CFDictionaryApplyFunction((CFDictionaryRef) Packages_, &PackageImport, &fail);
         _trace();
-        [Metadata_ removeObjectForKey:@"Packages"];
-        Packages_ = nil;
-        Changed_ = true;
+
+        if (!fail) {
+            [Metadata_ removeObjectForKey:@"Packages"];
+            Packages_ = nil;
+            Changed_ = true;
+        }
     }
 
     Finishes_ = [NSArray arrayWithObjects:@"return", @"reopen", @"restart", @"reload", @"reboot", nil];
 
-    if (substrate && access("/Library/MobileSubstrate/DynamicLibraries/SimulatedKeyEvents.dylib", F_OK) == 0)
-        dlopen("/Library/MobileSubstrate/DynamicLibraries/SimulatedKeyEvents.dylib", RTLD_LAZY | RTLD_GLOBAL);
-    if (substrate && access("/Applications/WinterBoard.app/WinterBoard.dylib", F_OK) == 0)
-        dlopen("/Applications/WinterBoard.app/WinterBoard.dylib", RTLD_LAZY | RTLD_GLOBAL);
+#define MobileSubstrate_(name) \
+    if (substrate && access("/Library/MobileSubstrate/DynamicLibraries/" #name ".dylib", F_OK) == 0) \
+        dlopen("/Library/MobileSubstrate/DynamicLibraries/" #name ".dylib", RTLD_LAZY | RTLD_GLOBAL);
+
+    MobileSubstrate_(Activator)
+    MobileSubstrate_(libstatusbar)
+    MobileSubstrate_(SimulatedKeyEvents)
+    MobileSubstrate_(WinterBoard)
+
     /*if (substrate && access("/Library/MobileSubstrate/MobileSubstrate.dylib", F_OK) == 0)
         dlopen("/Library/MobileSubstrate/MobileSubstrate.dylib", RTLD_LAZY | RTLD_GLOBAL);*/