]> git.saurik.com Git - cydia.git/blobdiff - MobileCydia.mm
Port root->mobile preference shift to the future.
[cydia.git] / MobileCydia.mm
index fc48972db2fb69b268631b5f2b989010d1464160..187370296d11e273bc319e105bba4f9a9351016c 100644 (file)
@@ -26,6 +26,9 @@
 #include "CyteKit/UCPlatform.h"
 #include "CyteKit/Localize.h"
 
+#include <unicode/ustring.h>
+#include <unicode/utrans.h>
+
 #include <objc/objc.h>
 #include <objc/runtime.h>
 
@@ -159,7 +162,7 @@ class ProfileTime {
 
     void Print() {
         if (total_ != 0)
-            std::cerr << std::setw(5) << count_ << ", " << std::setw(7) << total_ << " : " << name_ << std::endl;
+            std::cerr << std::setw(7) << count_ << ", " << std::setw(8) << total_ << " : " << name_ << std::endl;
         total_ = 0;
         count_ = 0;
     }
@@ -205,7 +208,7 @@ extern NSString *Cydia_;
 
 #define ForRelease 1
 #define TraceLogging (1 && !ForRelease)
-#define HistogramInsertionSort (!ForRelease ? 0 : 0)
+#define HistogramInsertionSort (0 && !ForRelease)
 #define ProfileTimes (0 && !ForRelease)
 #define ForSaurik (0 && !ForRelease)
 #define LogBrowser (0 && !ForRelease)
@@ -213,7 +216,6 @@ extern NSString *Cydia_;
 #define ManualRefresh (1 && !ForRelease)
 #define ShowInternals (0 && !ForRelease)
 #define AlwaysReload (0 && !ForRelease)
-#define TryIndexedCollation (0 && !ForRelease)
 
 #if !TraceLogging
 #undef _trace
@@ -314,7 +316,7 @@ static CGFloat CYStatusBarHeight() {
 /* NSForcedOrderingSearch doesn't work on the iPhone */
 static const NSStringCompareOptions MatchCompareOptions_ = NSLiteralSearch | NSCaseInsensitiveSearch;
 static const NSStringCompareOptions LaxCompareOptions_ = NSNumericSearch | NSDiacriticInsensitiveSearch | NSWidthInsensitiveSearch | NSCaseInsensitiveSearch;
-static const CFStringCompareFlags LaxCompareFlags_ = kCFCompareCaseInsensitive | kCFCompareNonliteral | kCFCompareLocalized | kCFCompareNumerically | kCFCompareWidthInsensitive | kCFCompareForcedOrdering;
+static const CFStringCompareFlags LaxCompareFlags_ = kCFCompareNumerically | kCFCompareWidthInsensitive | kCFCompareForcedOrdering;
 
 /* Insertion Sort {{{ */
 
@@ -720,6 +722,56 @@ static _H<NSString> UserAgent_;
 static _H<NSString> Product_;
 static _H<NSString> Safari_;
 
+static _H<NSLocale> CollationLocale_;
+static _H<NSArray> CollationThumbs_;
+static std::vector<NSInteger> CollationOffset_;
+static _H<NSArray> CollationTitles_;
+static _H<NSArray> CollationStarts_;
+static UTransliterator *CollationTransl_;
+//static Function<NSString *, NSString *> CollationModify_;
+
+typedef std::basic_string<UChar> ustring;
+static ustring CollationString_;
+
+#define CUC const ustring &str(*reinterpret_cast<const ustring *>(rep))
+#define UC ustring &str(*reinterpret_cast<ustring *>(rep))
+static struct UReplaceableCallbacks CollationUCalls_ = {
+    .length = [](const UReplaceable *rep) -> int32_t { CUC;
+        return str.size();
+    },
+
+    .charAt = [](const UReplaceable *rep, int32_t offset) -> UChar { CUC;
+        //fprintf(stderr, "charAt(%d) : %d\n", offset, str.size());
+        if (offset >= str.size())
+            return 0xffff;
+        return str[offset];
+    },
+
+    .char32At = [](const UReplaceable *rep, int32_t offset) -> UChar32 { CUC;
+        //fprintf(stderr, "char32At(%d) : %d\n", offset, str.size());
+        if (offset >= str.size())
+            return 0xffff;
+        UChar32 c;
+        U16_GET(str.data(), 0, offset, str.size(), c);
+        return c;
+    },
+
+    .replace = [](UReplaceable *rep, int32_t start, int32_t limit, const UChar *text, int32_t length) -> void { UC;
+        //fprintf(stderr, "replace(%d, %d, %d) : %d\n", start, limit, length, str.size());
+        str.replace(start, limit - start, text, length);
+    },
+
+    .extract = [](UReplaceable *rep, int32_t start, int32_t limit, UChar *dst) -> void { UC;
+        //fprintf(stderr, "extract(%d, %d) : %d\n", start, limit, str.size());
+        str.copy(dst, limit - start, start);
+    },
+
+    .copy = [](UReplaceable *rep, int32_t start, int32_t limit, int32_t dest) -> void { UC;
+        //fprintf(stderr, "copy(%d, %d, %d) : %d\n", start, limit, dest, str.size());
+        str.replace(dest, 0, str, start, limit - start);
+    },
+};
+
 static CFLocaleRef Locale_;
 static NSArray *Languages_;
 static CGColorSpaceRef space_;
@@ -782,7 +834,7 @@ NSString *LocalizeSection(NSString *section) {
 
 NSString *Simplify(NSString *title) {
     const char *data = [title UTF8String];
-    size_t size = [title length];
+    size_t size = [title lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
 
     static Pcre square_r("^\\[(.*)\\]$");
     if (square_r(data, size))
@@ -1503,7 +1555,10 @@ static void PackageImport(const void *key, const void *value, void *context) {
         base_.set(pool, file);
 
         pkgAcquire acquire;
+        _profile(Source$setMetaIndex$GetIndexes)
         dindex->GetIndexes(&acquire, true);
+        _end
+        _profile(Source$setMetaIndex$DescURI)
         for (pkgAcquire::ItemIterator item(acquire.ItemsBegin()); item != acquire.ItemsEnd(); item++) {
             std::string file((*item)->DescURI());
             files_.insert(file);
@@ -1514,6 +1569,7 @@ static void PackageImport(const void *key, const void *value, void *context) {
             files_.insert(file + ".gz");
             files_.insert(file + "Index");
         }
+        _end
 
         FileFd fd;
         if (!fd.Open(dindex->MetaIndexFile("Release"), FileFd::ReadOnly))
@@ -1568,7 +1624,9 @@ static void PackageImport(const void *key, const void *value, void *context) {
         database_ = database;
         index_ = index;
 
+        _profile(Source$initWithMetaIndex$setMetaIndex)
         [self setMetaIndex:index inPool:pool];
+        _end
     } return self;
 }
 
@@ -1967,6 +2025,7 @@ struct ParsedPackage {
 
     CYString id_;
     CYString name_;
+    CYString transform_;
 
     CYString latest_;
     CYString installed_;
@@ -2089,9 +2148,11 @@ uint32_t PackageChangesRadix(Package *self, void *) {
     return _not(uint32_t) - value.key;
 }
 
+CYString &(*PackageName)(Package *self, SEL sel);
+
 uint32_t PackagePrefixRadix(Package *self, void *context) {
     size_t offset(reinterpret_cast<size_t>(context));
-    CYString &name([self cyname]);
+    CYString &name(PackageName(self, @selector(cyname)));
 
     size_t size(name.size());
     if (size == 0)
@@ -2144,21 +2205,17 @@ uint32_t PackagePrefixRadix(Package *self, void *context) {
     return OSSwapInt32(*reinterpret_cast<uint32_t *>(data));
 }
 
-CYString &(*PackageName)(Package *self, SEL sel);
-
-CFComparisonResult PackageNameCompare(Package *lhs, Package *rhs, void *arg) {
+CFComparisonResult StringNameCompare(CFStringRef lhn, CFStringRef rhn, size_t length) {
     _profile(PackageNameCompare)
-        CYString &lhi(PackageName(lhs, @selector(cyname)));
-        CYString &rhi(PackageName(rhs, @selector(cyname)));
-        CFStringRef lhn(lhi), rhn(rhi);
-
         if (lhn == NULL)
             return rhn == NULL ? kCFCompareEqualTo : kCFCompareLessThan;
         else if (rhn == NULL)
             return kCFCompareGreaterThan;
 
+        CFIndex length(CFStringGetLength(lhn));
+
         _profile(PackageNameCompare$NumbersLast)
-            if (!lhi.empty() && !rhi.empty()) {
+            if (length != 0 && CFStringGetLength(rhn) != 0) {
                 UniChar lhc(CFStringGetCharacterAtIndex(lhn, 0));
                 UniChar rhc(CFStringGetCharacterAtIndex(rhn, 0));
                 bool lha(CFUniCharIsMemberOf(lhc, kCFUniCharLetterCharacterSet));
@@ -2167,16 +2224,24 @@ CFComparisonResult PackageNameCompare(Package *lhs, Package *rhs, void *arg) {
             }
         _end
 
-        CFIndex length = CFStringGetLength(lhn);
-
         _profile(PackageNameCompare$Compare)
-            return CFStringCompareWithOptionsAndLocale(lhn, rhn, CFRangeMake(0, length), LaxCompareFlags_, Locale_);
+            return CFStringCompareWithOptionsAndLocale(lhn, rhn, CFRangeMake(0, length), LaxCompareFlags_, (CFLocaleRef) (id) CollationLocale_);
         _end
     _end
 }
 
-CFComparisonResult PackageNameCompare_(Package **lhs, Package **rhs, void *context) {
-    return PackageNameCompare(*lhs, *rhs, context);
+_finline CFComparisonResult StringNameCompare(NSString *lhn, NSString*rhn, size_t length) {
+    return StringNameCompare((CFStringRef) lhn, (CFStringRef) rhn, length);
+}
+
+CFComparisonResult PackageNameCompare(Package *lhs, Package *rhs, void *arg) {
+    CYString &lhn(PackageName(lhs, @selector(cyname)));
+    NSString *rhn(PackageName(rhs, @selector(cyname)));
+    return StringNameCompare(lhn, rhn, lhn.size());
+}
+
+CFComparisonResult PackageNameCompare_(Package **lhs, Package **rhs, void *arg) {
+    return PackageNameCompare(*lhs, *rhs, arg);
 }
 
 struct PackageNameOrdering :
@@ -2414,6 +2479,60 @@ struct PackageNameOrdering :
                 installed_.set(NULL, StripVersion_(current.VerStr()));
         _end
 
+        _profile(Package$initWithVersion$Transliterate) do {
+            if (CollationTransl_ == NULL)
+                break;
+            if (name_.empty())
+                break;
+
+            _profile(Package$initWithVersion$Transliterate$utf8)
+            const uint8_t *data(reinterpret_cast<const uint8_t *>(name_.data()));
+            for (size_t i(0), e(name_.size()); i != e; ++i)
+                if (data[i] >= 0x80)
+                    goto extended;
+            break; extended:;
+            _end
+
+            UErrorCode code(U_ZERO_ERROR);
+            int32_t length;
+
+            _profile(Package$initWithVersion$Transliterate$u_strFromUTF8WithSub)
+            CollationString_.resize(name_.size());
+            u_strFromUTF8WithSub(&CollationString_[0], CollationString_.size(), &length, name_.data(), name_.size(), 0xfffd, NULL, &code);
+            if (!U_SUCCESS(code))
+                break;
+            CollationString_.resize(length);
+            _end
+
+            _profile(Package$initWithVersion$Transliterate$utrans_trans)
+            length = CollationString_.size();
+            utrans_trans(CollationTransl_, reinterpret_cast<UReplaceable *>(&CollationString_), &CollationUCalls_, 0, &length, &code);
+            if (!U_SUCCESS(code))
+                break;
+            _assert(CollationString_.size() == length);
+            _end
+
+            _profile(Package$initWithVersion$Transliterate$u_strToUTF8WithSub$preflight)
+            u_strToUTF8WithSub(NULL, 0, &length, CollationString_.data(), CollationString_.size(), 0xfffd, NULL, &code);
+            if (code == U_BUFFER_OVERFLOW_ERROR)
+                code = U_ZERO_ERROR;
+            else if (!U_SUCCESS(code))
+                break;
+            _end
+
+            char *transform;
+            _profile(Package$initWithVersion$Transliterate$apr_palloc)
+            transform = static_cast<char *>(apr_palloc(pool_, length));
+            _end
+            _profile(Package$initWithVersion$Transliterate$u_strToUTF8WithSub$transform)
+            u_strToUTF8WithSub(transform, length, NULL, CollationString_.data(), CollationString_.size(), 0xfffd, NULL, &code);
+            if (!U_SUCCESS(code))
+                break;
+            _end
+
+            transform_.set(NULL, transform, length);
+        } while (false); _end
+
         _profile(Package$initWithVersion$Tags)
             pkgCache::TagIterator tag(iterator.TagList());
             if (!tag.end()) {
@@ -3013,6 +3132,10 @@ struct PackageNameOrdering :
     return source_ == (Source *) [NSNull null] ? nil : source_;
 }
 
+- (uint32_t) updated {
+    return std::numeric_limits<uint32_t>::max() - updated_;
+}
+
 - (uint32_t) rank {
     return rank_;
 }
@@ -3093,7 +3216,7 @@ struct PackageNameOrdering :
 }
 
 - (CYString &) cyname {
-    return name_.empty() ? id_ : name_;
+    return !transform_.empty() ? transform_ : !name_.empty() ? name_ : id_;
 }
 
 - (uint32_t) compareBySection:(NSArray *)sections {
@@ -3148,7 +3271,6 @@ struct PackageNameOrdering :
 /* Section Class {{{ */
 @interface Section : NSObject {
     _H<NSString> name_;
-    unichar index_;
     size_t row_;
     size_t count_;
     _H<NSString> localized_;
@@ -3158,9 +3280,9 @@ struct PackageNameOrdering :
 - (Section *) initWithName:(NSString *)name localized:(NSString *)localized;
 - (Section *) initWithName:(NSString *)name localize:(BOOL)localize;
 - (Section *) initWithName:(NSString *)name row:(size_t)row localize:(BOOL)localize;
-- (Section *) initWithIndex:(unichar)index row:(size_t)row;
+
 - (NSString *) name;
-- (unichar) index;
+- (void) setName:(NSString *)name;
 
 - (size_t) row;
 - (size_t) count;
@@ -3206,28 +3328,18 @@ struct PackageNameOrdering :
 - (Section *) initWithName:(NSString *)name row:(size_t)row localize:(BOOL)localize {
     if ((self = [super init]) != nil) {
         name_ = name;
-        index_ = '\0';
         row_ = row;
         if (localize)
             localized_ = LocalizeSection(name_);
     } return self;
 }
 
-/* XXX: localize the index thingees */
-- (Section *) initWithIndex:(unichar)index row:(size_t)row {
-    if ((self = [super init]) != nil) {
-        name_ = [NSString stringWithCharacters:&index length:1];
-        index_ = index;
-        row_ = row;
-    } return self;
-}
-
 - (NSString *) name {
     return name_;
 }
 
-- (unichar) index {
-    return index_;
+- (void) setName:(NSString *)name {
+    name_ = name;
 }
 
 - (size_t) row {
@@ -3585,18 +3697,26 @@ class CydiaLogCleaner :
     NSString *title(UCLocalize("DATABASE"));
 
     list_ = new pkgSourceList();
+    _profile(reloadDataWithInvocation$ReadMainList)
     if ([self popErrorWithTitle:title forOperation:list_->ReadMainList()])
         return;
+    _end
 
+    _profile(reloadDataWithInvocation$Source$initWithMetaIndex)
     for (pkgSourceList::const_iterator source = list_->begin(); source != list_->end(); ++source) {
         Source *object([[[Source alloc] initWithMetaIndex:*source forDatabase:self inPool:pool_] autorelease]);
         [sourceList_ addObject:object];
     }
+    _end
 
     _trace();
     OpProgress progress;
+    bool opened;
   open:
-    if (!cache_.Open(progress, true)) {
+    _profile(reloadDataWithInvocation$pkgCacheFile)
+        opened = cache_.Open(progress, true);
+    _end
+    if (!opened) {
         // XXX: what if there are errors, but Open() == true? this should be merged with popError:
         while (!_error->empty()) {
             std::string error;
@@ -3643,20 +3763,26 @@ class CydiaLogCleaner :
         return;
     }
 
+    _profile(reloadDataWithInvocation$pkgApplyStatus)
     if ([self popErrorWithTitle:title forOperation:pkgApplyStatus(cache_)])
         return;
+    _end
 
     if (cache_->BrokenCount() != 0) {
+        _profile(pkgApplyStatus$pkgFixBroken)
         if ([self popErrorWithTitle:title forOperation:pkgFixBroken(cache_)])
             return;
+        _end
 
         if (cache_->BrokenCount() != 0) {
             [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:UCLocalize("STILL_BROKEN_EX") ofType:kCydiaProgressEventTypeError] forTask:title];
             return;
         }
 
+        _profile(pkgApplyStatus$pkgMinimizeUpgrade)
         if ([self popErrorWithTitle:title forOperation:pkgMinimizeUpgrade(cache_)])
             return;
+        _end
     }
 
     for (Source *object in (id) sourceList_) {
@@ -3676,14 +3802,13 @@ class CydiaLogCleaner :
         packages.reserve(std::max(10000U, [packages_ count] + 1000));
         packages_ = nil;*/
 
-        _trace();
-
+        _profile(reloadDataWithInvocation$packageWithIterator)
         for (pkgCache::PkgIterator iterator = cache_->PkgBegin(); !iterator.end(); ++iterator)
             if (Package *package = [Package packageWithIterator:iterator withZone:zone_ inPool:pool_ database:self])
                 //packages.push_back(package);
                 CFArrayAppendValue(packages_, CFRetain(package));
+        _end
 
-        _trace();
 
         /*if (packages.empty())
             packages_ = [[NSArray alloc] init];
@@ -3691,35 +3816,43 @@ class CydiaLogCleaner :
             packages_ = [[NSArray alloc] initWithObjects:&packages.front() count:packages.size()];
         _trace();*/
 
-        [(NSMutableArray *) packages_ radixSortUsingFunction:reinterpret_cast<MenesRadixSortFunction>(&PackagePrefixRadix) withContext:reinterpret_cast<void *>(16)];
+        _profile(reloadDataWithInvocation$radix$8)
+        [(NSMutableArray *) packages_ radixSortUsingFunction:reinterpret_cast<MenesRadixSortFunction>(&PackagePrefixRadix) withContext:reinterpret_cast<void *>(8)];
+        _end
+
+        _profile(reloadDataWithInvocation$radix$4)
         [(NSMutableArray *) packages_ radixSortUsingFunction:reinterpret_cast<MenesRadixSortFunction>(&PackagePrefixRadix) withContext:reinterpret_cast<void *>(4)];
-        [(NSMutableArray *) packages_ radixSortUsingFunction:reinterpret_cast<MenesRadixSortFunction>(&PackagePrefixRadix) withContext:reinterpret_cast<void *>(0)];
+        _end
 
-        /*_trace();
-        PrintTimes();
-        _trace();*/
+        _profile(reloadDataWithInvocation$radix$0)
+        [(NSMutableArray *) packages_ radixSortUsingFunction:reinterpret_cast<MenesRadixSortFunction>(&PackagePrefixRadix) withContext:reinterpret_cast<void *>(0)];
+        _end
 
-        _trace();
+        _profile(reloadDataWithInvocation$insertion)
+        CFArrayInsertionSortValues(packages_, CFRangeMake(0, CFArrayGetCount(packages_)), reinterpret_cast<CFComparatorFunction>(&PackageNameCompare), NULL);
+        _end
 
-        /*if (!packages.empty())
-            CFQSortArray(&packages.front(), packages.size(), sizeof(packages.front()), reinterpret_cast<CFComparatorFunction>(&PackageNameCompare_), NULL);*/
-        //std::sort(packages.begin(), packages.end(), PackageNameOrdering());
+        /*_profile(reloadDataWithInvocation$CFQSortArray)
+        CFQSortArray(&packages.front(), packages.size(), sizeof(packages.front()), reinterpret_cast<CFComparatorFunction>(&PackageNameCompare_), NULL);
+        _end*/
 
-        //CFArraySortValues((CFMutableArrayRef) packages_, CFRangeMake(0, [packages_ count]), reinterpret_cast<CFComparatorFunction>(&PackageNameCompare), NULL);
+        /*_profile(reloadDataWithInvocation$stdsort)
+        std::sort(packages.begin(), packages.end(), PackageNameOrdering());
+        _end*/
 
-        CFArrayInsertionSortValues(packages_, CFRangeMake(0, CFArrayGetCount(packages_)), reinterpret_cast<CFComparatorFunction>(&PackageNameCompare), NULL);
+        /*_profile(reloadDataWithInvocation$CFArraySortValues)
+        CFArraySortValues((CFMutableArrayRef) packages_, CFRangeMake(0, [packages_ count]), reinterpret_cast<CFComparatorFunction>(&PackageNameCompare), NULL);
+        _end*/
 
-        //[packages_ sortUsingFunction:reinterpret_cast<NSComparisonResult (*)(id, id, void *)>(&PackageNameCompare) context:NULL];
+        /*_profile(reloadDataWithInvocation$sortUsingFunction)
+        [packages_ sortUsingFunction:reinterpret_cast<NSComparisonResult (*)(id, id, void *)>(&PackageNameCompare) context:NULL];
+        _end*/
 
-        _trace();
 
         size_t count(CFArrayGetCount(packages_));
         MetaFile_->active_ = count;
-
         for (size_t index(0); index != count; ++index)
             [(Package *) CFArrayGetValueAtIndex(packages_, index) setIndex:index];
-
-        _trace();
     }
 } }
 
@@ -6177,10 +6310,12 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     _transient Database *database_;
     unsigned era_;
     _H<NSArray> packages_;
-    _H<NSMutableArray> sections_;
+    _H<NSArray> sections_;
     _H<UITableView, 2> list_;
-    _H<NSMutableArray> index_;
-    _H<NSMutableDictionary> indices_;
+
+    _H<NSArray> thumbs_;
+    std::vector<NSInteger> offset_;
+
     _H<NSString> title_;
     unsigned reloading_;
 }
@@ -6190,6 +6325,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 - (void) resetCursor;
 - (void) clearData;
 
+- (NSArray *) sectionsForPackages:(NSMutableArray *)packages;
+
 @end
 
 @implementation PackageListController
@@ -6301,12 +6438,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [[self navigationController] pushViewController:view animated:YES];
 }
 
-#if TryIndexedCollation
-+ (BOOL) hasIndexedCollation {
-    return NO; // XXX: objc_getClass("UILocalizedIndexedCollation") != nil;
-}
-#endif
-
 - (NSInteger) numberOfSectionsInTableView:(UITableView *)list {
     NSInteger count([sections_ count]);
     return count == 0 ? 1 : count;
@@ -6352,20 +6483,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (NSArray *) sectionIndexTitlesForTableView:(UITableView *)tableView {
-    if (![self showsSections])
-        return nil;
-
-    return index_;
+    return thumbs_;
 }
 
 - (NSInteger) tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
-#if TryIndexedCollation
-    if ([[self class] hasIndexedCollation]) {
-        return [[objc_getClass("UILocalizedIndexedCollation") currentCollation] sectionForSectionIndexTitleAtIndex:index];
-    }
-#endif
-
-    return index;
+    return offset_[index];
 }
 
 - (void) updateHeight {
@@ -6403,8 +6525,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
     packages_ = nil;
     sections_ = nil;
-    index_ = nil;
-    indices_ = nil;
+
+    thumbs_ = nil;
+    offset_.clear();
 
     [super releaseSubviews];
 }
@@ -6435,7 +6558,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         return;
     }
 
-    NSArray *packages;
+    NSMutableArray *packages;
 
   reload:
     if ([self shouldYield]) {
@@ -6464,92 +6587,82 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         goto reload;
     reloading_ = 0;
 
-    packages_ = packages;
-
-    indices_ = [NSMutableDictionary dictionaryWithCapacity:32];
-    sections_ = [NSMutableArray arrayWithCapacity:16];
-
-    Section *section = nil;
+    thumbs_ = nil;
+    offset_.clear();
 
-#if TryIndexedCollation
-    if ([[self class] hasIndexedCollation]) {
-        index_ = [[objc_getClass("UILocalizedIndexedCollation") currentCollation] sectionIndexTitles];
+    packages_ = packages;
 
-        id collation = [objc_getClass("UILocalizedIndexedCollation") currentCollation];
-        NSArray *titles = [collation sectionIndexTitles];
-        int secidx = -1;
+    if ([self showsSections])
+        sections_ = [self sectionsForPackages:packages];
+    else {
+        Section *section([[[Section alloc] initWithName:nil row:0 localize:NO] autorelease]);
+        [section setCount:[packages_ count]];
+        sections_ = [NSArray arrayWithObject:section];
+    }
 
-        _profile(PackageTable$reloadData$Section)
-            for (size_t offset(0), end([packages_ count]); offset != end; ++offset) {
-                Package *package;
-                int index;
+    [self updateHeight];
 
-                _profile(PackageTable$reloadData$Section$Package)
-                    package = [packages_ objectAtIndex:offset];
-                    index = [collation sectionForObject:package collationStringSelector:@selector(name)];
-                _end
+    _profile(PackageTable$reloadData$List)
+        [(UITableView *) list_ setDataSource:self];
+        [list_ reloadData];
+    _end
+}
 
-                while (secidx < index) {
-                    secidx += 1;
+    PrintTimes();
+}
 
-                    _profile(PackageTable$reloadData$Section$Allocate)
-                        section = [[[Section alloc] initWithName:[titles objectAtIndex:secidx] row:offset localize:NO] autorelease];
-                    _end
+- (NSArray *) sectionsForPackages:(NSMutableArray *)packages {
+    Section *prefix([[[Section alloc] initWithName:nil row:0 localize:NO] autorelease]);
+    size_t end([packages count]);
 
-                    _profile(PackageTable$reloadData$Section$Add)
-                        [sections_ addObject:section];
-                    _end
-                }
+    NSMutableArray *sections([NSMutableArray arrayWithCapacity:16]);
+    Section *section(prefix);
 
-                [section addToCount];
-            }
-        _end
-    } else
-#endif
-    {
-        index_ = [NSMutableArray arrayWithCapacity:32];
+    thumbs_ = CollationThumbs_;
+    offset_ = CollationOffset_;
 
-        bool sectioned([self showsSections]);
-        if (!sectioned) {
-            section = [[[Section alloc] initWithName:nil localize:false] autorelease];
-            [sections_ addObject:section];
-        }
+    size_t offset(0);
+    size_t offsets([CollationStarts_ count]);
 
-        _profile(PackageTable$reloadData$Section)
-            for (size_t offset(0), end([packages_ count]); offset != end; ++offset) {
-                Package *package;
-                unichar index;
+    NSString *start([CollationStarts_ objectAtIndex:offset]);
+    size_t length([start length]);
 
-                _profile(PackageTable$reloadData$Section$Package)
-                    package = [packages_ objectAtIndex:offset];
-                    index = [package index];
-                _end
+    for (size_t index(0); index != end; ++index) {
+        if (start != nil) {
+            Package *package([packages objectAtIndex:index]);
+            NSString *name(PackageName(package, @selector(cyname)));
 
-                if (sectioned && (section == nil || [section index] != index)) {
-                    _profile(PackageTable$reloadData$Section$Allocate)
-                        section = [[[Section alloc] initWithIndex:index row:offset] autorelease];
-                    _end
+            //while ([start compare:name options:NSNumericSearch range:NSMakeRange(0, length) locale:CollationLocale_] != NSOrderedDescending) {
+            while (StringNameCompare(start, name, length) != kCFCompareGreaterThan) {
+                NSString *title([CollationTitles_ objectAtIndex:offset]);
+                section = [[[Section alloc] initWithName:title row:index localize:NO] autorelease];
+                [sections addObject:section];
 
-                    [index_ addObject:[section name]];
-                    //[indices_ setObject:[NSNumber numberForInt:[sections_ count]] forKey:index];
+                start = ++offset == offsets ? nil : [CollationStarts_ objectAtIndex:offset];
+                if (start == nil)
+                    break;
+                length = [start length];
+            }
+        }
 
-                    _profile(PackageTable$reloadData$Section$Add)
-                        [sections_ addObject:section];
-                    _end
-                }
+        [section addToCount];
+    }
 
-                [section addToCount];
-            }
-        _end
+    for (; offset != offsets; ++offset) {
+        NSString *title([CollationTitles_ objectAtIndex:offset]);
+        Section *section([[[Section alloc] initWithName:title row:end localize:NO] autorelease]);
+        [sections addObject:section];
     }
 
-    [self updateHeight];
+    if ([prefix count] != 0) {
+        Section *suffix([sections lastObject]);
+        [prefix setName:[suffix name]];
+        [suffix setName:nil];
+        [sections insertObject:prefix atIndex:(offsets - 1)];
+    }
 
-    _profile(PackageTable$reloadData$List)
-        [(UITableView *) list_ setDataSource:self];
-        [list_ reloadData];
-    _end
-} }
+    return sections;
+}
 
 - (void) reloadData {
     [super reloadData];
@@ -6603,16 +6716,14 @@ typedef Function<void, NSMutableArray *> PackageSorter;
 } }
 
 - (NSMutableArray *) _reloadPackages {
-    NSMutableArray *filtered;
-    PackageSorter sorter;
-
 @synchronized (database_) {
     era_ = [database_ era];
-    NSArray *packages([database_ packages]);
 
-    filtered = [NSMutableArray arrayWithCapacity:[packages count]];
+    NSArray *packages([database_ packages]);
+    NSMutableArray *filtered([NSMutableArray arrayWithCapacity:[packages count]]);
 
     PackageFilter filter;
+    PackageSorter sorter;
 
     @synchronized (self) {
         filter = filter_;
@@ -6624,12 +6735,11 @@ typedef Function<void, NSMutableArray *> PackageSorter;
             if ([package valid] && filter(package))
                 [filtered addObject:package];
     _end
-}
 
     if (sorter)
         sorter(filtered);
     return filtered;
-}
+} }
 
 - (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(PackageFilter)filter {
     if ((self = [super initWithDatabase:database title:title]) != nil) {
@@ -7291,15 +7401,7 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
 /* }}} */
 
 /* Changes Controller {{{ */
-@interface ChangesController : CyteViewController <
-    UITableViewDataSource,
-    UITableViewDelegate
-> {
-    _transient Database *database_;
-    unsigned era_;
-    _H<NSMutableArray> packages_;
-    _H<NSMutableArray> sections_;
-    _H<UITableView, 2> list_;
+@interface ChangesController : FilteredPackageListController {
     unsigned upgrades_;
 }
 
@@ -7309,30 +7411,12 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
 
 @implementation ChangesController
 
-- (NSURL *) navigationURL {
-    return [NSURL URLWithString:@"cydia://changes"];
-}
-
-- (void) viewDidAppear:(BOOL)animated {
-    [super viewDidAppear:animated];
-    [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
-}
-
-- (NSInteger) numberOfSectionsInTableView:(UITableView *)list {
-    NSInteger count([sections_ count]);
-    return count == 0 ? 1 : count;
-}
-
-- (NSString *) tableView:(UITableView *)list titleForHeaderInSection:(NSInteger)section {
-    if ([sections_ count] == 0)
-        return nil;
-    return [[sections_ objectAtIndex:section] name];
+- (NSURL *) referrerURL {
+    return [NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/changes/", UI_]];
 }
 
-- (NSInteger) tableView:(UITableView *)list numberOfRowsInSection:(NSInteger)section {
-    if ([sections_ count] == 0)
-        return 0;
-    return [[sections_ objectAtIndex:section] count];
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://changes"];
 }
 
 - (Package *) packageAtIndexPath:(NSIndexPath *)path {
@@ -7348,24 +7432,6 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
     return [[[packages_ objectAtIndex:([section row] + row)] retain] autorelease];
 } }
 
-- (UITableViewCell *) tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)path {
-    PackageCell *cell((PackageCell *) [table dequeueReusableCellWithIdentifier:@"Package"]);
-    if (cell == nil)
-        cell = [[[PackageCell alloc] init] autorelease];
-
-    Package *package([database_ packageWithName:[[self packageAtIndexPath:path] id]]);
-    [cell setPackage:package asSummary:false];
-    return cell;
-}
-
-- (NSIndexPath *) tableView:(UITableView *)table willSelectRowAtIndexPath:(NSIndexPath *)path {
-    Package *package([self packageAtIndexPath:path]);
-    CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_ forPackage:[package id] withReferrer:[NSString stringWithFormat:@"%@/#!/changes/", UI_]] autorelease]);
-    [view setDelegate:delegate_];
-    [[self navigationController] pushViewController:view animated:YES];
-    return path;
-}
-
 - (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
     NSString *context([alert context]);
 
@@ -7404,84 +7470,48 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
     [[self navigationItem] setRightBarButtonItem:nil animated:YES];
 }
 
-- (void) loadView {
-    UIView *view([[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]);
-    [view setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
-    [self setView:view];
-
-    list_ = [[[UITableView alloc] initWithFrame:[view bounds] style:UITableViewStylePlain] autorelease];
-    [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-    [list_ setRowHeight:73];
-    [(UITableView *) list_ setDataSource:self];
-    [list_ setDelegate:self];
-    [view addSubview:list_];
+- (bool) shouldYield {
+    return true;
 }
 
-- (void) viewDidLoad {
-    [super viewDidLoad];
-
-    [[self navigationItem] setTitle:UCLocalize("CHANGES")];
+- (bool) shouldBlock {
+    return true;
 }
 
-- (void) releaseSubviews {
-    list_ = nil;
-
-    packages_ = nil;
-    sections_ = nil;
+- (void) useFilter {
+@synchronized (self) {
+    [self setFilter:[](Package *package) {
+        return [package upgradableAndEssential:YES] || [package visible];
+    }];
 
-    [super releaseSubviews];
-}
+    [self setSorter:[](NSMutableArray *packages) {
+        [packages radixSortUsingFunction:reinterpret_cast<MenesRadixSortFunction>(&PackageChangesRadix) withContext:NULL];
+    }];
+} }
 
 - (id) initWithDatabase:(Database *)database {
-    if ((self = [super init]) != nil) {
-        database_ = database;
+    if ((self = [super initWithDatabase:database title:UCLocalize("CHANGES")]) != nil) {
+        [self useFilter];
     } return self;
 }
 
-- (NSMutableArray *) _reloadPackages {
-@synchronized (database_) {
-    era_ = [database_ era];
-    NSArray *packages([database_ packages]);
-
-    NSMutableArray *filtered([NSMutableArray arrayWithCapacity:[packages count]]);
-
-    _trace();
-    _profile(ChangesController$_reloadPackages$Filter)
-        for (Package *package in packages)
-            if ([package upgradableAndEssential:YES] || [package visible])
-                CFArrayAppendValue((CFMutableArrayRef) filtered, package);
-    _end
-    _trace();
-    _profile(ChangesController$_reloadPackages$radixSort)
-        [filtered radixSortUsingFunction:reinterpret_cast<MenesRadixSortFunction>(&PackageChangesRadix) withContext:NULL];
-    _end
-    _trace();
-
-    return filtered;
-} }
-
-- (void) _reloadData {
+- (void) viewDidLoad {
+    [super viewDidLoad];
     [self setLeftBarButtonItem];
+}
 
-    NSMutableArray *packages;
-
-  reload:
-    if (true) {
-        UIProgressHUD *hud([delegate_ addProgressHUD]);
-        [hud setText:UCLocalize("LOADING")];
-        //NSLog(@"HUD:%@::%@", delegate_, hud);
-        packages = [self yieldToSelector:@selector(_reloadPackages)];
-        [delegate_ removeProgressHUD:hud];
-    } else {
-        packages = [self _reloadPackages];
-    }
+- (void) viewWillAppear:(BOOL)animated {
+    [super viewWillAppear:animated];
+    [self setLeftBarButtonItem];
+}
 
-@synchronized (database_) {
-    if (era_ != [database_ era])
-        goto reload;
+- (void) reloadData {
+    [self setLeftBarButtonItem];
+    [super reloadData];
+}
 
-    packages_ = packages;
-    sections_ = [NSMutableArray arrayWithCapacity:16];
+- (NSArray *) sectionsForPackages:(NSMutableArray *)packages {
+    NSMutableArray *sections([NSMutableArray arrayWithCapacity:16]);
 
     Section *upgradable = [[[Section alloc] initWithName:UCLocalize("AVAILABLE_UPGRADES") localize:NO] autorelease];
     Section *ignored = nil;
@@ -7493,8 +7523,8 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
 
     CFDateFormatterRef formatter(CFDateFormatterCreate(NULL, Locale_, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle));
 
-    for (size_t offset = 0, count = [packages_ count]; offset != count; ++offset) {
-        Package *package = [packages_ objectAtIndex:offset];
+    for (size_t offset = 0, count = [packages count]; offset != count; ++offset) {
+        Package *package = [packages objectAtIndex:offset];
 
         BOOL uae = [package upgradableAndEssential:YES];
 
@@ -7512,7 +7542,7 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
                 _profile(ChangesController$reloadData$Allocate)
                     name = [NSString stringWithFormat:UCLocalize("NEW_AT"), name];
                     section = [[[Section alloc] initWithName:name row:offset localize:NO] autorelease];
-                    [sections_ addObject:section];
+                    [sections addObject:section];
                 _end
             }
 
@@ -7532,16 +7562,16 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
     CFRelease(formatter);
 
     if (unseens) {
-        Section *last = [sections_ lastObject];
+        Section *last = [sections lastObject];
         size_t count = [last count];
-        [packages_ removeObjectsInRange:NSMakeRange([packages_ count] - count, count)];
-        [sections_ removeLastObject];
+        [packages removeObjectsInRange:NSMakeRange([packages count] - count, count)];
+        [sections removeLastObject];
     }
 
     if ([ignored count] != 0)
-        [sections_ insertObject:ignored atIndex:0];
+        [sections insertObject:ignored atIndex:0];
     if (upgrades_ != 0)
-        [sections_ insertObject:upgradable atIndex:0];
+        [sections insertObject:upgradable atIndex:0];
 
     [list_ reloadData];
 
@@ -7552,12 +7582,7 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
         action:@selector(upgradeButtonClicked)
     ] autorelease]) animated:YES];
 
-    PrintTimes();
-} }
-
-- (void) reloadData {
-    [super reloadData];
-    [self performSelector:@selector(_reloadData) withObject:nil afterDelay:0];
+    return sections;
 }
 
 @end
@@ -7683,8 +7708,19 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
 - (id) initWithDatabase:(Database *)database query:(NSString *)query {
     if ((self = [super initWithDatabase:database title:UCLocalize("SEARCH")])) {
         search_ = [[[UISearchBar alloc] init] autorelease];
+        [search_ setPlaceholder:UCLocalize("SEARCH_EX")];
         [search_ setDelegate:self];
 
+        UITextField *textField;
+        if ([search_ respondsToSelector:@selector(searchField)])
+            textField = [search_ searchField];
+        else
+            textField = MSHookIvar<UITextField *>(search_, "_searchField");
+
+        [textField setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
+        [textField setEnablesReturnKeyAutomatically:NO];
+        [[self navigationItem] setTitleView:textField];
+
         if (query != nil)
             [search_ setText:query];
         [self useSearch];
@@ -7698,17 +7734,6 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
         searchloaded_ = YES;
         [search_ setFrame:CGRectMake(0, 0, [[self view] bounds].size.width, 44.0f)];
         [search_ layoutSubviews];
-        [search_ setPlaceholder:UCLocalize("SEARCH_EX")];
-
-        UITextField *textField;
-        if ([search_ respondsToSelector:@selector(searchField)])
-            textField = [search_ searchField];
-        else
-            textField = MSHookIvar<UITextField *>(search_, "_searchField");
-
-        [textField setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
-        [textField setEnablesReturnKeyAutomatically:NO];
-        [[self navigationItem] setTitleView:textField];
     }
 
     if ([self isSummarized])
@@ -7902,6 +7927,7 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
 
 /* Installed Controller {{{ */
 @interface InstalledController : FilteredPackageListController {
+    bool sectioned_;
 }
 
 - (id) initWithDatabase:(Database *)database;
@@ -7919,18 +7945,41 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
     return [NSURL URLWithString:@"cydia://installed"];
 }
 
+- (bool) showsSections {
+    return sectioned_;
+}
+
+- (void) useUpdated {
+    sectioned_ = false;
+
+@synchronized (self) {
+    [self setFilter:[](Package *package) {
+        return ![package uninstalled] && package->role_ < 7;
+    }];
+
+    [self setSorter:[](NSMutableArray *packages) {
+        [packages radixSortUsingSelector:@selector(updated)];
+    }];
+} }
+
 - (void) useFilter:(UISegmentedControl *)segmented {
-    bool simple([segmented selectedSegmentIndex] == 0);
+    NSInteger selected([segmented selectedSegmentIndex]);
+    if (selected == 2)
+        return [self useUpdated];
+    bool simple(selected == 0);
+    sectioned_ = true;
 
 @synchronized (self) {
     [self setFilter:[=](Package *package) {
         return ![package uninstalled] && package->role_ <= (simple ? 1 : 3);
     }];
+
+    [self setSorter:nullptr];
 } }
 
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super initWithDatabase:database title:UCLocalize("INSTALLED")]) != nil) {
-        UISegmentedControl *segmented([[[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:UCLocalize("SIMPLE"), UCLocalize("EXPERT"), nil]] autorelease]);
+        UISegmentedControl *segmented([[[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:UCLocalize("USER"), UCLocalize("EXPERT"), UCLocalize("RECENT"), nil]] autorelease]);
         [segmented setSelectedSegmentIndex:0];
         [segmented setSegmentedControlStyle:UISegmentedControlStyleBar];
         [[self navigationItem] setTitleView:segmented];
@@ -8933,6 +8982,7 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
 }
 
 - (void) reloadDataWithInvocation:(NSInvocation *)invocation {
+_profile(reloadDataWithInvocation)
 @synchronized (self) {
     UIProgressHUD *hud(loaded_ ? [self addProgressHUD] : nil);
     if (hud != nil)
@@ -8945,6 +8995,7 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
     [essential_ removeAllObjects];
     [broken_ removeAllObjects];
 
+    _profile(reloadDataWithInvocation$Essential)
     NSArray *packages([database_ packages]);
     for (Package *package in packages) {
         if ([package half])
@@ -8955,6 +9006,7 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
             ++changes;
         }
     }
+    _end
 
     UITabBarItem *changesItem = [[[tabbar_ viewControllers] objectAtIndex:2] tabBarItem];
     if (changes != 0) {
@@ -8974,7 +9026,11 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
 
     if (hud != nil)
         [self removeProgressHUD:hud];
-} }
+}
+_end
+
+    PrintTimes();
+}
 
 - (void) updateData {
     [self _updateData];
@@ -8995,10 +9051,13 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
     if (emulated_ == nil)
         return;
 
-    [window_ addSubview:[tabbar_ view]];
     if ([window_ respondsToSelector:@selector(setRootViewController:)])
         [window_ setRootViewController:tabbar_];
-    [[emulated_ view] removeFromSuperview];
+    else {
+        [window_ addSubview:[tabbar_ view]];
+        [[emulated_ view] removeFromSuperview];
+    }
+
     emulated_ = nil;
     [window_ setUserInteractionEnabled:YES];
 }
@@ -9285,6 +9344,9 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
         return false;
     }
 
+    if ([tabbar_ modalViewController] != nil)
+        return false;
+
     // Use external process status API internally.
     // This is probably a really bad idea.
     // XXX: what is the point of this? does this solve anything at all?
@@ -9688,9 +9750,10 @@ _trace();
     [self setupViewControllers];
 
     emulated_ = [[[CydiaLoadingViewController alloc] init] autorelease];
-    [window_ addSubview:[emulated_ view]];
     if ([window_ respondsToSelector:@selector(setRootViewController:)])
         [window_ setRootViewController:emulated_];
+    else
+        [window_ addSubview:[emulated_ view]];
 
     [self performSelector:@selector(loadData) withObject:nil afterDelay:0];
 _trace();
@@ -9714,8 +9777,6 @@ _trace();
 
     [self reloadDataWithInvocation:nil];
     [self refreshIfPossible];
-    PrintTimes();
-
     [self disemulate];
 
     int savedIndex = [[Metadata_ objectForKey:@"InterfaceIndex"] intValue];
@@ -9778,7 +9839,7 @@ _trace();
 
     // (Try to) show the startup URL.
     if (starturl_ != nil) {
-        [self openCydiaURL:starturl_ forExternal:NO];
+        [self openCydiaURL:starturl_ forExternal:YES];
         starturl_ = nil;
     }
 }
@@ -9842,10 +9903,15 @@ Class $CFXPreferencesPropertyListSource;
 MSHook(BOOL, CFXPreferencesPropertyListSource$_backingPlistChangedSinceLastSync, CFXPreferencesPropertyListSource *self, SEL _cmd) {
     NSURL *&url(MSHookIvar<NSURL *>(self, "_url")), *old(url);
     NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
+
     url = MobilizeURL(url);
-    BOOL value(_CFXPreferencesPropertyListSource$_backingPlistChangedSinceLastSync(self, _cmd));
-    //NSLog(@"%@ %s", [url absoluteString], value ? "YES" : "NO");
-    url = old;
+    BOOL value; @try {
+        value = _CFXPreferencesPropertyListSource$_backingPlistChangedSinceLastSync(self, _cmd);
+        //NSLog(@"CFX %@ %s", [url absoluteString], value ? "YES" : "NO");
+    } @finally {
+        url = old;
+    }
+
     [pool release];
     return value;
 }
@@ -9853,10 +9919,15 @@ MSHook(BOOL, CFXPreferencesPropertyListSource$_backingPlistChangedSinceLastSync,
 MSHook(void *, CFXPreferencesPropertyListSource$createPlistFromDisk, CFXPreferencesPropertyListSource *self, SEL _cmd) {
     NSURL *&url(MSHookIvar<NSURL *>(self, "_url")), *old(url);
     NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
+
     url = MobilizeURL(url);
-    void *value(_CFXPreferencesPropertyListSource$createPlistFromDisk(self, _cmd));
-    //NSLog(@"%@ %@", [url absoluteString], value);
-    url = old;
+    void *value; @try {
+        value = _CFXPreferencesPropertyListSource$createPlistFromDisk(self, _cmd);
+        //NSLog(@"CFX %@ %@", [url absoluteString], value);
+    } @finally {
+        url = old;
+    }
+
     [pool release];
     return value;
 }
@@ -9976,7 +10047,9 @@ int main(int argc, char *argv[]) {
         if (Method method = class_getInstanceMethod($WAKWindow, @selector(screenSize)))
             method_setImplementation(method, (IMP) &$WAKWindow$screenSize);
 
-    $CFXPreferencesPropertyListSource = objc_getClass("CFXPreferencesPropertyListSource");
+    $CFXPreferencesPropertyListSource = objc_getClass("CFXPreferencesPropertyListSourceSynchronizer");
+    if ($CFXPreferencesPropertyListSource == Nil)
+        $CFXPreferencesPropertyListSource = objc_getClass("CFXPreferencesPropertyListSource");
 
     Method CFXPreferencesPropertyListSource$_backingPlistChangedSinceLastSync(class_getInstanceMethod($CFXPreferencesPropertyListSource, @selector(_backingPlistChangedSinceLastSync)));
     if (CFXPreferencesPropertyListSource$_backingPlistChangedSinceLastSync != NULL) {
@@ -10032,6 +10105,44 @@ int main(int argc, char *argv[]) {
         std::setlocale(LC_ALL, lang);
     }
     /* }}} */
+    /* Index Collation {{{ */
+    if (Class $UILocalizedIndexedCollation = objc_getClass("UILocalizedIndexedCollation")) {
+        NSBundle *bundle([NSBundle bundleForClass:$UILocalizedIndexedCollation]);
+        NSString *path([bundle pathForResource:@"UITableViewLocalizedSectionIndex" ofType:@"plist"]);
+        //path = @"/System/Library/Frameworks/UIKit.framework/.lproj/UITableViewLocalizedSectionIndex.plist";
+        NSDictionary *dictionary([NSDictionary dictionaryWithContentsOfFile:path]);
+        _H<UILocalizedIndexedCollation> collation([[[UILocalizedIndexedCollation alloc] initWithDictionary:dictionary] autorelease]);
+
+        CollationLocale_ = MSHookIvar<NSLocale *>(collation, "_locale");
+
+        CollationThumbs_ = [collation sectionIndexTitles];
+        for (size_t index(0), end([CollationThumbs_ count]); index != end; ++index)
+            CollationOffset_.push_back([collation sectionForSectionIndexTitleAtIndex:index]);
+
+        CollationTitles_ = [collation sectionTitles];
+        CollationStarts_ = MSHookIvar<NSArray *>(collation, "_sectionStartStrings");
+
+        NSString *&transform(MSHookIvar<NSString *>(collation, "_transform"));
+        if (&transform != NULL && transform != nil) {
+            /*if ([collation respondsToSelector:@selector(transformedCollationStringForString:)])
+                CollationModify_ = [=](NSString *value) { return [collation transformedCollationStringForString:value]; };*/
+            const UChar *uid(reinterpret_cast<const UChar *>([transform cStringUsingEncoding:NSUnicodeStringEncoding]));
+            UErrorCode code(U_ZERO_ERROR);
+            CollationTransl_ = utrans_openU(uid, -1, UTRANS_FORWARD, NULL, 0, NULL, &code);
+            if (!U_SUCCESS(code))
+                NSLog(@"%s", u_errorName(code));
+        }
+    } else {
+        CollationLocale_ = [[[NSLocale alloc] initWithLocaleIdentifier:@"en@collation=dictionary"] autorelease];
+
+        CollationThumbs_ = [NSArray arrayWithObjects:@"A",@"B",@"C",@"D",@"E",@"F",@"G",@"H",@"I",@"J",@"K",@"L",@"M",@"N",@"O",@"P",@"Q",@"R",@"S",@"T",@"U",@"V",@"W",@"X",@"Y",@"Z",@"#",nil];
+        for (NSInteger offset(0); offset != 28; ++offset)
+            CollationOffset_.push_back(offset);
+
+        CollationTitles_ = [NSArray arrayWithObjects:@"A",@"B",@"C",@"D",@"E",@"F",@"G",@"H",@"I",@"J",@"K",@"L",@"M",@"N",@"O",@"P",@"Q",@"R",@"S",@"T",@"U",@"V",@"W",@"X",@"Y",@"Z",@"#",nil];
+        CollationStarts_ = [NSArray arrayWithObjects:@"a",@"b",@"c",@"d",@"e",@"f",@"g",@"h",@"i",@"j",@"k",@"l",@"m",@"n",@"o",@"p",@"q",@"r",@"s",@"t",@"u",@"v",@"w",@"x",@"y",@"z",@"ʒ",nil];
+    }
+    /* }}} */
 
     apr_app_initialize(&argc, const_cast<const char * const **>(&argv), NULL);