├── .gitignore ├── .gitmodules ├── Application ├── ApplicationDelegate.h ├── ApplicationDelegate.m ├── BinaryImageCell.h ├── BinaryImageCell.m ├── Button.h ├── Button.m ├── CrashLog.h ├── CrashLog.m ├── CrashLogGroup.h ├── CrashLogGroup.m ├── Entitlements.plist ├── Makefile ├── ModalActionSheet.h ├── ModalActionSheet.m ├── PackageCache.h ├── PackageCache.m ├── RootCell.h ├── RootCell.m ├── RootViewController.h ├── RootViewController.m ├── ScriptViewController.h ├── ScriptViewController.m ├── SectionHeaderView.h ├── SectionHeaderView.m ├── SuspectsViewController.h ├── SuspectsViewController.m ├── TableViewCell.h ├── TableViewCell.m ├── TableViewCellLine.h ├── TableViewCellLine.m ├── TableViewController.h ├── TableViewController.m ├── UIImage+CrashReporter.h ├── UIImage+CrashReporter.m ├── UITableView+CrashReporter.h ├── UITableView+CrashReporter.m ├── VictimCell.h ├── VictimCell.m ├── VictimViewController.h ├── VictimViewController.m ├── font-awesome.h ├── main.m ├── pastie.h └── pastie.m ├── CHANGELOG.md ├── Design ├── icon-72.xcf ├── icon-72@2x.xcf ├── icon.idraw ├── icon.xcf ├── icon@2x.xcf └── navicon.xcf ├── LICENSE ├── Makefile ├── README.md ├── as_root ├── Entitlements.plist ├── Makefile └── as_root.c ├── common ├── crashlog_util.h ├── crashlog_util.m ├── exec_as_root.h ├── exec_as_root.m ├── paths.h └── preferences.h ├── extrainst_ ├── Entitlements.plist ├── Makefile └── main.mm ├── layout ├── Applications │ └── CrashReporter.app │ │ ├── CrashReporter_ │ │ ├── Default-568h@2x.png │ │ ├── Default-700-568h@2x.png │ │ ├── Default-700-Landscape@2x.png │ │ ├── Default-700-Portrait@2x.png │ │ ├── Default-700@2x.png │ │ ├── Default-800-667h@2x.png │ │ ├── Default-800-667h@3x.png │ │ ├── Default-800-Landscape-736h@3x.png │ │ ├── Default-800-Portrait-736h@3x.png │ │ ├── Default-Landscape.png │ │ ├── Default-Portrait.png │ │ ├── Default.png │ │ ├── Default@2x.png │ │ ├── Documentation │ │ ├── APPS.html │ │ ├── APP_EXTENSIONS.html │ │ ├── CRASHED_PROCESS.html │ │ ├── EARLIER.html │ │ ├── LATEST.html │ │ ├── LOADED_BINARIES.html │ │ ├── MAIN_SUSPECT.html │ │ ├── OTHER_SUSPECTS.html │ │ ├── REPORT_OVERVIEW.html │ │ ├── SERVICES.html │ │ └── style.css │ │ ├── FontAwesome.otf │ │ ├── Icon-60.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-76@2x~ipad.png │ │ ├── Icon-76~ipad.png │ │ ├── Icon-Small-40.png │ │ ├── Icon-Small-40@2x.png │ │ ├── Icon-Small-50.png │ │ ├── Icon-Small-50@2x.png │ │ ├── Icon-Small.png │ │ ├── Icon-Small@2x.png │ │ ├── Info.plist │ │ ├── help_button.png │ │ ├── help_button@2x.png │ │ ├── help_button@3x.png │ │ ├── icon-72.png │ │ ├── icon-72@2x.png │ │ ├── icon.png │ │ ├── icon@2x.png │ │ ├── navicon.png │ │ ├── navicon@2x.png │ │ ├── notifier_ │ │ └── whitelist.plist ├── DEBIAN │ ├── control │ └── crash_reporter └── Library │ ├── MobileSubstrate │ └── DynamicLibraries │ │ ├── CrashReporter.dylib │ │ ├── CrashReporter.plist │ │ ├── CrashReporterScanner.dylib │ │ └── CrashReporterScanner.plist │ └── PreferenceLoader │ └── Preferences │ └── CrashReporter │ ├── CrashReporter.plist │ ├── icon.png │ ├── icon@2x.png │ └── icon@3x.png ├── monitor ├── Makefile ├── Tweak.xm └── monitor.plist ├── notifier ├── Entitlements.plist ├── Makefile └── main.m └── scanner ├── CRAlertItem.h ├── CRAlertItem.xm ├── CRCannotEmailAlertItem.h ├── CRCannotEmailAlertItem.xm ├── CRMailViewController.h ├── CRMailViewController.m ├── CRMissingFilterAlertItem.h ├── CRMissingFilterAlertItem.xm ├── Makefile ├── Tweak.xm └── scanner.plist /.gitignore: -------------------------------------------------------------------------------- 1 | theos 2 | theos/* 3 | .theos/* 4 | *.o 5 | */obj/* 6 | **/obj/* 7 | _/* 8 | *.deb 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Localization"] 2 | path = Localization 3 | url = https://github.com/ashikase/Localization.git 4 | [submodule "Common"] 5 | path = Libraries/Common 6 | url = http://github.com/ashikase/Common 7 | -------------------------------------------------------------------------------- /Application/ApplicationDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import 13 | 14 | @interface ApplicationDelegate : NSObject 15 | @end 16 | 17 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 18 | -------------------------------------------------------------------------------- /Application/ApplicationDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "ApplicationDelegate.h" 13 | 14 | #import 15 | #import "CrashLog.h" 16 | #import "RootViewController.h" 17 | #import "ScriptViewController.h" 18 | #import "SuspectsViewController.h" 19 | 20 | #include 21 | #include "paths.h" 22 | #include "preferences.h" 23 | 24 | NSString * const kNotificationCrashLogsChanged = @"notificationCrashLogsChanged"; 25 | 26 | static void resetIconBadgeNumber() { 27 | // Reset preference used for tracking count. 28 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 29 | [defaults setInteger:0 forKey:@kCrashesSinceLastLaunch]; 30 | [defaults synchronize]; 31 | 32 | // Reset icon badge number to zero. 33 | [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; 34 | } 35 | 36 | @interface UIAlertView () 37 | - (void)setNumberOfRows:(int)rows; 38 | @end 39 | 40 | @interface ApplicationDelegate () 41 | @end 42 | 43 | @implementation ApplicationDelegate { 44 | UIWindow *window_; 45 | UINavigationController *navigationController_; 46 | 47 | NSMutableDictionary *notificationFilepaths_; 48 | } 49 | 50 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 51 | // Create dictionary to hold filepaths for any incoming notifications. 52 | notificationFilepaths_ = [[NSMutableDictionary alloc] init]; 53 | 54 | // Create root view controller. 55 | RootViewController *rootController = [[RootViewController alloc] init]; 56 | navigationController_ = [[UINavigationController alloc] initWithRootViewController:rootController]; 57 | [rootController release]; 58 | 59 | // Create window. 60 | window_ = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 61 | if (IOS_LT(4_0)) { 62 | [window_ addSubview:[navigationController_ view]]; 63 | } else { 64 | [window_ setRootViewController:navigationController_]; 65 | } 66 | [window_ makeKeyAndVisible]; 67 | 68 | // If launched via notification, handle the notification. 69 | UILocalNotification *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey]; 70 | if (notification != nil) { 71 | NSString *filepath = [[notification userInfo] objectForKey:@"filepath"]; 72 | if (filepath != nil) { 73 | [self showDetailsForLogAtPath:filepath animated:NO]; 74 | } 75 | } 76 | 77 | // Reset icon badge number. 78 | resetIconBadgeNumber(); 79 | 80 | return YES; 81 | } 82 | 83 | - (void)dealloc { 84 | [notificationFilepaths_ release]; 85 | [navigationController_ release]; 86 | [window_ release]; 87 | [super dealloc]; 88 | } 89 | 90 | // FIXME: Attempt to call Base64 method will cause a crash on iOS < 4.0. 91 | - (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url { 92 | BOOL didOpenURL = NO; 93 | 94 | NSString *command = [url host]; 95 | if ([command isEqualToString:@"script"]) { 96 | ScriptViewController *viewController = nil; 97 | 98 | // NOTE: Script command must include either a "data" or "url" parameter. 99 | // The "data" parameter takes precedence. 100 | for (NSString *param in [[url query] componentsSeparatedByString:@"&"]) { 101 | if ([param hasPrefix:@"data="]) { 102 | NSString *value = [param substringFromIndex:5]; 103 | NSData *data = nil; 104 | if (IOS_LT(7_0)) { 105 | data = [[NSData alloc] initWithBase64Encoding:value]; 106 | } else { 107 | data = [[NSData alloc] initWithBase64EncodedString:value options:0]; 108 | } 109 | if (data != nil) { 110 | NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 111 | if (string != nil) { 112 | viewController = [[ScriptViewController alloc] initWithString:string]; 113 | [string release]; 114 | } else { 115 | NSLog(@"ERROR: Failed to interpret decoded data as UTF8 string."); 116 | } 117 | [data release]; 118 | } else { 119 | NSLog(@"ERROR: Failed to decode provided data."); 120 | } 121 | break; 122 | } else if ([param hasPrefix:@"url="]) { 123 | NSString *urlString = [[param substringFromIndex:4] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 124 | NSURL *url = [[NSURL alloc] initWithString:urlString]; 125 | if (url != nil) { 126 | viewController = [[ScriptViewController alloc] initWithURL:url]; 127 | [url release]; 128 | } else { 129 | NSLog(@"ERROR: Provided url string is invalid: %@", urlString); 130 | } 131 | break; 132 | } 133 | } 134 | 135 | if (viewController != nil) { 136 | [navigationController_ popToRootViewControllerAnimated:NO]; 137 | [navigationController_ pushViewController:viewController animated:YES]; 138 | [viewController release]; 139 | 140 | didOpenURL = YES; 141 | } else { 142 | NSLog(@"ERROR: Command \"script\" requires a valid \"data\" or \"url\" parameter."); 143 | } 144 | } else { 145 | NSLog(@"ERROR: URL did not contain a supported command."); 146 | } 147 | 148 | return didOpenURL; 149 | } 150 | 151 | - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification { 152 | NSString *filepath = [[notification userInfo] objectForKey:@"filepath"]; 153 | 154 | // NOTE: This method is call if a notification is received while 155 | // CrashReporter is running (foreground/background). 156 | // 157 | UIApplicationState state = [application applicationState]; 158 | if (state == UIApplicationStateActive) { 159 | // CrashReporter is in the foreground. 160 | NSString *title = NSLocalizedString(@"CRASH_DETECTED", nil); 161 | NSString *viewTitle = NSLocalizedString(@"VIEW", nil); 162 | NSString *ignoreTitle = NSLocalizedString(@"IGNORE", nil); 163 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:[notification alertBody] 164 | delegate:self cancelButtonTitle:ignoreTitle otherButtonTitles:viewTitle, nil]; 165 | 166 | if (filepath != nil) { 167 | NSString *key = [NSString stringWithFormat:@"%p", alert]; 168 | [notificationFilepaths_ setObject:filepath forKey:key]; 169 | } 170 | 171 | [alert show]; 172 | [alert release]; 173 | 174 | // Reset icon badge number. 175 | resetIconBadgeNumber(); 176 | } else { 177 | // CrashReporter was in the background. 178 | if (filepath != nil) { 179 | [self showDetailsForLogAtPath:filepath animated:NO]; 180 | } 181 | } 182 | 183 | // Post notification to update views. 184 | [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationCrashLogsChanged object:self]; 185 | } 186 | 187 | - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { 188 | [TSInstruction flushInstructions]; 189 | } 190 | 191 | - (void)applicationWillEnterForeground:(UIApplication *)application { 192 | // Recreate the "is_running" file. 193 | FILE *f = fopen(kIsRunningFilepath, "w"); 194 | if (f != NULL) { 195 | fclose(f); 196 | } else { 197 | fprintf(stderr, "ERROR: Failed to recreate \"is running\" file, errno = %d.\n", errno); 198 | } 199 | 200 | // Reset icon badge count. 201 | resetIconBadgeNumber(); 202 | 203 | // Post notification to update views. 204 | [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationCrashLogsChanged object:self]; 205 | } 206 | 207 | - (void)applicationDidEnterBackground:(UIApplication *)application { 208 | // NOTE: Although the app has not technically shutdown, this may be our last 209 | // chance to do any processing, as once the app is suspended in the 210 | // background, it will be shutdown via SIGKILL. 211 | // NOTE: If a tweak causes an issue after this point, we will not be able to 212 | // detect it and will not be able to enable Safe Mode on next start-up. 213 | if (unlink(kIsRunningFilepath) != 0) { 214 | fprintf(stderr, "ERROR: Failed to delete \"is running\" file, errno = %d.\n", errno); 215 | } 216 | } 217 | 218 | - (void)applicationWillTerminate:(UIApplication *)application { 219 | // NOTE: As this app supports background suspension (as opposed to 220 | // termination), this method will only be called if the user manually 221 | // terminates the app. 222 | if (unlink(kIsRunningFilepath) != 0) { 223 | fprintf(stderr, "ERROR: Failed to delete \"is running\" file, errno = %d.\n", errno); 224 | } 225 | } 226 | 227 | #pragma mark - Other 228 | 229 | - (void)showDetailsForLogAtPath:(NSString *)filepath animated:(BOOL)animated { 230 | NSError *error = nil; 231 | NSFileManager *fileMan = [NSFileManager defaultManager]; 232 | NSDictionary *attrib = [fileMan attributesOfItemAtPath:filepath error:&error]; 233 | if (attrib != nil) { 234 | if ([[attrib fileType] isEqualToString:NSFileTypeSymbolicLink]) { 235 | filepath = [fileMan destinationOfSymbolicLinkAtPath:filepath error:&error]; 236 | if (filepath == nil) { 237 | NSLog(@"ERROR: Unable to determine destination of symbolic link: %@", [error localizedDescription]); 238 | } 239 | } 240 | 241 | CrashLog *crashLog = [CrashLog crashLogWithFilepath:filepath]; 242 | if (crashLog != nil) { 243 | SuspectsViewController *controller = [[SuspectsViewController alloc] initWithCrashLog:crashLog]; 244 | [navigationController_ popToRootViewControllerAnimated:animated]; 245 | [navigationController_ pushViewController:controller animated:animated]; 246 | [controller release]; 247 | } 248 | } else { 249 | NSLog(@"ERROR: Unable to retrieve attributes for file: %@", [error localizedDescription]); 250 | } 251 | } 252 | 253 | #pragma mark - UIAlertViewDelegate 254 | 255 | - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { 256 | // Is local notification alert. 257 | NSString *key = [NSString stringWithFormat:@"%p", alertView]; 258 | if (buttonIndex != 0) { 259 | NSString *filepath = [notificationFilepaths_ objectForKey:key]; 260 | if (filepath != nil) { 261 | [self showDetailsForLogAtPath:filepath animated:YES]; 262 | } 263 | } 264 | [notificationFilepaths_ removeObjectForKey:key]; 265 | } 266 | 267 | @end 268 | 269 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 270 | -------------------------------------------------------------------------------- /Application/BinaryImageCell.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "TableViewCell.h" 13 | 14 | typedef enum : NSUInteger { 15 | BinaryImageCellPackageTypeUnknown, 16 | BinaryImageCellPackageTypeApple, 17 | BinaryImageCellPackageTypeDebian 18 | } BinaryImageCellPackageType; 19 | 20 | @interface BinaryImageCell : TableViewCell 21 | @property(nonatomic, assign, getter = isNewer) BOOL newer; 22 | @property(nonatomic, assign, getter = isRecent) BOOL recent; 23 | @property(nonatomic, assign) BinaryImageCellPackageType packageType; 24 | + (CGFloat)heightForPackageRowCount:(NSUInteger)rowCount; 25 | - (void)setPackageName:(NSString *)packageName; 26 | - (void)setPackageIdentifier:(NSString *)packageIdentifier; 27 | - (void)setPackageInstallDate:(NSString *)packageInstallDate; 28 | @end 29 | 30 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 31 | -------------------------------------------------------------------------------- /Application/BinaryImageCell.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "BinaryImageCell.h" 13 | 14 | #import 15 | #import 16 | #import "TableViewCellLine.h" 17 | #include "font-awesome.h" 18 | 19 | #define kColorInstallDate [UIColor grayColor] 20 | #define kColorNewer [UIColor lightGrayColor] 21 | #define kColorRecent [UIColor redColor] 22 | 23 | @implementation BinaryImageCell { 24 | TableViewCellLine *packageNameLine_; 25 | TableViewCellLine *packageIdentifierLine_; 26 | TableViewCellLine *packageInstallDateLine_; 27 | } 28 | 29 | @synthesize newer = newer_; 30 | @synthesize recent = recent_; 31 | @synthesize packageType = packageType_; 32 | 33 | @dynamic showsTopSeparator; 34 | 35 | #pragma mark - Creation & Destruction 36 | 37 | + (CGFloat)heightForPackageRowCount:(NSUInteger)rowCount { 38 | // FIXME: The (+ x.0) values added to the font sizes are only valid for the 39 | // current font sizes (18.0 and 12.0). Determine proper calculation. 40 | return [super cellHeight] + [TableViewCellLine defaultHeight] * rowCount; 41 | } 42 | 43 | #pragma mark - Overrides (TableViewCell) 44 | 45 | - (id)initWithReuseIdentifier:(NSString *)reuseIdentifier { 46 | self = [super initWithReuseIdentifier:reuseIdentifier]; 47 | if (self != nil) { 48 | packageNameLine_ = [[self addLine] retain]; 49 | packageIdentifierLine_ = [[self addLine] retain]; 50 | packageInstallDateLine_ = [[self addLine] retain]; 51 | packageInstallDateLine_.iconLabel.text = @kFontAwesomeClockO; 52 | } 53 | return self; 54 | } 55 | 56 | - (void)dealloc { 57 | [packageNameLine_ release]; 58 | [packageIdentifierLine_ release]; 59 | [packageInstallDateLine_ release]; 60 | [super dealloc]; 61 | } 62 | 63 | - (void)configureWithObject:(id)object { 64 | NSAssert([object isKindOfClass:[CRBinaryImage class]], @"ERROR: Incorrect class type: Expected CRBinaryImage, received %@.", [object class]); 65 | 66 | CRBinaryImage *binaryImage = object; 67 | NSString *text = [[binaryImage path] lastPathComponent]; 68 | [self setName:text]; 69 | 70 | PIPackage *package = [binaryImage package]; 71 | if (package != nil) { 72 | NSString *string = nil; 73 | BOOL isRecent = NO; 74 | NSDate *installDate = [package installDate]; 75 | const NSTimeInterval interval = [[self referenceDate] timeIntervalSinceDate:installDate]; 76 | if (interval < 86400.0) { 77 | if (interval < 3600.0) { 78 | string = NSLocalizedString(@"LESS_THAN_HOUR", nil); 79 | } else { 80 | string = [NSString stringWithFormat:NSLocalizedString(@"LESS_THAN_HOURS", nil), (unsigned)ceil(interval / 3600.0)]; 81 | } 82 | isRecent = YES; 83 | } else { 84 | string = [[[self class] dateFormatter] stringFromDate:installDate]; 85 | } 86 | [self setPackageInstallDate:string]; 87 | [self setRecent:isRecent]; 88 | 89 | [self setPackageName:[NSString stringWithFormat:@"%@ (v%@)", [package name] , [package version]]]; 90 | [self setPackageIdentifier:[package identifier]]; 91 | [self setPackageType:([package isKindOfClass:[PIApplePackage class]] ? 92 | BinaryImageCellPackageTypeApple : BinaryImageCellPackageTypeDebian)]; 93 | } else { 94 | [self setPackageName:nil]; 95 | [self setPackageIdentifier:nil]; 96 | [self setPackageInstallDate:nil]; 97 | [self setPackageType:BinaryImageCellPackageTypeUnknown]; 98 | } 99 | } 100 | 101 | #pragma mark - Properties 102 | 103 | - (void)setPackageName:(NSString *)packageName { 104 | [self setText:packageName forLabel:packageNameLine_.label]; 105 | } 106 | 107 | - (void)setPackageIdentifier:(NSString *)packageIdentifier { 108 | [self setText:packageIdentifier forLabel:packageIdentifierLine_.label]; 109 | } 110 | 111 | - (void)setPackageInstallDate:(NSString *)packageInstallDate { 112 | if ([packageInstallDate length] != 0) { 113 | packageInstallDate = [NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"PACKAGE_INSTALL_DATE_PREFIX", nil), packageInstallDate]; 114 | } 115 | [self setText:packageInstallDate forLabel:packageInstallDateLine_.label]; 116 | } 117 | 118 | - (void)setPackageType:(BinaryImageCellPackageType)packageType { 119 | if (packageType_ != packageType) { 120 | packageType_ = packageType; 121 | 122 | NSString *text = nil; 123 | switch (packageType_) { 124 | case BinaryImageCellPackageTypeApple: text = @kFontAwesomeApple; break; 125 | case BinaryImageCellPackageTypeDebian: text = @kFontAwesomeDropbox; break; 126 | default: break; 127 | } 128 | packageIdentifierLine_.iconLabel.text = text; 129 | [self setNeedsLayout]; 130 | } 131 | } 132 | 133 | - (void)setNewer:(BOOL)newer { 134 | if (newer_ != newer) { 135 | newer_ = newer; 136 | [packageInstallDateLine_.label setTextColor:(newer_ ? kColorNewer : kColorInstallDate)]; 137 | } 138 | } 139 | 140 | - (void)setRecent:(BOOL)recent { 141 | if (recent_ != recent) { 142 | recent_ = recent; 143 | [packageInstallDateLine_.label setTextColor:(recent_ ? kColorRecent : kColorInstallDate)]; 144 | } 145 | } 146 | 147 | @end 148 | 149 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 150 | -------------------------------------------------------------------------------- /Application/Button.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | @interface Button : UIButton 13 | + (Button *)button; 14 | @end 15 | 16 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 17 | -------------------------------------------------------------------------------- /Application/Button.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "Button.h" 13 | 14 | #import "UIImage+CrashReporter.h" 15 | 16 | @interface UIImage (UIImagePrivate) 17 | + (id)kitImageNamed:(NSString *)name; 18 | @end 19 | 20 | @implementation Button 21 | 22 | + (instancetype)button { 23 | Button *button = [Button buttonWithType:UIButtonTypeCustom]; 24 | [button setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; 25 | 26 | CALayer *layer = button.layer; 27 | [layer setBorderWidth:1.0]; 28 | 29 | if (IOS_LT(7_0)) { 30 | [button setAdjustsImageWhenHighlighted:YES]; 31 | 32 | [layer setBorderColor:[[UIColor colorWithRed:(171.0 / 255.0) green:(171.0 / 255.0) blue:(171.0 / 255.0) alpha:1.0] CGColor]]; 33 | [layer setCornerRadius:8.0]; 34 | [layer setMasksToBounds:YES]; 35 | 36 | UILabel *label = [button titleLabel]; 37 | [label setFont:[UIFont boldSystemFontOfSize:18.0]]; 38 | 39 | if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { 40 | UIImage *image = [UIImage kitImageNamed:@"UINavigationBarSilverTallBackground.png"]; 41 | [button setBackgroundImage:[image stretchableImageWithLeftCapWidth:0.0 topCapHeight:0.0] forState:UIControlStateNormal]; 42 | [button setTitleColor:[UIColor colorWithRed:(114.0 / 255.0) green:(121.0 / 255.0) blue:(130.0 / 255.0) alpha:1.0] forState:UIControlStateNormal]; 43 | [button setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; 44 | [button setTitleShadowColor:[UIColor colorWithRed:(230.0 / 255.0) green:(230.0 / 255.0) blue:(230.0 / 255.0) alpha:1.0] forState:UIControlStateNormal]; 45 | [button setTitleShadowColor:[UIColor blackColor] forState:UIControlStateHighlighted]; 46 | [label setShadowOffset:CGSizeMake(0.0, 1.0)]; 47 | } else { 48 | UIImage *image = [UIImage kitImageNamed:@"UINavigationBarDefaultBackground.png"]; 49 | [button setBackgroundImage:[image stretchableImageWithLeftCapWidth:0.0 topCapHeight:0.0] forState:UIControlStateNormal]; 50 | [label setShadowOffset:CGSizeMake(0.0, -1.0)]; 51 | } 52 | } else { 53 | UIColor *buttonColor = [UIColor colorWithRed:(36.0 / 255.0) green:(132.0 / 255.0) blue:(232.0 / 255.0) alpha:1.0]; 54 | UIImage *image = [[UIImage imageWithColor:buttonColor] stretchableImageWithLeftCapWidth:0.0 topCapHeight:0.0]; 55 | [button setBackgroundImage:image forState:UIControlStateNormal]; 56 | [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 57 | 58 | buttonColor = [UIColor colorWithRed:0.85 green:0.85 blue:0.85 alpha:1.0]; 59 | image = [[UIImage imageWithColor:buttonColor] stretchableImageWithLeftCapWidth:0.0 topCapHeight:0.0]; 60 | [button setBackgroundImage:image forState:UIControlStateDisabled]; 61 | [button setTitleColor:[UIColor grayColor] forState:UIControlStateDisabled]; 62 | 63 | layer.borderColor = [[UIColor blackColor] CGColor]; 64 | } 65 | 66 | return button; 67 | } 68 | 69 | @end 70 | 71 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 72 | -------------------------------------------------------------------------------- /Application/CrashLog.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import 13 | 14 | extern NSString * const kViewedCrashLogs; 15 | 16 | typedef enum : NSUInteger { 17 | CrashLogTypeUnknown, 18 | CrashLogTypeApp, 19 | CrashLogTypeAppExtension, 20 | CrashLogTypeService 21 | } CrashLogType; 22 | 23 | typedef enum : NSUInteger { 24 | CrashLogBugTypeUnknown, 25 | CrashLogBugTypeCrash, 26 | CrashLogBugTypeLowMemory, 27 | CrashLogBugTypeOther 28 | } CrashLogBugType; 29 | 30 | @class CRBinaryImage; 31 | 32 | @interface CrashLog : NSObject 33 | @property(nonatomic, readonly) NSString *filepath; 34 | @property(nonatomic, readonly) NSString *logName; 35 | @property(nonatomic, readonly) NSDate *logDate; 36 | @property(nonatomic, readonly) CrashLogType type; 37 | @property(nonatomic, readonly) CrashLogBugType bugType; 38 | @property(nonatomic, readonly) CRBinaryImage *victim; 39 | @property(nonatomic, readonly) NSArray *suspects; 40 | @property(nonatomic, readonly) NSArray *potentialSuspects; 41 | @property(nonatomic, readonly, getter = isLoaded) BOOL loaded; 42 | @property(nonatomic, readonly, getter = isSymbolicated) BOOL symbolicated; 43 | @property(nonatomic, assign, getter = isViewed) BOOL viewed; 44 | + (instancetype)crashLogWithFilepath:(NSString *)filepath; 45 | - (BOOL)delete; 46 | - (BOOL)load; 47 | @end 48 | 49 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 50 | -------------------------------------------------------------------------------- /Application/CrashLogGroup.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import 13 | 14 | #import "CrashLog.h" 15 | 16 | extern NSString * const kCrashLogDirectoryForMobile; 17 | extern NSString * const kCrashLogDirectoryForRoot; 18 | 19 | typedef enum : NSUInteger { 20 | CrashLogGroupTypeUnknown = CrashLogTypeUnknown, 21 | CrashLogGroupTypeApp = CrashLogTypeApp, 22 | CrashLogGroupTypeAppExtension = CrashLogTypeAppExtension, 23 | CrashLogGroupTypeService = CrashLogTypeService 24 | } CrashLogGroupType; 25 | 26 | @interface CrashLogGroup : NSObject 27 | @property (nonatomic, readonly) NSString *name; 28 | @property (nonatomic, readonly) NSString *logDirectory; 29 | @property (nonatomic, readonly) NSArray *crashLogs; 30 | @property (nonatomic, readonly) CrashLogGroupType type; 31 | + (NSArray *)groupsForType:(CrashLogGroupType)type; 32 | + (void)forgetGroups; 33 | + (instancetype)groupWithName:(NSString *)name logDirectory:(NSString *)logDirectory; 34 | - (instancetype)initWithName:(NSString *)name logDirectory:(NSString *)logDirectory; 35 | - (void)addCrashLog:(CrashLog *)crashLog; 36 | - (BOOL)delete; 37 | - (BOOL)deleteCrashLog:(CrashLog *)crashLog; 38 | @end 39 | 40 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 41 | -------------------------------------------------------------------------------- /Application/CrashLogGroup.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "CrashLogGroup.h" 13 | 14 | #include "paths.h" 15 | 16 | static NSMutableArray *crashLogGroups$ = nil; 17 | 18 | static NSMutableArray *appCrashLogGroups$ = nil; 19 | static NSMutableArray *appExtensionCrashLogGroups$ = nil; 20 | static NSMutableArray *serviceCrashLogGroups$ = nil; 21 | 22 | static NSArray *crashLogGroupsForDirectory(NSString *directory) { 23 | NSMutableDictionary *groups = [NSMutableDictionary dictionary]; 24 | NSMutableArray *existentFilepaths = [[NSMutableArray alloc] init]; 25 | 26 | // Look in path for crash log files; group logs by app name. 27 | NSFileManager *fileMan = [NSFileManager defaultManager]; 28 | NSError *error = nil; 29 | NSArray *contents = [fileMan contentsOfDirectoryAtPath:directory error:&error]; 30 | if (contents != nil) { 31 | for (NSString *filename in contents) { 32 | if ([filename hasSuffix:@"ips"] || [filename hasSuffix:@"plist"] || [filename hasSuffix:@"synced"]) { 33 | NSString *filepath = [directory stringByAppendingPathComponent:filename]; 34 | CrashLog *crashLog = [CrashLog crashLogWithFilepath:filepath]; 35 | if (crashLog != nil) { 36 | // Filter out non crash-related logs. 37 | // NOTE: Determining the bug type requires parsing the crash 38 | // report, which is time consuming. 39 | // NOTE: Not required on iOS versions before 9.3, where the 40 | // filenames of crash-relatd logs differ from other 41 | // log types. 42 | if (IOS_GTE(9_3)) { 43 | if ([crashLog bugType] == CrashLogBugTypeOther) { 44 | continue; 45 | } 46 | } 47 | 48 | // Store filepath for "known viewed" check below. 49 | [existentFilepaths addObject:filepath]; 50 | 51 | // Store crash log object in group. 52 | NSString *name = [crashLog logName]; 53 | CrashLogGroup *group = [groups objectForKey:name]; 54 | if (group == nil) { 55 | group = [[CrashLogGroup alloc] initWithName:name logDirectory:directory]; 56 | [groups setObject:group forKey:name]; 57 | [group release]; 58 | } 59 | [group addCrashLog:crashLog]; 60 | } 61 | } 62 | } 63 | } else { 64 | NSLog(@"ERROR: Unable to retrieve contents of directory \"%@\": %@", directory, [error localizedDescription]); 65 | } 66 | 67 | // Update list of viewed crash logs, removing entries that no longer exist. 68 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 69 | NSArray *oldViewedCrashLogs = [defaults arrayForKey:kViewedCrashLogs]; 70 | NSMutableArray *newViewedCrashLogs = [[NSMutableArray alloc] initWithArray:oldViewedCrashLogs]; 71 | for (NSString *filepath in oldViewedCrashLogs) { 72 | if ([[filepath stringByDeletingLastPathComponent] isEqualToString:directory]) { 73 | if (![existentFilepaths containsObject:filepath]) { 74 | [newViewedCrashLogs removeObject:filepath]; 75 | } 76 | } 77 | } 78 | [defaults setObject:newViewedCrashLogs forKey:kViewedCrashLogs]; 79 | [defaults synchronize]; 80 | [newViewedCrashLogs release]; 81 | [existentFilepaths release]; 82 | 83 | return [groups allValues]; 84 | } 85 | 86 | static NSInteger compareCrashLogGroups(CrashLogGroup *a, CrashLogGroup *b, void *context) { 87 | return [[a name] compare:[b name] options:NSCaseInsensitiveSearch]; 88 | } 89 | 90 | static NSInteger reverseCompareCrashLogs(CrashLog *a, CrashLog *b, void *context) { 91 | return [[b filepath] compare:[a filepath]]; 92 | } 93 | 94 | static NSArray *crashLogGroups() { 95 | if (crashLogGroups$ == nil) { 96 | NSMutableArray *groups = [[NSMutableArray alloc] init]; 97 | [groups addObjectsFromArray:crashLogGroupsForDirectory(@kCrashLogDirectoryForMobile)]; 98 | [groups addObjectsFromArray:crashLogGroupsForDirectory(@kCrashLogDirectoryForRoot)]; 99 | crashLogGroups$ = [[groups sortedArrayUsingFunction:compareCrashLogGroups context:NULL] mutableCopy]; 100 | [groups release]; 101 | } 102 | return crashLogGroups$; 103 | } 104 | 105 | static NSArray *crashLogGroupsForType(CrashLogGroupType type) { 106 | NSMutableArray *groups = [NSMutableArray array]; 107 | for (CrashLogGroup *group in crashLogGroups()) { 108 | if ([group type] == type) { 109 | [groups addObject:group]; 110 | } 111 | } 112 | return groups; 113 | } 114 | 115 | @implementation CrashLogGroup { 116 | NSMutableArray *crashLogs_; 117 | } 118 | 119 | @synthesize name = name_; 120 | @synthesize logDirectory = logDirectory_; 121 | 122 | + (NSArray *)groupsForType:(CrashLogGroupType)type { 123 | NSArray *groups = nil; 124 | 125 | switch (type) { 126 | case CrashLogGroupTypeApp: 127 | if (appCrashLogGroups$ == nil) { 128 | appCrashLogGroups$ = [crashLogGroupsForType(type) mutableCopy]; 129 | } 130 | groups = appCrashLogGroups$; 131 | break; 132 | case CrashLogGroupTypeAppExtension: 133 | if (appExtensionCrashLogGroups$ == nil) { 134 | appExtensionCrashLogGroups$ = [crashLogGroupsForType(type) mutableCopy]; 135 | } 136 | groups = appExtensionCrashLogGroups$; 137 | break; 138 | case CrashLogGroupTypeService: 139 | if (serviceCrashLogGroups$ == nil) { 140 | serviceCrashLogGroups$ = [crashLogGroupsForType(type) mutableCopy]; 141 | } 142 | groups = serviceCrashLogGroups$; 143 | break; 144 | default: 145 | break; 146 | } 147 | 148 | return groups; 149 | } 150 | 151 | + (void)forgetGroups { 152 | [crashLogGroups$ release]; 153 | crashLogGroups$ = nil; 154 | [appCrashLogGroups$ release]; 155 | appCrashLogGroups$ = nil; 156 | [appExtensionCrashLogGroups$ release]; 157 | appExtensionCrashLogGroups$ = nil; 158 | [serviceCrashLogGroups$ release]; 159 | serviceCrashLogGroups$ = nil; 160 | } 161 | 162 | + (instancetype)groupWithName:(NSString *)name logDirectory:(NSString *)logDirectory { 163 | return [[[self alloc] initWithName:name logDirectory:logDirectory] autorelease]; 164 | } 165 | 166 | - (instancetype)initWithName:(NSString *)name logDirectory:(NSString *)logDirectory { 167 | self = [super init]; 168 | if (self != nil) { 169 | name_ = [name copy]; 170 | logDirectory_ = [logDirectory copy]; 171 | crashLogs_ = [[NSMutableArray alloc] init]; 172 | } 173 | return self; 174 | } 175 | 176 | - (void)dealloc { 177 | [name_ release]; 178 | [logDirectory_ release]; 179 | [crashLogs_ release]; 180 | [super dealloc]; 181 | } 182 | 183 | - (NSArray *)crashLogs { 184 | return [crashLogs_ sortedArrayUsingFunction:reverseCompareCrashLogs context:NULL]; 185 | } 186 | 187 | - (void)addCrashLog:(CrashLog *)crashLog { 188 | [crashLogs_ addObject:crashLog]; 189 | } 190 | 191 | - (BOOL)delete { 192 | // Delete contained crash logs. 193 | const NSUInteger count = [crashLogs_ count]; 194 | for (NSInteger i = (count - 1); i >= 0; --i) { 195 | CrashLog *crashLog = [crashLogs_ objectAtIndex:i]; 196 | if ([crashLog delete]) { 197 | [crashLogs_ removeObjectAtIndex:i]; 198 | } else { 199 | // Failed to delete a log file; stop and return. 200 | return NO; 201 | } 202 | } 203 | 204 | // Remove group from global arrays. 205 | // NOTE: Not all global arrays will contain the group. 206 | [crashLogGroups$ removeObject:self]; 207 | [appCrashLogGroups$ removeObject:self]; 208 | [appExtensionCrashLogGroups$ removeObject:self]; 209 | [serviceCrashLogGroups$ removeObject:self]; 210 | 211 | return YES; 212 | } 213 | 214 | // FIXME: Update "LatestCrash-*" link, if necessary. 215 | - (BOOL)deleteCrashLog:(CrashLog *)crashLog { 216 | if ([crashLogs_ containsObject:crashLog]) { 217 | [crashLog delete]; 218 | if (![[NSFileManager defaultManager] fileExistsAtPath:[crashLog filepath]]) { 219 | [crashLogs_ removeObject:crashLog]; 220 | return YES; 221 | } 222 | } 223 | return NO; 224 | } 225 | 226 | #pragma mark - Type 227 | 228 | - (CrashLogGroupType)type { 229 | CrashLogGroupType type = CrashLogGroupTypeUnknown; 230 | 231 | NSArray *crashLogs = [self crashLogs]; 232 | if ([crashLogs count] > 0) { 233 | CrashLog *crashLog = [crashLogs objectAtIndex:0]; 234 | type = (CrashLogGroupType)[crashLog type]; 235 | } 236 | 237 | return type; 238 | } 239 | 240 | @end 241 | 242 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 243 | -------------------------------------------------------------------------------- /Application/Entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | platform-application 6 | 7 | com.apple.private.security.no-container 8 | 9 | com.apple.private.skip-library-validation 10 | 11 | com.apple.springboard.launchapplications 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Application/Makefile: -------------------------------------------------------------------------------- 1 | APPLICATION_NAME = CrashReporter 2 | CrashReporter_FILES = \ 3 | $(THEOS_PROJECT_DIR)/common/crashlog_util.m \ 4 | $(THEOS_PROJECT_DIR)/common/exec_as_root.m \ 5 | ApplicationDelegate.m \ 6 | BinaryImageCell.m \ 7 | Button.m \ 8 | CrashLog.m \ 9 | CrashLogGroup.m \ 10 | ModalActionSheet.m \ 11 | PackageCache.m \ 12 | RootCell.m \ 13 | RootViewController.m \ 14 | ScriptViewController.m \ 15 | SectionHeaderView.m \ 16 | SuspectsViewController.m \ 17 | TableViewCell.m \ 18 | TableViewCellLine.m \ 19 | TableViewController.m \ 20 | UIImage+CrashReporter.m \ 21 | UITableView+CrashReporter.m \ 22 | VictimCell.m \ 23 | VictimViewController.m \ 24 | main.m \ 25 | pastie.m 26 | CrashReporter_CFLAGS = -F$(THEOS)/Frameworks -I$(THEOS_PROJECT_DIR)/Libraries 27 | CrashReporter_LDFLAGS = -F$(THEOS)/Frameworks 28 | CrashReporter_LIBRARIES = crashreport icucore packageinfo 29 | CrashReporter_FRAMEWORKS = CoreGraphics MessageUI SystemConfiguration TechSupport UIKit 30 | 31 | CrashReporter_CODESIGN_FLAGS="-SEntitlements.plist" 32 | 33 | include $(THEOS_MAKE_PATH)/common.mk 34 | include $(THEOS_MAKE_PATH)/application.mk 35 | 36 | after-clean:: 37 | - rm -rf $(THEOS_PROJECT_DIR)/Application/Common 38 | - rm -rf $(THEOS_PROJECT_DIR)/Application/common 39 | -------------------------------------------------------------------------------- /Application/ModalActionSheet.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import 13 | 14 | @class UIProgressHUD; 15 | 16 | @interface ModalActionSheet : NSObject 17 | - (void)show; 18 | - (void)hide; 19 | - (void)updateText:(NSString*)newText; 20 | @end 21 | 22 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 23 | -------------------------------------------------------------------------------- /Application/ModalActionSheet.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "ModalActionSheet.h" 13 | 14 | #include 15 | 16 | @interface UIProgressHUD : UIView 17 | - (void)setText:(id)text; 18 | - (void)showInView:(id)view; 19 | @end 20 | 21 | @implementation ModalActionSheet { 22 | UIProgressHUD *hud_; 23 | UIWindow *window_; 24 | } 25 | 26 | - (id)init { 27 | self = [super init]; 28 | if (self != nil) { 29 | 30 | UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 31 | window.backgroundColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.5]; 32 | window.windowLevel = UIWindowLevelAlert; 33 | window_ = window; 34 | 35 | UIProgressHUD *hud = [[UIProgressHUD alloc] init]; 36 | [hud showInView:window]; 37 | hud_ = hud; 38 | } 39 | return self; 40 | } 41 | 42 | - (void)dealloc { 43 | [window_ release]; 44 | [hud_ release]; 45 | [super dealloc]; 46 | } 47 | 48 | - (void)show { 49 | window_.hidden = NO; 50 | } 51 | 52 | - (void)hide { 53 | window_.hidden = YES; 54 | } 55 | 56 | - (void)updateText:(NSString *)text { 57 | [hud_ setText:text]; 58 | CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.01, false); 59 | } 60 | 61 | @end 62 | 63 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 64 | -------------------------------------------------------------------------------- /Application/PackageCache.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import 13 | 14 | @class TSPackage; 15 | 16 | @interface PackageCache : NSObject 17 | + (instancetype)sharedInstance; 18 | - (TSPackage *)packageForFile:(NSString *)filepath; 19 | @end 20 | 21 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 22 | -------------------------------------------------------------------------------- /Application/PackageCache.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "PackageCache.h" 13 | 14 | #import 15 | 16 | @implementation PackageCache { 17 | NSMutableDictionary *cache_; 18 | } 19 | 20 | + (instancetype)sharedInstance { 21 | static dispatch_once_t once; 22 | static id instance; 23 | dispatch_once(&once, ^{ 24 | instance = [[self alloc] init]; 25 | }); 26 | return instance; 27 | } 28 | 29 | - (id)init { 30 | self = [super init]; 31 | if (self != nil) { 32 | cache_ = [[NSMutableDictionary alloc] init]; 33 | 34 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning) 35 | name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; 36 | } 37 | return self; 38 | } 39 | 40 | - (void)dealloc { 41 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 42 | 43 | [cache_ release]; 44 | [super dealloc]; 45 | } 46 | 47 | - (void)didReceiveMemoryWarning { 48 | [cache_ removeAllObjects]; 49 | } 50 | 51 | - (TSPackage *)packageForFile:(NSString *)filepath { 52 | TSPackage *package = [cache_ objectForKey:filepath]; 53 | if (package == nil) { 54 | package = [TSPackage packageForFile:filepath]; 55 | if (package != nil) { 56 | [cache_ setObject:package forKey:filepath]; 57 | } 58 | } 59 | return package; 60 | } 61 | 62 | @end 63 | 64 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 65 | -------------------------------------------------------------------------------- /Application/RootCell.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "TableViewCell.h" 13 | 14 | @interface RootCell : TableViewCell 15 | @property(nonatomic, assign, getter = isNewer) BOOL newer; 16 | @property(nonatomic, assign, getter = isRecent) BOOL recent; 17 | - (void)setLatestCrashDate:(NSString *)date; 18 | @end 19 | 20 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 21 | -------------------------------------------------------------------------------- /Application/RootCell.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "RootCell.h" 13 | 14 | #import "CrashLog.h" 15 | #import "CrashLogGroup.h" 16 | #import "TableViewCellLine.h" 17 | #include "font-awesome.h" 18 | 19 | #define kColorCrashDate [UIColor grayColor] 20 | #define kColorNewer [UIColor lightGrayColor] 21 | #define kColorRecent [UIColor redColor] 22 | 23 | static const CGFloat kFontSizeCrashDate = 12.0; 24 | 25 | @implementation RootCell { 26 | TableViewCellLine *latestCrashDateLine_; 27 | UIImageView *latestCrashDateImageView_; 28 | } 29 | 30 | @synthesize newer = newer_; 31 | @synthesize recent = recent_; 32 | 33 | #pragma mark - Overrides (TableViewCell) 34 | 35 | + (CGFloat)cellHeight { 36 | // FIXME: The (+ x.0) values added to the font sizes are only valid for the 37 | // current font sizes (18.0 and 12.0). Determine proper calculation. 38 | return [super cellHeight] + kFontSizeCrashDate; 39 | } 40 | 41 | - (id)initWithReuseIdentifier:(NSString *)reuseIdentifier { 42 | self = [super initWithReuseIdentifier:reuseIdentifier]; 43 | if (self != nil) { 44 | self.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 45 | self.detailTextLabel.backgroundColor = [UIColor clearColor]; 46 | 47 | latestCrashDateLine_ = [[self addLine] retain]; 48 | latestCrashDateLine_.iconLabel.text = @kFontAwesomeClockO; 49 | } 50 | return self; 51 | } 52 | 53 | - (void)dealloc { 54 | [latestCrashDateLine_ release]; 55 | [latestCrashDateImageView_ release]; 56 | [super dealloc]; 57 | } 58 | 59 | - (void)configureWithObject:(id)object { 60 | NSAssert([object isKindOfClass:[CrashLogGroup class]], @"ERROR: Incorrect class type: Expected CrashLogGroup, received %@.", [object class]); 61 | 62 | CrashLogGroup *group = object; 63 | NSArray *crashLogs = [group crashLogs]; 64 | CrashLog *crashLog = [crashLogs objectAtIndex:0]; 65 | 66 | // Name of crashed process. 67 | [self setName:group.name]; 68 | 69 | // Date of latest crash. 70 | NSString *string = nil; 71 | BOOL isRecent = NO; 72 | NSDate *logDate = [crashLog logDate]; 73 | NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:logDate]; 74 | if (interval < 86400.0) { 75 | if (interval < 3600.0) { 76 | string = NSLocalizedString(@"CRASHED_LESS_THAN_HOUR", nil); 77 | } else { 78 | string = [NSString stringWithFormat:NSLocalizedString(@"CRASHED_LESS_THAN_HOURS", nil), (unsigned)ceil(interval / 3600.0)]; 79 | } 80 | isRecent = YES; 81 | } else { 82 | string = [[[self class] dateFormatter] stringFromDate:logDate]; 83 | } 84 | [self setLatestCrashDate:string]; 85 | [self setRecent:isRecent]; 86 | 87 | // Number of unviewed logs and total logs. 88 | const unsigned long totalCount = [crashLogs count]; 89 | unsigned long unviewedCount = 0; 90 | for (CrashLog *crashLog in crashLogs) { 91 | if (![crashLog isViewed]) { 92 | ++unviewedCount; 93 | } 94 | } 95 | self.detailTextLabel.text = [NSString stringWithFormat:@"%lu/%lu", unviewedCount, totalCount]; 96 | } 97 | 98 | #pragma mark - Properties 99 | 100 | - (void)setLatestCrashDate:(NSString *)latestCrashDate { 101 | if ([latestCrashDate length] != 0) { 102 | latestCrashDate = [NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"LATEST", nil), latestCrashDate]; 103 | } 104 | [self setText:latestCrashDate forLabel:latestCrashDateLine_.label]; 105 | } 106 | 107 | - (void)setNewer:(BOOL)newer { 108 | if (newer_ != newer) { 109 | newer_ = newer; 110 | [latestCrashDateLine_.label setTextColor:(newer_ ? kColorNewer : kColorCrashDate)]; 111 | } 112 | } 113 | 114 | - (void)setRecent:(BOOL)recent { 115 | if (recent_ != recent) { 116 | recent_ = recent; 117 | [latestCrashDateLine_.label setTextColor:(recent_ ? kColorRecent : kColorCrashDate)]; 118 | } 119 | } 120 | 121 | @end 122 | 123 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 124 | -------------------------------------------------------------------------------- /Application/RootViewController.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "TableViewController.h" 13 | 14 | @interface RootViewController : TableViewController 15 | @end 16 | 17 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 18 | -------------------------------------------------------------------------------- /Application/ScriptViewController.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import 13 | 14 | @interface ScriptViewController : TSHTMLViewController 15 | - (instancetype)initWithString:(NSString *)string; 16 | - (instancetype)initWithURL:(NSURL *)url; 17 | @end 18 | 19 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 20 | -------------------------------------------------------------------------------- /Application/ScriptViewController.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "ScriptViewController.h" 13 | 14 | #import 15 | #import "Button.h" 16 | 17 | @interface ScriptViewController () 18 | @end 19 | 20 | @implementation ScriptViewController { 21 | Button *executeButton_; 22 | BOOL hasShownExplanation_; 23 | 24 | NSString *script_; 25 | NSURL *scriptURL_; 26 | NSURLConnection *connection_; 27 | NSMutableData *data_; 28 | 29 | NSArray *instructions_; 30 | } 31 | 32 | static void init(ScriptViewController *self) { 33 | self.title = NSLocalizedString(@"SCRIPT", nil); 34 | 35 | UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelButtonTapped)]; 36 | [self.navigationItem setLeftBarButtonItem:item]; 37 | [item release]; 38 | } 39 | 40 | - (instancetype)initWithString:(NSString *)string { 41 | self = [super initWithHTMLContent:string]; 42 | if (self != nil) { 43 | init(self); 44 | script_ = [string copy]; 45 | instructions_ = [[TSInstruction instructionsWithString:script_] retain]; 46 | } 47 | return self; 48 | } 49 | 50 | - (instancetype)initWithURL:(NSURL *)url { 51 | self = [super initWithHTMLContent:@""]; 52 | if (self != nil) { 53 | init(self); 54 | scriptURL_ = [url copy]; 55 | } 56 | return self; 57 | } 58 | 59 | - (void)loadView { 60 | [super loadView]; 61 | 62 | UIScreen *mainScreen = [UIScreen mainScreen]; 63 | const CGRect screenBounds = [mainScreen bounds]; 64 | const CGFloat scale = [mainScreen scale]; 65 | const CGFloat buttonViewHeight = 44.0 + 20.0; 66 | const CGFloat webViewHeight = (screenBounds.size.height - buttonViewHeight); 67 | 68 | [self.webView setFrame:CGRectMake(0.0, 0.0, screenBounds.size.width, webViewHeight)]; 69 | 70 | UIView *buttonView = [[UIView alloc] initWithFrame:CGRectMake(0.0, webViewHeight, screenBounds.size.width, buttonViewHeight)]; 71 | buttonView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin; 72 | buttonView.backgroundColor = [UIColor colorWithRed:(247.0 / 255.0) green:(247.0 / 255.0) blue:(247.0 / 255.0) alpha:1.0]; 73 | 74 | UIView *borderView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, screenBounds.size.width, (1.0 / scale))]; 75 | borderView.backgroundColor = [UIColor colorWithRed:(178.0 / 255.0) green:(178.0 / 255.0) blue:(178.0 / 255.0) alpha:1.0]; 76 | [buttonView addSubview:borderView]; 77 | [borderView release]; 78 | 79 | Button *button; 80 | button = [Button button]; 81 | [button setEnabled:(instructions_ != nil)]; 82 | [button setFrame:CGRectMake(10.0, 10.0, screenBounds.size.width - 20.0, 44.0)]; 83 | [button setTitle:NSLocalizedString(@"SCRIPT_EXECUTE", nil) forState:UIControlStateNormal]; 84 | [button addTarget:self action:@selector(executeButtonTapped) forControlEvents:UIControlEventTouchUpInside]; 85 | [buttonView addSubview:button]; 86 | executeButton_ = [button retain]; 87 | 88 | [self.view addSubview:buttonView]; 89 | [buttonView release]; 90 | } 91 | 92 | - (void)dealloc { 93 | [connection_ release]; 94 | [data_ release]; 95 | [instructions_ release]; 96 | [script_ release]; 97 | [scriptURL_ release]; 98 | [executeButton_ release]; 99 | [super dealloc]; 100 | } 101 | 102 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { 103 | return interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown; 104 | } 105 | 106 | - (void)viewDidAppear:(BOOL)animated { 107 | if (script_ == nil) { 108 | if (scriptURL_ != nil) { 109 | // NOTE: Performing synchronously for simplicity; should perform async in 110 | // real application. 111 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:scriptURL_ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60.0]; 112 | connection_ = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]; 113 | [request release]; 114 | 115 | [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES]; 116 | } 117 | } else { 118 | [self showExplanation]; 119 | } 120 | } 121 | 122 | #pragma mark - Actions 123 | 124 | - (void)cancelButtonTapped { 125 | [self.navigationController popViewControllerAnimated:YES]; 126 | } 127 | 128 | - (void)executeButtonTapped { 129 | if (instructions_ != nil) { 130 | // Process include commands. 131 | Class $TSIncludeInstruction = [TSIncludeInstruction class]; 132 | for (TSInstruction *instruction in instructions_) { 133 | if ([instruction isKindOfClass:$TSIncludeInstruction]) { 134 | (void)[(TSIncludeInstruction *)instruction content]; 135 | } 136 | } 137 | 138 | // Present results in contact form. 139 | NSString *detailFormat = 140 | @"Additional information from the user:\n" 141 | "-------------------------------------------\n" 142 | "%@\n" 143 | "-------------------------------------------"; 144 | 145 | TSContactViewController *controller = [[TSContactViewController alloc] initWithPackage:nil instructions:instructions_]; 146 | [controller setByline:[NSString stringWithFormat:@"/* Generated by CrashReporter (v%@) - cydia://package/crash-reporter */", 147 | [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]]]; 148 | [controller setTitle:@"Results Form"]; 149 | [controller setSubject:@"CrashReporter: Script Results"]; 150 | [controller setDetailEntryPlaceholderText:@"Enter any additional information here."]; 151 | [controller setMessageBody:@"Attached are the results of the script that was provided to this user."]; 152 | [controller setDetailFormat:detailFormat]; 153 | [controller setRequiresDetailsFromUser:NO]; 154 | [self.navigationController pushViewController:controller animated:YES]; 155 | [controller release]; 156 | } 157 | } 158 | 159 | #pragma mark - Other 160 | 161 | - (void)showExplanation { 162 | if (!hasShownExplanation_) { 163 | NSString *title = NSLocalizedString(@"SCRIPT_INTRO_TITLE", nil); 164 | NSString *message = [NSString stringWithFormat:NSLocalizedString(@"SCRIPT_INTRO_MESSAGE", nil), 165 | NSLocalizedString(@"SCRIPT_EXECUTE", nil), NSLocalizedString(@"CANCEL", nil)]; 166 | UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title message:message delegate:self 167 | cancelButtonTitle:nil 168 | otherButtonTitles:NSLocalizedString(@"OK", nil), nil]; 169 | [alertView show]; 170 | [alertView release]; 171 | hasShownExplanation_ = YES; 172 | } 173 | } 174 | 175 | - (void)showInvalid { 176 | NSString *title = [NSString stringWithFormat:@"\u2639 %@ \u2639", NSLocalizedString(@"SCRIPT_INVALID_TITLE", nil)]; 177 | NSString *message = NSLocalizedString(@"SCRIPT_INVALID_MESSAGE", nil); 178 | UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title message:message delegate:nil 179 | cancelButtonTitle:nil 180 | otherButtonTitles:NSLocalizedString(@"OK", nil), nil]; 181 | [alertView show]; 182 | [alertView release]; 183 | } 184 | 185 | - (void)showWarning { 186 | NSString *title = [NSString stringWithFormat:@"\u26A0 %@ \u26A0", NSLocalizedString(@"SCRIPT_CAUTION_TITLE", nil)]; 187 | NSString *message = NSLocalizedString(@"SCRIPT_CAUTION_MESSAGE", nil); 188 | UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title message:message delegate:nil 189 | cancelButtonTitle:nil 190 | otherButtonTitles:NSLocalizedString(@"OK", nil), nil]; 191 | [alertView show]; 192 | [alertView release]; 193 | } 194 | 195 | #pragma mark - NSURLConnectionDelegate 196 | 197 | - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { 198 | const NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode]; 199 | if (statusCode == 200) { 200 | data_ = [[NSMutableData alloc] init]; 201 | } else { 202 | // NOTE: Only a warning as the response may be a redirect (which 203 | // would lead to this delegate method getting called again). 204 | NSLog(@"WARNING: Received response: %@", response); 205 | } 206 | } 207 | 208 | - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { 209 | if (data_ != nil) { 210 | [data_ appendData:data]; 211 | } 212 | } 213 | 214 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection { 215 | if (data_ != nil) { 216 | NSString *content = [[NSString alloc] initWithData:data_ encoding:NSUTF8StringEncoding]; 217 | if (content != nil) { 218 | [script_ release]; 219 | script_ = content; 220 | [instructions_ release]; 221 | instructions_ = [[TSInstruction instructionsWithString:script_] retain]; 222 | if (instructions_ != nil) { 223 | [executeButton_ setEnabled:YES]; 224 | } 225 | [self setContent:script_]; 226 | [self showExplanation]; 227 | } else { 228 | NSLog(@"ERROR: Unable to interpret downloaded content as a UTF8 string."); 229 | } 230 | 231 | [data_ release]; 232 | data_ = nil; 233 | [connection_ release]; 234 | connection_ = nil; 235 | } 236 | 237 | [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; 238 | } 239 | 240 | - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { 241 | NSLog(@"ERROR: Connection failed: %@ %@", 242 | [error localizedDescription], 243 | [[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]); 244 | [data_ release]; 245 | data_ = nil; 246 | [connection_ release]; 247 | connection_ = nil; 248 | 249 | [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; 250 | } 251 | 252 | #pragma mark - UIAlertViewDelegate 253 | 254 | - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { 255 | if (instructions_ == nil) { 256 | [self showInvalid]; 257 | } else { 258 | BOOL containsCommand = NO; 259 | Class $TSIncludeInstruction = [TSIncludeInstruction class]; 260 | for (TSInstruction *instruction in instructions_) { 261 | if ([instruction isKindOfClass:$TSIncludeInstruction]) { 262 | if ([(TSIncludeInstruction *)instruction includeType] == TSIncludeInstructionTypeCommand) { 263 | containsCommand = YES; 264 | break; 265 | } 266 | } 267 | } 268 | if (containsCommand) { 269 | [self showWarning]; 270 | } 271 | } 272 | } 273 | 274 | @end 275 | 276 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 277 | -------------------------------------------------------------------------------- /Application/SectionHeaderView.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | @interface SectionHeaderView : UIView 13 | @property (nonatomic, readonly) UILabel *textLabel; 14 | @property (nonatomic, readonly) UIButton *helpButton; 15 | + (CGFloat)defaultHeight; 16 | - (instancetype)initWithDefaultSize; 17 | @end 18 | 19 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 20 | -------------------------------------------------------------------------------- /Application/SectionHeaderView.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "SectionHeaderView.h" 13 | 14 | static const CGFloat kSectionHeaderHeight = 36.0; 15 | 16 | @implementation SectionHeaderView 17 | 18 | @synthesize textLabel = textLabel_; 19 | @synthesize helpButton = helpButton_; 20 | 21 | + (CGFloat)defaultHeight { 22 | return kSectionHeaderHeight; 23 | } 24 | 25 | - (instancetype)initWithDefaultSize { 26 | const CGRect screenBounds = [[UIScreen mainScreen] bounds]; 27 | const CGRect frame = CGRectMake(0.0, 0.0, screenBounds.size.width, kSectionHeaderHeight); 28 | self = [super initWithFrame:frame]; 29 | if (self != nil) { 30 | UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero]; 31 | label.autoresizingMask = UIViewAutoresizingFlexibleRightMargin; 32 | label.backgroundColor = [UIColor clearColor]; 33 | label.textColor = [UIColor colorWithRed:(109.0 / 255.0) green:(109.0 / 255.0) blue:(114.0 / 255.0) alpha:1.0]; 34 | label.font = [UIFont boldSystemFontOfSize:15.0]; 35 | 36 | UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 37 | [button setImage:[UIImage imageNamed:@"help_button"] forState:UIControlStateNormal]; 38 | 39 | self.autoresizingMask = UIViewAutoresizingFlexibleWidth; 40 | [self addSubview:label]; 41 | [self addSubview:button]; 42 | 43 | textLabel_ = label; 44 | helpButton_ = [button retain]; 45 | } 46 | return self; 47 | } 48 | 49 | - (void)layoutSubviews { 50 | [super layoutSubviews]; 51 | 52 | UIScreen *mainScreen = [UIScreen mainScreen]; 53 | const CGRect screenBounds = [mainScreen bounds]; 54 | 55 | UIButton *button = self.helpButton; 56 | const CGSize buttonSize = CGSizeMake(kSectionHeaderHeight + 2.0, kSectionHeaderHeight); 57 | const CGRect buttonFrame = CGRectMake(screenBounds.size.width - buttonSize.width, 0.0, buttonSize.width, buttonSize.height); 58 | button.frame = buttonFrame; 59 | button.imageEdgeInsets = UIEdgeInsetsMake(kSectionHeaderHeight - 32.0, 0.0, 0.0, 0.0); 60 | 61 | UILabel *label = self.textLabel; 62 | const CGFloat labelHeight = textLabel_.font.pointSize + 4.0; 63 | const CGRect labelFrame = CGRectMake(10.0, kSectionHeaderHeight - labelHeight - 3.0, buttonFrame.origin.x, labelHeight); 64 | label.frame = labelFrame; 65 | } 66 | 67 | @end 68 | 69 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 70 | -------------------------------------------------------------------------------- /Application/SuspectsViewController.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "TableViewController.h" 13 | 14 | @class CrashLog; 15 | 16 | @interface SuspectsViewController : TableViewController 17 | - (id)initWithCrashLog:(CrashLog *)crashLog; 18 | @end 19 | 20 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 21 | -------------------------------------------------------------------------------- /Application/TableViewCell.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import 13 | 14 | @class TableViewCellLine; 15 | 16 | @interface TableViewCell : UITableViewCell 17 | @property(nonatomic, readonly) UILabel *nameLabel; 18 | @property(nonatomic, retain) NSDate *referenceDate; 19 | @property(nonatomic, assign) BOOL showsTopSeparator; 20 | @property(nonatomic, assign, getter = isViewed) BOOL viewed; 21 | + (CGFloat)cellHeight; 22 | + (NSDateFormatter *)dateFormatter; 23 | - (id)initWithReuseIdentifier:(NSString *)reuseIdentifier; 24 | - (void)configureWithObject:(id)object; 25 | - (void)setName:(NSString *)name; 26 | - (TableViewCellLine *)addLine; 27 | - (void)setText:(NSString *)text forLabel:(UILabel *)label; 28 | @end 29 | 30 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 31 | -------------------------------------------------------------------------------- /Application/TableViewCell.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "TableViewCell.h" 13 | 14 | #import "TableViewCellLine.h" 15 | 16 | #define kColorName [UIColor blackColor] 17 | #define kColorViewed [UIColor grayColor] 18 | 19 | static const UIEdgeInsets kContentInset = (UIEdgeInsets){10.0, 15.0, 10.0, 15.0}; 20 | static const CGFloat kFontSizeName = 18.0; 21 | 22 | @implementation TableViewCell { 23 | UILabel *nameLabel_; 24 | NSMutableArray *lines_; 25 | UIView *topSeparatorView_; 26 | UIView *bottomSeparatorView_; 27 | } 28 | 29 | @synthesize nameLabel = nameLabel_; 30 | @synthesize referenceDate = referenceDate_; 31 | @synthesize viewed = viewed_; 32 | 33 | @dynamic showsTopSeparator; 34 | 35 | + (CGFloat)cellHeight { 36 | return kContentInset.top + kContentInset.bottom + (kFontSizeName + 4.0); 37 | } 38 | 39 | + (NSDateFormatter *)dateFormatter { 40 | static NSDateFormatter *dateFormatter = nil; 41 | if (dateFormatter == nil ) { 42 | dateFormatter = [[NSDateFormatter alloc] init]; 43 | [dateFormatter setTimeStyle:NSDateFormatterMediumStyle]; 44 | [dateFormatter setDateStyle:NSDateFormatterShortStyle]; 45 | } 46 | return dateFormatter; 47 | } 48 | 49 | - (id)initWithReuseIdentifier:(NSString *)reuseIdentifier { 50 | self = [super initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:reuseIdentifier]; 51 | if (self != nil) { 52 | // NOTE: A background view is necessary for older versions of iOS. 53 | // NOTE: Confirmed necessary for iOS 4.1.2, not necessary for iOS 8.4. 54 | // Other versions unconfirmed. 55 | UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectZero]; 56 | backgroundView.backgroundColor = [UIColor whiteColor]; 57 | self.backgroundView = backgroundView; 58 | [backgroundView release]; 59 | 60 | UIFont *font = [UIFont systemFontOfSize:kFontSizeName]; 61 | UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero]; 62 | label.autoresizingMask = UIViewAutoresizingFlexibleWidth; 63 | label.backgroundColor = [UIColor clearColor]; 64 | label.font = font; 65 | label.textColor = kColorName; 66 | [self.contentView addSubview:label]; 67 | nameLabel_ = label; 68 | 69 | lines_ = [[NSMutableArray alloc] init]; 70 | 71 | // Provide our own separator views for more control. 72 | UIView *separatorView; 73 | 74 | separatorView = [[UIView alloc] initWithFrame:CGRectZero]; 75 | separatorView.backgroundColor = [UIColor colorWithRed:(200.0 / 255.0) green:(199.0 / 255.0) blue:(204.0 / 255.0) alpha:1.0]; 76 | separatorView.hidden = YES; 77 | [self addSubview:separatorView]; 78 | topSeparatorView_ = separatorView; 79 | 80 | separatorView = [[UIView alloc] initWithFrame:CGRectZero]; 81 | separatorView.backgroundColor = [UIColor colorWithRed:(200.0 / 255.0) green:(199.0 / 255.0) blue:(204.0 / 255.0) alpha:1.0]; 82 | [self addSubview:separatorView]; 83 | bottomSeparatorView_ = separatorView; 84 | } 85 | return self; 86 | } 87 | 88 | - (void)dealloc { 89 | [referenceDate_ release]; 90 | [nameLabel_ release]; 91 | [lines_ release]; 92 | [topSeparatorView_ release]; 93 | [bottomSeparatorView_ release]; 94 | [super dealloc]; 95 | } 96 | 97 | - (TableViewCellLine *)addLine { 98 | TableViewCellLine *line = [[TableViewCellLine alloc] init]; 99 | [self addSubview:line]; 100 | [lines_ addObject:line]; 101 | return [line autorelease]; 102 | } 103 | 104 | #pragma mark - View (Layout) 105 | 106 | - (void)layoutSubviews { 107 | [super layoutSubviews]; 108 | 109 | const CGSize contentSize = self.contentView.bounds.size; 110 | const CGFloat maxWidth = contentSize.width - kContentInset.left - kContentInset.right; 111 | CGSize maxSize = CGSizeMake(maxWidth, 10000.0); 112 | 113 | // Name. 114 | CGRect nameLabelFrame = CGRectZero; 115 | if ([[nameLabel_ text] length] > 0) { 116 | nameLabelFrame = [nameLabel_ frame]; 117 | nameLabelFrame.origin.x = kContentInset.left; 118 | nameLabelFrame.origin.y = kContentInset.top; 119 | nameLabelFrame.size = [nameLabel_ sizeThatFits:maxSize]; 120 | } 121 | [nameLabel_ setFrame:nameLabelFrame]; 122 | 123 | // Lines. 124 | const CGFloat lineHeight = [TableViewCellLine defaultHeight]; 125 | 126 | CGRect lineFrame = CGRectMake( 127 | kContentInset.left + 2.0, 128 | nameLabelFrame.origin.y + nameLabelFrame.size.height, 129 | maxWidth - 2.0, 130 | lineHeight 131 | ); 132 | 133 | for (TableViewCellLine *line in lines_) { 134 | if ([[line.label text] length] > 0) { 135 | line.frame = lineFrame; 136 | lineFrame.origin.y += lineHeight; 137 | } else { 138 | line.frame = CGRectZero; 139 | } 140 | } 141 | 142 | // Separators. 143 | const CGFloat scale = [[UIScreen mainScreen] scale]; 144 | const CGFloat separatorHeight = 1.0 / scale; 145 | const CGSize size = self.bounds.size; 146 | [topSeparatorView_ setFrame:CGRectMake(0.0, 0.0, size.width, separatorHeight)]; 147 | [bottomSeparatorView_ setFrame:CGRectMake(0.0, size.height - separatorHeight, size.width, separatorHeight)]; 148 | } 149 | 150 | #pragma mark - Configuration 151 | 152 | - (void)configureWithObject:(id)object { 153 | } 154 | 155 | - (void)setText:(NSString *)text forLabel:(UILabel *)label { 156 | const NSUInteger oldLength = [[label text] length]; 157 | const NSUInteger newLength = [text length]; 158 | 159 | [label setText:text]; 160 | 161 | if (((oldLength == 0) && (newLength != 0)) || ((oldLength != 0) && (newLength == 0))) { 162 | [self setNeedsLayout]; 163 | } 164 | } 165 | 166 | - (void)setName:(NSString *)name { 167 | [self setText:name forLabel:nameLabel_]; 168 | } 169 | 170 | #pragma mark - Properties 171 | 172 | - (BOOL)showsTopSeparator { 173 | return topSeparatorView_.hidden; 174 | } 175 | 176 | - (void)setShowsTopSeparator:(BOOL)shows { 177 | topSeparatorView_.hidden = !shows; 178 | } 179 | 180 | - (void)setViewed:(BOOL)viewed { 181 | if (viewed_ != viewed) { 182 | viewed_ = viewed; 183 | nameLabel_.textColor = viewed ? kColorViewed : kColorName; 184 | } 185 | } 186 | 187 | @end 188 | 189 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 190 | -------------------------------------------------------------------------------- /Application/TableViewCellLine.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import 13 | 14 | @interface TableViewCellLine : UIView 15 | @property(nonatomic, readonly) UILabel *iconLabel; 16 | @property(nonatomic, readonly) UILabel *label; 17 | + (CGFloat)defaultHeight; 18 | @end 19 | 20 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 21 | -------------------------------------------------------------------------------- /Application/TableViewCellLine.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "TableViewCellLine.h" 13 | 14 | #define kColorIconLabel [UIColor blackColor] 15 | #define kColorLabel [UIColor grayColor] 16 | 17 | static const CGFloat kFontSizeIconLabel = 11.0; 18 | static const CGFloat kFontSizeLabel = 12.0; 19 | 20 | @implementation TableViewCellLine 21 | 22 | @synthesize iconLabel = iconLabel_; 23 | @synthesize label = label_; 24 | 25 | + (CGFloat)defaultHeight { 26 | return kFontSizeLabel + 4.0; 27 | } 28 | 29 | - (instancetype)init { 30 | self = [super initWithFrame:CGRectZero]; 31 | if (self != nil) { 32 | self.clipsToBounds = YES; 33 | 34 | UILabel *label; 35 | 36 | label = [[UILabel alloc] initWithFrame:CGRectZero]; 37 | label.backgroundColor = [UIColor clearColor]; 38 | label.font = [UIFont fontWithName:@"FontAwesome" size:kFontSizeIconLabel]; 39 | if (IOS_LT(5_0)) { 40 | // NOTE: For reasons unknown, center alignment on iOS 4 causes 41 | // certain icon font images to be cut off on the right side. 42 | label.textAlignment = NSTextAlignmentLeft; 43 | } else { 44 | label.textAlignment = NSTextAlignmentCenter; 45 | } 46 | label.textColor = kColorIconLabel; 47 | [self addSubview:label]; 48 | iconLabel_ = label; 49 | 50 | label = [[UILabel alloc] initWithFrame:CGRectZero]; 51 | label.autoresizingMask = UIViewAutoresizingFlexibleWidth; 52 | label.backgroundColor = [UIColor clearColor]; 53 | label.textColor = kColorLabel; 54 | label.font = [UIFont systemFontOfSize:kFontSizeLabel]; 55 | [self addSubview:label]; 56 | label_ = label; 57 | } 58 | return self; 59 | } 60 | 61 | - (void)dealloc { 62 | [iconLabel_ release]; 63 | [label_ release]; 64 | [super dealloc]; 65 | } 66 | 67 | - (void)layoutSubviews { 68 | [super layoutSubviews]; 69 | 70 | const CGSize size = self.bounds.size; 71 | 72 | UILabel *iconLabel = self.iconLabel; 73 | CGFloat iconLabelWidth; 74 | CGFloat labelX; 75 | if ([iconLabel.text length] > 0) { 76 | iconLabelWidth = kFontSizeIconLabel; 77 | labelX = iconLabelWidth + 3.0; 78 | } else { 79 | iconLabelWidth = 0.0; 80 | labelX = 0.0; 81 | } 82 | iconLabel.frame = CGRectMake(0.0, 0.0, iconLabelWidth, size.height); 83 | self.label.frame = CGRectMake(labelX, 0.0, size.width - labelX, size.height); 84 | } 85 | 86 | @end 87 | 88 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 89 | -------------------------------------------------------------------------------- /Application/TableViewController.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import 13 | 14 | @interface TableViewController : UIViewController 15 | @property (nonatomic, readonly) UITableView *tableView; 16 | @property (nonatomic, assign) BOOL supportsRefreshControl; 17 | - (void)presentHelpForName:(NSString *)name; 18 | - (void)refresh:(NSNotification *)notification; 19 | @end 20 | 21 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 22 | -------------------------------------------------------------------------------- /Application/TableViewController.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "TableViewController.h" 13 | 14 | #import 15 | #import "SectionHeaderView.h" 16 | #import "TableViewCell.h" 17 | 18 | extern NSString * const kNotificationCrashLogsChanged; 19 | 20 | @interface TableViewController () 21 | @property (nonatomic, retain) UITableView *tableView; 22 | @end 23 | 24 | @implementation TableViewController 25 | 26 | @synthesize tableView = tableView_; 27 | 28 | + (Class)cellClass { 29 | return nil; 30 | } 31 | 32 | - (void)dealloc { 33 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 34 | [tableView_ release]; 35 | [super dealloc]; 36 | } 37 | 38 | - (instancetype)init { 39 | self = [super init]; 40 | if (self != nil) { 41 | // Set title for back button. 42 | NSString *title = NSLocalizedString(@"BACK", nil); 43 | UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithTitle:title style:UIBarButtonItemStylePlain 44 | target:nil action:NULL]; 45 | [[self navigationItem] setBackBarButtonItem:buttonItem]; 46 | [buttonItem release]; 47 | } 48 | return self; 49 | } 50 | 51 | - (void)loadView { 52 | UIScreen *mainScreen = [UIScreen mainScreen]; 53 | const CGRect screenBounds = [mainScreen bounds]; 54 | 55 | // Add a table view. 56 | UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectMake(0.0, 0.0, screenBounds.size.width, screenBounds.size.height)]; 57 | //tableView.allowsSelectionDuringEditing = YES; 58 | tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 59 | tableView.backgroundColor = [UIColor colorWithRed:(239.0 / 255.0) green:(239.0 / 255.0) blue:(239.0 / 255.0) alpha:1.0]; 60 | tableView.dataSource = self; 61 | tableView.delegate = self; 62 | tableView.separatorStyle = UITableViewCellSeparatorStyleNone; 63 | self.tableView = tableView; 64 | 65 | // Add footer so that separators are not shown for "empty" cells. 66 | UIView *footerView = [[UIView alloc] initWithFrame:CGRectZero]; 67 | [tableView setTableFooterView:footerView]; 68 | [footerView release]; 69 | 70 | UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, screenBounds.size.width, screenBounds.size.height)]; 71 | view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 72 | view.backgroundColor = [UIColor whiteColor]; 73 | [view addSubview:tableView]; 74 | self.view = view; 75 | 76 | [tableView release]; 77 | [view release]; 78 | } 79 | 80 | - (void)viewDidLoad { 81 | if (IOS_GTE(6_0)) { 82 | if (self.supportsRefreshControl) { 83 | // Add a refresh control. 84 | UITableView *tableView = [self tableView]; 85 | tableView.alwaysBounceVertical = YES; 86 | UIRefreshControl *refreshControl = [[NSClassFromString(@"UIRefreshControl") alloc] init]; 87 | [refreshControl addTarget:self action:@selector(refresh:) forControlEvents:UIControlEventValueChanged]; 88 | [tableView addSubview:refreshControl]; 89 | [refreshControl release]; 90 | 91 | // Listen for changes to crash log files. 92 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refresh:) name:kNotificationCrashLogsChanged object:nil]; 93 | } 94 | } 95 | } 96 | 97 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { 98 | return interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown; 99 | } 100 | 101 | #pragma mark - Actions 102 | 103 | - (void)helpButtonTapped:(UIButton *)button { 104 | [self presentHelpForSection:button.tag]; 105 | } 106 | 107 | - (void)refresh:(id)sender { 108 | [self.tableView reloadData]; 109 | 110 | if ([sender isKindOfClass:NSClassFromString(@"UIRefreshControl")]) { 111 | [sender endRefreshing]; 112 | } 113 | } 114 | 115 | #pragma mark - Help 116 | 117 | - (void)presentHelpForName:(NSString *)name { 118 | NSURL *url = [[NSBundle mainBundle] URLForResource:name withExtension:@"html" subdirectory:@"Documentation"]; 119 | if (url != nil) { 120 | TSHTMLViewController *controller = [[TSHTMLViewController alloc] initWithURL:url]; 121 | controller.title = NSLocalizedString(name, nil); 122 | [self.navigationController pushViewController:controller animated:YES]; 123 | [controller release]; 124 | } else { 125 | NSLog(@"ERROR: Unable to obtain URL for help file \"%@\".", name); 126 | } 127 | } 128 | 129 | - (void)presentHelpForSection:(NSInteger)section { 130 | NSString *name = [self titleForHeaderInSection:section]; 131 | [self presentHelpForName:name]; 132 | } 133 | 134 | #pragma mark - Other 135 | 136 | - (NSArray *)arrayForSection:(NSInteger)section { 137 | return nil; 138 | } 139 | 140 | - (NSDate *)referenceDate { 141 | return nil; 142 | } 143 | 144 | - (NSString *)titleForEmptyCell { 145 | return @"NONE"; 146 | } 147 | 148 | - (NSString *)titleForHeaderInSection:(NSInteger)section { 149 | return nil; 150 | } 151 | 152 | #pragma mark - Delegate (UITableViewDataSource) 153 | 154 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 155 | NSArray *array = [self arrayForSection:section]; 156 | return [array count] ?: 1; 157 | } 158 | 159 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 160 | NSArray *array = [self arrayForSection:indexPath.section]; 161 | if ([array count] > 0) { 162 | Class klass = [[self class] cellClass]; 163 | NSString *reuseIdentifier = NSStringFromClass(klass); 164 | TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier]; 165 | if (cell == nil) { 166 | cell = [[[klass alloc] initWithReuseIdentifier:reuseIdentifier] autorelease]; 167 | } 168 | cell.referenceDate = [self referenceDate]; 169 | cell.showsTopSeparator = (indexPath.row == 0); 170 | [cell configureWithObject:[array objectAtIndex:indexPath.row]]; 171 | return cell; 172 | } else { 173 | NSString * const reuseIdentifier = @"EmptyCell"; 174 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier]; 175 | if (cell == nil) { 176 | cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier] autorelease]; 177 | cell.backgroundColor = [UIColor clearColor]; 178 | cell.selectionStyle = UITableViewCellSelectionStyleNone; 179 | 180 | UILabel *label = cell.textLabel; 181 | label.font = [UIFont boldSystemFontOfSize:15.0]; 182 | label.textColor = [UIColor colorWithRed:(109.0 / 255.0) green:(109.0 / 255.0) blue:(114.0 / 255.0) alpha:1.0]; 183 | label.text = NSLocalizedString([self titleForEmptyCell], nil); 184 | } 185 | return cell; 186 | } 187 | } 188 | 189 | #pragma mark - Delegate (UITableViewDelegate) 190 | 191 | - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { 192 | SectionHeaderView *headerView = [[SectionHeaderView alloc] initWithDefaultSize]; 193 | 194 | NSString *key = [self titleForHeaderInSection:section]; 195 | headerView.textLabel.text = NSLocalizedString(key, nil); 196 | 197 | UIButton *button = headerView.helpButton; 198 | button.tag = section; 199 | [button addTarget:self action:@selector(helpButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; 200 | 201 | return [headerView autorelease]; 202 | } 203 | 204 | - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { 205 | UIView *view = [[UIView alloc] initWithFrame:CGRectZero]; 206 | return [view autorelease]; 207 | } 208 | 209 | - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { 210 | return [SectionHeaderView defaultHeight]; 211 | } 212 | 213 | - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { 214 | NSInteger lastSection = [tableView numberOfSections] - 1; 215 | return (section != lastSection) ? 10.0 : 0.0; 216 | } 217 | 218 | - (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath { 219 | NSArray *array = [self arrayForSection:indexPath.section]; 220 | if ([array count] > 0) { 221 | return UITableViewCellEditingStyleDelete; 222 | } else { 223 | return UITableViewCellEditingStyleNone; 224 | } 225 | } 226 | 227 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 228 | NSArray *array = [self arrayForSection:indexPath.section]; 229 | if ([array count] > 0) { 230 | return [[[self class] cellClass] cellHeight]; 231 | } else { 232 | // Height for "EmptyCell". 233 | return 30.0; 234 | } 235 | } 236 | 237 | @end 238 | 239 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 240 | -------------------------------------------------------------------------------- /Application/UIImage+CrashReporter.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | @interface UIImage (CrashReporter) 13 | + (instancetype)imageWithColor:(UIColor *)color; 14 | + (instancetype)imageWithText:(NSString *)text font:(UIFont *)font color:(UIColor *)color imageSize:(CGSize)imageSize; 15 | @end 16 | 17 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 18 | -------------------------------------------------------------------------------- /Application/UIImage+CrashReporter.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "UIImage+CrashReporter.h" 13 | 14 | @implementation UIImage (CrashReporter) 15 | 16 | + (instancetype)imageWithColor:(UIColor *)color { 17 | // Determine if color is opaque. 18 | CGFloat alpha; 19 | [color getRed:NULL green:NULL blue:NULL alpha:&alpha]; 20 | 21 | // Create 1x1 image. 22 | const CGRect rect = CGRectMake(0.0, 0.0, 1.0, 1.0); 23 | UIGraphicsBeginImageContextWithOptions(rect.size, (alpha == 1.0), 0.0); 24 | CGContextRef context = UIGraphicsGetCurrentContext(); 25 | CGContextSetFillColorWithColor(context, [color CGColor]); 26 | CGContextFillRect(context, rect); 27 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 28 | UIGraphicsEndImageContext(); 29 | return image; 30 | } 31 | 32 | + (instancetype)imageWithText:(NSString *)text font:(UIFont *)font color:(UIColor *)color imageSize:(CGSize)imageSize { 33 | UIImage *image = nil; 34 | 35 | const CGSize textSize = [text sizeWithFont:font constrainedToSize:imageSize]; 36 | UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0.0); 37 | [color setFill]; 38 | const CGPoint point = CGPointMake(0.5 * (imageSize.width - textSize.width), 0.5 * (imageSize.height - textSize.height)); 39 | [text drawAtPoint:point withFont:font]; 40 | image = UIGraphicsGetImageFromCurrentImageContext(); 41 | UIGraphicsEndImageContext(); 42 | 43 | return image; 44 | } 45 | 46 | @end 47 | 48 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 49 | -------------------------------------------------------------------------------- /Application/UITableView+CrashReporter.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | @interface UITableView (CrashReporter) 13 | @end 14 | 15 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 16 | -------------------------------------------------------------------------------- /Application/UITableView+CrashReporter.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "UITableView+CrashReporter.h" 13 | 14 | @implementation UITableView (CrashReporter) 15 | 16 | // Prevent header views from "collapsing" when scrolling. 17 | // NOTE: Overrides private API. 18 | - (BOOL)allowsHeaderViewsToFloat { 19 | return NO; 20 | } 21 | 22 | @end 23 | 24 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 25 | -------------------------------------------------------------------------------- /Application/VictimCell.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "TableViewCell.h" 13 | 14 | @interface VictimCell : TableViewCell 15 | @end 16 | 17 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 18 | -------------------------------------------------------------------------------- /Application/VictimCell.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "VictimCell.h" 13 | 14 | #import "CrashLog.h" 15 | 16 | @implementation VictimCell 17 | 18 | + (NSDateFormatter *)timeFormatter { 19 | static NSDateFormatter *formatter = nil; 20 | if (formatter == nil) { 21 | formatter = [[NSDateFormatter alloc] init]; 22 | [formatter setDateFormat:@"HH:mm:ss (yyyy MMM d)"]; 23 | } 24 | return formatter; 25 | } 26 | 27 | #pragma mark - Overrides (TableViewCell) 28 | 29 | - (id)initWithReuseIdentifier:(NSString *)reuseIdentifier { 30 | self = [super initWithReuseIdentifier:reuseIdentifier]; 31 | if (self != nil) { 32 | self.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 33 | } 34 | return self; 35 | } 36 | 37 | - (void)configureWithObject:(id)object { 38 | NSAssert([object isKindOfClass:[CrashLog class]], @"ERROR: Incorrect class type: Expected CrashLog, received %@.", [object class]); 39 | 40 | CrashLog *crashLog = object; 41 | [self setName:[[[self class] timeFormatter] stringFromDate:[crashLog logDate]]]; 42 | [self setViewed:[crashLog isViewed]]; 43 | } 44 | 45 | @end 46 | 47 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 48 | -------------------------------------------------------------------------------- /Application/VictimViewController.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "TableViewController.h" 13 | 14 | @class CrashLogGroup; 15 | 16 | @interface VictimViewController : TableViewController 17 | - (id)initWithGroup:(CrashLogGroup *)group; 18 | @end 19 | 20 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 21 | -------------------------------------------------------------------------------- /Application/VictimViewController.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import "VictimViewController.h" 13 | 14 | #import 15 | #import "CrashLog.h" 16 | #import "CrashLogGroup.h" 17 | #import "SectionHeaderView.h" 18 | #import "SuspectsViewController.h" 19 | #import "VictimCell.h" 20 | 21 | #include "paths.h" 22 | 23 | @implementation VictimViewController { 24 | CrashLogGroup *group_; 25 | } 26 | 27 | - (id)initWithGroup:(CrashLogGroup *)group { 28 | self = [super init]; 29 | if (self != nil) { 30 | group_ = [group retain]; 31 | 32 | self.title = group_.name; 33 | self.supportsRefreshControl = YES; 34 | 35 | // Add button for deleting all logs for this group. 36 | UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash 37 | target:self action:@selector(trashButtonTapped)]; 38 | self.navigationItem.rightBarButtonItem = buttonItem; 39 | [buttonItem release]; 40 | } 41 | return self; 42 | } 43 | 44 | - (void)dealloc { 45 | [group_ release]; 46 | [super dealloc]; 47 | } 48 | 49 | - (void)viewWillAppear:(BOOL)animated { 50 | [self.tableView reloadData]; 51 | } 52 | 53 | #pragma mark - Actions 54 | 55 | - (void)trashButtonTapped { 56 | NSString *message = [[NSString alloc] initWithFormat:NSLocalizedString(@"DELETE_ALL_FOR_MESSAGE", nil), [group_ name]]; 57 | NSString *deleteTitle = NSLocalizedString(@"DELETE", nil); 58 | NSString *cancelTitle = NSLocalizedString(@"CANCEL", nil); 59 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:message delegate:self 60 | cancelButtonTitle:cancelTitle otherButtonTitles:deleteTitle, nil]; 61 | [alert show]; 62 | [alert release]; 63 | [message release]; 64 | } 65 | 66 | #pragma mark - Other 67 | 68 | - (void)reloadCrashLogGroup { 69 | // Reload all crash log groups. 70 | [CrashLogGroup forgetGroups]; 71 | 72 | NSArray *crashLogGroups = [CrashLogGroup groupsForType:[group_ type]]; 73 | 74 | // Find the new group with the same group name (i.e. same process). 75 | NSString *groupName = [group_ name]; 76 | for (CrashLogGroup *group in crashLogGroups) { 77 | if ([[group name] isEqualToString:groupName]) { 78 | [group_ release]; 79 | group_ = [group retain]; 80 | [self.tableView reloadData]; 81 | break; 82 | } 83 | } 84 | } 85 | 86 | - (void)showSuspectsForCrashLog:(CrashLog *)crashLog { 87 | SuspectsViewController *controller = [[SuspectsViewController alloc] initWithCrashLog:crashLog]; 88 | [self.navigationController pushViewController:controller animated:YES]; 89 | [controller release]; 90 | } 91 | 92 | #pragma mark - Overrides (TableViewController) 93 | 94 | + (Class)cellClass { 95 | return [VictimCell class]; 96 | } 97 | 98 | - (NSArray *)arrayForSection:(NSInteger)section { 99 | NSArray *array = nil; 100 | 101 | switch (section) { 102 | case 0: { 103 | NSArray *crashLogs = [group_ crashLogs]; 104 | const NSUInteger count = [crashLogs count]; 105 | if (count > 0) { 106 | array = [crashLogs subarrayWithRange:NSMakeRange(0, 1)]; 107 | } 108 | } break; 109 | case 1: { 110 | NSArray *crashLogs = [group_ crashLogs]; 111 | const NSUInteger count = [crashLogs count]; 112 | if (count > 1) { 113 | array = [crashLogs subarrayWithRange:NSMakeRange(1, count - 1)]; 114 | } 115 | } break; 116 | default: 117 | break; 118 | } 119 | 120 | return array; 121 | } 122 | 123 | - (void)refresh:(id)sender { 124 | [self reloadCrashLogGroup]; 125 | [super refresh:sender]; 126 | } 127 | 128 | - (NSString *)titleForHeaderInSection:(NSInteger)section { 129 | return (section == 0) ? @"LATEST" : @"EARLIER"; 130 | } 131 | 132 | #pragma mark - Delegate (UIAlertView) 133 | 134 | - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { 135 | if (buttonIndex == 1) { 136 | if ([group_ delete]) { 137 | // FIXME: For a better visual effect, refresh the table, detect when 138 | // the reload has finished, and then, after a brief delay, pop. 139 | [self.navigationController popViewControllerAnimated:YES]; 140 | } else { 141 | NSString *title = NSLocalizedString(@"ERROR", nil); 142 | NSString *message = NSLocalizedString(@"DELETE_ALL_FAILED", nil); 143 | NSString *okMessage = NSLocalizedString(@"OK", nil); 144 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:message delegate:nil 145 | cancelButtonTitle:okMessage otherButtonTitles:nil]; 146 | [alert show]; 147 | [alert release]; 148 | 149 | [self refresh:nil]; 150 | } 151 | } 152 | } 153 | 154 | #pragma mark - UITableViewDataSource 155 | 156 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 157 | return 2; 158 | } 159 | 160 | #pragma mark - UITableViewDelegate 161 | 162 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 163 | NSArray *array = [self arrayForSection:indexPath.section]; 164 | if (array != nil) { 165 | CrashLog *crashLog = [array objectAtIndex:indexPath.row]; 166 | [self showSuspectsForCrashLog:crashLog]; 167 | } 168 | } 169 | 170 | - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { 171 | const NSInteger section = indexPath.section; 172 | 173 | // NOTE: Retrieve arrays for both sections before deletion in order to know 174 | // what has changed, needed for deletion animation. 175 | NSArray *latest = [self arrayForSection:0]; 176 | NSArray *earlier = [self arrayForSection:1]; 177 | 178 | NSArray *array = (section == 0) ? latest : earlier; 179 | CrashLog *crashLog = [array objectAtIndex:indexPath.row]; 180 | if ([group_ deleteCrashLog:crashLog]) { 181 | // Animate deletion of row. 182 | NSArray *indexPaths = [NSArray arrayWithObject:indexPath]; 183 | [tableView beginUpdates]; 184 | if ([array count] == 1) { 185 | [tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationLeft]; 186 | } else { 187 | [tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationLeft]; 188 | } 189 | if (section == 0) { 190 | // Animate movement of row from section 1 to section 0. 191 | const NSUInteger earlierCount = [earlier count]; 192 | NSArray *earlierIndexPaths = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:1]]; 193 | if (earlierCount == 1) { 194 | [tableView reloadRowsAtIndexPaths:earlierIndexPaths withRowAnimation:UITableViewRowAnimationLeft]; 195 | } else if (earlierCount > 1) { 196 | [tableView deleteRowsAtIndexPaths:earlierIndexPaths withRowAnimation:UITableViewRowAnimationLeft]; 197 | } 198 | } 199 | [tableView endUpdates]; 200 | } else { 201 | NSString *title = NSLocalizedString(@"ERROR", nil); 202 | NSString *message = NSLocalizedString(@"FILE_DELETION_FAILED" 203 | , nil); 204 | NSString *okMessage = NSLocalizedString(@"OK", nil); 205 | UIAlertView* alert = [[UIAlertView alloc] initWithTitle:title message:message delegate:nil 206 | cancelButtonTitle:okMessage otherButtonTitles:nil]; 207 | [alert show]; 208 | [alert release]; 209 | } 210 | } 211 | 212 | @end 213 | 214 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 215 | -------------------------------------------------------------------------------- /Application/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import 13 | 14 | int main (int argc, char *argv[]) { 15 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 16 | int ret = UIApplicationMain(argc, argv, nil, @"ApplicationDelegate"); 17 | [pool drain]; 18 | return ret; 19 | } 20 | 21 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 22 | -------------------------------------------------------------------------------- /Application/pastie.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | @class NSArray, ModalActionSheet; 13 | 14 | /// Send an array of strings to pastie, and return the URLs. 15 | NSArray* pastie(NSArray* strings, ModalActionSheet* hud); 16 | 17 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 18 | -------------------------------------------------------------------------------- /Application/pastie.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: CrashReporter 3 | * Type: iOS application 4 | * Desc: iOS app for viewing the details of a crash, determining the possible 5 | * cause of said crash, and reporting this information to the developer(s) 6 | * responsible. 7 | * 8 | * Author: Lance Fetters (aka. ashikase) 9 | * License: GPL v3 (See LICENSE file for details) 10 | */ 11 | 12 | #import 13 | #include 14 | #if DEBUG_PASTIE 15 | @class ModalActionSheet; 16 | #else 17 | #import 18 | #import 19 | #import "ModalActionSheet.h" 20 | #endif 21 | 22 | struct lengthIndexPair { 23 | NSUInteger length; 24 | NSUInteger i; 25 | NSUInteger bin; 26 | }; 27 | 28 | static int reverseLengthCompare(const void* a, const void* b) { 29 | NSUInteger al = ((const struct lengthIndexPair*)a)->length, bl = ((const struct lengthIndexPair*)b)->length; 30 | return (int)(bl - al); 31 | } 32 | 33 | static NSArray* pack(NSArray* strings, NSUInteger maxBinSize) { 34 | // assume the user doesn't go crazy and send thousand of files. 35 | NSUInteger count = [strings count]; 36 | struct lengthIndexPair lengths[count]; 37 | NSUInteger binSizes[count]; 38 | Class NSString_class = [NSString class]; 39 | 40 | NSUInteger i = 0; 41 | for (NSString* s in strings) { 42 | lengths[i].length = [s isKindOfClass:NSString_class] ? [s length] : 0; 43 | lengths[i].bin = -1; 44 | binSizes[i] = maxBinSize; 45 | lengths[i].i = i; 46 | i++; 47 | } 48 | 49 | // sort the lengths. 50 | qsort(lengths, count, sizeof(lengths[0]), reverseLengthCompare); 51 | 52 | // pack files using FFD. 53 | NSUInteger maxJ = 0; 54 | for (i = 0; i < count; ++ i) { 55 | for (NSUInteger j = 0; j < count; ++ j) { 56 | if (lengths[i].length < binSizes[j]) { 57 | lengths[i].bin = j; 58 | binSizes[j] -= lengths[i].length; 59 | if (j >= maxJ) 60 | maxJ = j+1; 61 | break; 62 | } 63 | } 64 | } 65 | 66 | if (maxJ == 0) 67 | return nil; 68 | 69 | // create the string. 70 | NSString* packed[maxJ]; 71 | memset(packed, 0, sizeof(packed[0])*maxJ); 72 | for (i = 0; i < count; ++ i) { 73 | NSString* stringToPack = [strings objectAtIndex:lengths[i].i]; 74 | if (![stringToPack isKindOfClass:NSString_class]) 75 | stringToPack = @""; 76 | int bin = lengths[i].bin; 77 | if (bin < 0) { 78 | NSLog(@"CrashReporter: Bin index of object %lu is negative. The string to pack into should be '%@'.", (unsigned long)i, stringToPack); 79 | continue; 80 | } 81 | if (packed[bin]) 82 | packed[bin] = [packed[bin] stringByAppendingString:stringToPack]; 83 | else 84 | packed[bin] = stringToPack; 85 | } 86 | 87 | return [NSArray arrayWithObjects:packed count:maxJ]; 88 | } 89 | 90 | static BOOL seeded = NO; 91 | static NSURLRequest* multipartRequest(NSURL* url, NSDictionary* form) { 92 | if (!seeded) { 93 | seeded = YES; 94 | srand(time(NULL)); 95 | } 96 | 97 | NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url]; 98 | [req setHTTPMethod:@"POST"]; 99 | 100 | 101 | UInt8 boundary[strlen("---------------------------123456123456123456")]; 102 | 103 | { 104 | // construct a random boundary. 105 | const int count_of_minus_signs = sizeof(boundary)-18; 106 | memset(boundary, '-', count_of_minus_signs); 107 | 108 | UInt8* b = boundary + count_of_minus_signs; 109 | for (int i = 0; i < 3; ++ i) { 110 | int r = rand(); 111 | for (int j = 0; j < 6; ++ j) { 112 | int bits = r & 31; 113 | *b++ = bits + (bits < 10 ? '0' : 'a'-10); 114 | r >>= 5; 115 | } 116 | } 117 | } 118 | 119 | // [req setValue:@"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2" forHTTPHeaderField:@"User-Agent"]; 120 | [req setValue:@"http://pastie.org/pastes/new" forHTTPHeaderField:@"Referer"]; 121 | [req setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%.*s", (int)sizeof(boundary)-2, boundary+2] forHTTPHeaderField:@"Content-Type"]; 122 | 123 | NSMutableData* data = [[NSMutableData alloc] init]; 124 | 125 | for (NSString* key in form) { 126 | [data appendBytes:boundary length:sizeof(boundary)]; 127 | [data appendBytes:"\r\nContent-Disposition: form-data; name=\"" length:strlen("\r\nContent-Disposition: form-data; name=\"")]; 128 | [data appendData:[key dataUsingEncoding:NSUTF8StringEncoding]]; 129 | [data appendBytes:"\"\r\n\r\n" length:5]; 130 | 131 | [data appendData:[[form objectForKey:key] dataUsingEncoding:NSUTF8StringEncoding]]; 132 | [data appendBytes:"\r\n" length:2]; 133 | } 134 | [data appendBytes:boundary length:sizeof(boundary)]; 135 | [data appendBytes:"--\r\n" length:4]; 136 | 137 | // [req setValue:[NSString stringWithFormat:@"%lu", [data length]] forHTTPHeaderField:@"Content-Length"]; 138 | [req setHTTPBody:data]; 139 | 140 | [data release]; 141 | 142 | return req; 143 | } 144 | 145 | static NSURL* pastieOne(NSString* str, ModalActionSheet* hud) { 146 | NSUInteger firstLineBreak = [str rangeOfString:@"\n"].location; 147 | NSString* firstLine = [str substringWithRange:NSMakeRange(3, firstLineBreak-3)]; 148 | NSBundle* mainBundle = [NSBundle mainBundle]; 149 | 150 | NSDictionary* dict = [[NSDictionary alloc] initWithObjectsAndKeys: 151 | @"6", @"paste[parser_id]", 152 | @"1", @"paste[restricted]", 153 | str, @"paste[body]", 154 | ([mainBundle objectForInfoDictionaryKey:@"PastieAuth"] ?: @"burger"), @"paste[authorization]", 155 | @"", @"key", 156 | @"Paste", @"commit", 157 | nil]; 158 | 159 | NSURLRequest* req = multipartRequest([NSURL URLWithString:@"http://pastie.org/pastes"], dict); 160 | [dict release]; 161 | 162 | [hud updateText:[NSString stringWithFormat:NSLocalizedString(@"Uploading %@", nil), firstLine]]; 163 | 164 | NSURLResponse* resp = nil; 165 | NSError* err = nil; 166 | if (![NSURLConnection sendSynchronousRequest:req returningResponse:&resp error:&err]) { 167 | #if !DEBUG_PASTIE 168 | UIAlertView* alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Upload failed", nil) 169 | message:[NSString stringWithFormat:NSLocalizedString(@"UPLOAD_FAILED_2", nil), 170 | firstLine, [err localizedDescription]] 171 | delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", nil) otherButtonTitles:nil]; 172 | [alert show]; 173 | [alert release]; 174 | #endif 175 | return nil; 176 | } else { 177 | return [resp URL]; 178 | } 179 | } 180 | 181 | NSArray* pastie(NSArray* strings, ModalActionSheet* hud) { 182 | #if !DEBUG_PASTIE 183 | SCNetworkReachabilityFlags flags = 0; 184 | SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, "pastie.org"); 185 | if (SCNetworkReachabilityGetFlags(reachability, &flags)) { 186 | if (flags & (kSCNetworkReachabilityFlagsReachable|kSCNetworkReachabilityFlagsConnectionOnTraffic|kSCNetworkReachabilityFlagsIsWWAN)) { 187 | UIApplication* app = [UIApplication sharedApplication]; 188 | app.networkActivityIndicatorVisible = YES; 189 | #endif 190 | 191 | // pastie.org is reachable. now send the files. 192 | NSArray* packed = pack(strings, 102400); 193 | NSMutableArray* urls = [NSMutableArray array]; 194 | for (NSString* str in packed) { 195 | NSURL* url = pastieOne(str, hud); 196 | if (url != nil) 197 | [urls addObject:url]; 198 | } 199 | #if !DEBUG_PASTIE 200 | app.networkActivityIndicatorVisible = NO; 201 | #endif 202 | if ([urls count] != 0) 203 | return urls; 204 | #if !DEBUG_PASTIE 205 | } 206 | } 207 | #endif 208 | 209 | return nil; 210 | } 211 | 212 | #if DEBUG_PASTIE 213 | int main (int argc, const char* argv[]) { 214 | if (argc == 1) { 215 | printf("Usage: pastie ..."); 216 | } else { 217 | NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; 218 | 219 | NSMutableArray* res = [[NSMutableArray alloc] initWithCapacity:argc-1]; 220 | for (int i = 1; i < argc; ++ i) { 221 | NSString* fileContent = [[NSString alloc] initWithContentsOfFile:[NSString stringWithUTF8String:argv[i]] usedEncoding:NULL error:NULL]; 222 | NSString* data = [[NSString alloc] initWithFormat:@"## %s\n%@\n", argv[i], fileContent]; 223 | [fileContent release]; 224 | [res addObject:data]; 225 | [data release]; 226 | } 227 | NSArray* urls = pastie(res, nil); 228 | [res release]; 229 | 230 | CFShow(urls); 231 | 232 | [pool drain]; 233 | } 234 | return 0; 235 | } 236 | #endif 237 | 238 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 239 | -------------------------------------------------------------------------------- /Design/icon-72.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/Design/icon-72.xcf -------------------------------------------------------------------------------- /Design/icon-72@2x.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/Design/icon-72@2x.xcf -------------------------------------------------------------------------------- /Design/icon.idraw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/Design/icon.idraw -------------------------------------------------------------------------------- /Design/icon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/Design/icon.xcf -------------------------------------------------------------------------------- /Design/icon@2x.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/Design/icon@2x.xcf -------------------------------------------------------------------------------- /Design/navicon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/Design/navicon.xcf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 Lance Fetters (aka. ashikase) 2 | 3 | CrashReporter was originally developed by KennyTM~. 4 | http://networkpx.googlecode.com/svn/trunk/CrashReporter/?r=623 5 | Copyright (C) 2009 KennyTM~ 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | The GNU General Public License can be found at . 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SUBPROJECTS = Application as_root monitor notifier scanner extrainst_ 2 | PKG_ID = crash-reporter 3 | 4 | export ARCHS = armv6 arm64 5 | export SDKVERSION = 7.1 6 | export TARGET = iphone:clang 7 | export TARGET_IPHONEOS_DEPLOYMENT_VERSION = 3.0 8 | 9 | export ADDITIONAL_CFLAGS += -I$(THEOS_PROJECT_DIR)/common -I$(THEOS_PROJECT_DIR)/Libraries/Common -include firmware.h 10 | export ADDITIONAL_LDFLAGS = -L$(THEOS)/lib/arm 11 | 12 | include theos/makefiles/common.mk 13 | include theos/makefiles/aggregate.mk 14 | 15 | after-stage:: 16 | # Give as_root the power of root in order to move/delete root-owned files. 17 | - chmod u+s $(THEOS_STAGING_DIR)/Applications/CrashReporter.app/as_root 18 | # Copy localization files. 19 | - cp -a $(THEOS_PROJECT_DIR)/Localization/CrashReporter/Application/*.lproj $(THEOS_STAGING_DIR)/Applications/CrashReporter.app/ 20 | - cp -a $(THEOS_PROJECT_DIR)/Localization/CrashReporter/Preferences/*.lproj $(THEOS_STAGING_DIR)/Library/PreferenceLoader/Preferences/CrashReporter/ 21 | # Optimize png files 22 | - find $(THEOS_STAGING_DIR) -iname '*.png' -exec pincrush -i {} \; 23 | # Convert plist files to binary 24 | - find $(THEOS_STAGING_DIR)/ -type f -iname '*.plist' -exec plutil -convert binary1 {} \; 25 | # Remove repository-related files 26 | - find $(THEOS_STAGING_DIR) -name '.gitkeep' -delete 27 | 28 | after-install:: 29 | - ssh idevice killall CrashReporter 30 | 31 | distclean: clean 32 | - rm -f $(THEOS_PROJECT_DIR)/$(PKG_ID)*.deb 33 | - rm -f $(THEOS_PROJECT_DIR)/.theos/packages/* 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | About 2 | ===== 3 | 4 | CrashReporter is an iOS app for viewing the details of a crash, determining the possible cause of said crash, and reporting this information to the developer(s) responsible. 5 | 6 | Credit 7 | ===== 8 | 9 | [CrashReporter](http://code.google.com/p/networkpx/wiki/Using_CrashReporter) was orignally developed by [kennytm](https://github.com/kennytm). 10 | 11 | This project also makes use of the following: 12 | * [RegexKitLite framework](http://regexkit.sourceforge.net) 13 | * [libsymbolicate](http://github.com/ashikase/libsymbolicate) 14 | -------------------------------------------------------------------------------- /as_root/Entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | platform-application 6 | 7 | com.apple.private.skip-library-validation 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /as_root/Makefile: -------------------------------------------------------------------------------- 1 | TOOL_NAME = as_root 2 | as_root_INSTALL_PATH = /Applications/CrashReporter.app 3 | as_root_OBJC_FILES = as_root.c 4 | as_root_FRAMEWORKS = CoreFoundation 5 | 6 | as_root_CODESIGN_FLAGS="-SEntitlements.plist" 7 | 8 | include $(THEOS)/makefiles/common.mk 9 | include $(THEOS)/makefiles/tool.mk 10 | -------------------------------------------------------------------------------- /as_root/as_root.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: as_root 3 | * Type: iOS command line tool 4 | * Desc: Tool for moving and deleting specific sets of files as root. 5 | * 6 | * Author: Lance Fetters (aka. ashikase) 7 | * License: GPL v3 (See LICENSE file for details) 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "paths.h" 19 | 20 | static const char * const kTemporaryFilepath = "/tmp/CrashReporter.temp.XXXXXX"; 21 | 22 | static void print_usage() { 23 | fprintf(stderr, 24 | "Usage: as_root chmod \n" 25 | " as_root chown \n" 26 | " as_root copy \n" 27 | " as_root delete \n" 28 | " as_root move \n" 29 | " as_root read \n" 30 | "\n" 31 | " Note that only filepaths with the following prefixes are permitted:\n" 32 | " * \"%s\"\n" 33 | " * \"%s\"\n" 34 | " * \"%s\"\n", 35 | kCrashLogDirectoryForMobile, kCrashLogDirectoryForRoot, kTemporaryPath 36 | ); 37 | } 38 | 39 | static int copy(const char *from_filepath, const char *to_filepath) { 40 | int result = EXIT_FAILURE; 41 | 42 | char buffer[BUFSIZ]; 43 | size_t nitems; 44 | FILE *from_file = fopen(from_filepath, "r");; 45 | if (from_file != NULL) { 46 | FILE *to_file = fopen(to_filepath, "w");; 47 | if (to_file != NULL) { 48 | // Copy data to to_filepath. 49 | while ((nitems = fread(buffer, sizeof(char), sizeof(buffer), from_file)) > 0) { 50 | if (fwrite(buffer, sizeof(char), nitems, to_file) != nitems) { 51 | fprintf(stderr, "ERROR: Failure while copying file, errno = %d.\n", errno); 52 | goto exit_copy; 53 | } 54 | } 55 | result = EXIT_SUCCESS; 56 | fclose(to_file); 57 | } else { 58 | fprintf(stderr, "ERROR: Unable to open destination filepath for writing, errno = %d.\n", errno); 59 | } 60 | fclose(from_file); 61 | } else { 62 | fprintf(stderr, "ERROR: Unable to open source filepath for reading, errno = %d.\n", errno); 63 | } 64 | 65 | exit_copy: 66 | return result; 67 | } 68 | 69 | static int is_valid_filepath(const char *filepath) { 70 | return 71 | (strncmp(filepath, kCrashLogDirectoryForMobile, strlen(kCrashLogDirectoryForMobile)) == 0) || 72 | (strncmp(filepath, kCrashLogDirectoryForRoot, strlen(kCrashLogDirectoryForRoot)) == 0) || 73 | (strncmp(filepath, kTemporaryPath, strlen(kTemporaryPath)) == 0); 74 | } 75 | 76 | int main(int argc, const char *argv[]) { 77 | // Run as root. 78 | if (setuid(geteuid()) != 0) { 79 | fprintf(stderr, "ERROR: Unable to assume root powers, errno = %d.\n", errno); 80 | return EXIT_FAILURE; 81 | } 82 | 83 | if ((argc == 4) && (strcasecmp(argv[1], "chmod") == 0)) { 84 | // Get filepath and ownership info. 85 | const char *filepath = argv[2]; 86 | mode_t mode = strtol(argv[3], NULL, 8); 87 | 88 | // Change mode for filepath. 89 | if (chmod(filepath, mode) != 0) { 90 | fprintf(stderr, "WARNING: Failed to change mode of file: %s, errno = %d.\n", filepath, errno); 91 | return EXIT_FAILURE; 92 | } 93 | } else if ((argc == 5) && (strcasecmp(argv[1], "chown") == 0)) { 94 | // Get filepath and ownership info. 95 | const char *filepath = argv[2]; 96 | uid_t owner = atoi(argv[3]); 97 | gid_t group = atoi(argv[4]); 98 | 99 | // Change ownership for filepath. 100 | if (lchown(filepath, owner, group) != 0) { 101 | fprintf(stderr, "WARNING: Failed to change ownership of file: %s, errno = %d.\n", filepath, errno); 102 | return EXIT_FAILURE; 103 | } 104 | } else if ((argc == 4) && (strcasecmp(argv[1], "copy") == 0)) { 105 | // Get filepaths. 106 | const char *from_filepath = argv[2]; 107 | const char *to_filepath = argv[3]; 108 | 109 | // Check files at filepaths. 110 | if (!is_valid_filepath(from_filepath) || !is_valid_filepath(to_filepath)) { 111 | fprintf(stderr, "ERROR: At least one of the specified filepaths is not allowed.\n"); 112 | return EXIT_FAILURE; 113 | } 114 | 115 | // Copy from_filepath to to_filepath. 116 | if (copy(from_filepath, to_filepath) != 0) { 117 | return EXIT_FAILURE; 118 | } 119 | } else if ((argc == 4) && (strcasecmp(argv[1], "move") == 0)) { 120 | // Get filepaths. 121 | const char *from_filepath = argv[2]; 122 | const char *to_filepath = argv[3]; 123 | 124 | // Check files at filepaths. 125 | if (!is_valid_filepath(from_filepath) || !is_valid_filepath(to_filepath)) { 126 | fprintf(stderr, "ERROR: At least one of the specified filepaths is not allowed.\n"); 127 | return EXIT_FAILURE; 128 | } 129 | 130 | // Move from_filepath to to_filepath. 131 | if (strcmp(from_filepath, to_filepath) != 0) { 132 | if (rename(from_filepath, to_filepath) != 0) { 133 | fprintf(stderr, "ERROR: Failed to rename file, errno = %d.\n", errno); 134 | return EXIT_FAILURE; 135 | } 136 | } 137 | } else if ((argc == 3) && (strcasecmp(argv[1], "delete") == 0)) { 138 | // Get filepath. 139 | const char *filepath = argv[2]; 140 | 141 | // Check file at filepath. 142 | if (!is_valid_filepath(filepath)) { 143 | fprintf(stderr, "ERROR: Specified filepath is not allowed.\n"); 144 | return EXIT_FAILURE; 145 | } 146 | 147 | // Delete file at filepath. 148 | if (unlink(filepath) != 0) { 149 | fprintf(stderr, "ERROR: Failed to delete file, errno = %d.\n", errno); 150 | return EXIT_FAILURE; 151 | } 152 | } else if ((argc == 3) && (strcasecmp(argv[1], "read") == 0)) { 153 | // Get filepath. 154 | const char *filepath = argv[2]; 155 | 156 | // Check file at filepath. 157 | if (!is_valid_filepath(filepath)) { 158 | fprintf(stderr, "ERROR: Specified filepath is not allowed.\n"); 159 | return EXIT_FAILURE; 160 | } 161 | 162 | // Create temporary filepath. 163 | char temp_filepath[strlen(kTemporaryFilepath) + 1 ]; 164 | memcpy(temp_filepath, kTemporaryFilepath, sizeof(temp_filepath)); 165 | if (mktemp(temp_filepath) == NULL) { 166 | fprintf(stderr, "ERROR: Unable to create temporary filepath.\n"); 167 | return EXIT_FAILURE; 168 | } 169 | 170 | // Copy filepath to temporary filepath. 171 | if (copy(filepath, temp_filepath) != 0) { 172 | return EXIT_FAILURE; 173 | } 174 | 175 | // Print temporary filepath. 176 | fprintf(stdout, "%s\n", temp_filepath); 177 | } else { 178 | print_usage(); 179 | } 180 | 181 | return EXIT_SUCCESS; 182 | } 183 | -------------------------------------------------------------------------------- /common/crashlog_util.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Desc: Collection of misc. crash log related functions used by both 3 | * CrashReporter app and notifier. 4 | * 5 | * Author: Lance Fetters (aka. ashikase) 6 | * License: GPL v3 (See LICENSE file for details) 7 | */ 8 | 9 | @class CRCrashReport; 10 | 11 | BOOL fileIsSymbolicated(NSString *filepath, CRCrashReport *report); 12 | NSData *dataForFile(NSString *filepath); 13 | BOOL deleteFile(NSString *filepath); 14 | BOOL fixFileOwnershipAndPermissions(NSString *filepath); 15 | NSString *symbolicateFile(NSString *filepath, CRCrashReport *report); 16 | NSString *syslogPathForFile(NSString *filepath); 17 | BOOL writeToFile(NSString *string, NSString *outputFilepath); 18 | 19 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 20 | -------------------------------------------------------------------------------- /common/crashlog_util.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Desc: Collection of misc. crash log related functions used by both 3 | * CrashReporter app and notifier. 4 | * 5 | * Author: Lance Fetters (aka. ashikase) 6 | * License: GPL v3 (See LICENSE file for details) 7 | */ 8 | 9 | #import "crashlog_util.h" 10 | 11 | #import 12 | #include 13 | #include 14 | #include "exec_as_root.h" 15 | 16 | static const char * const kTemporaryFilepath = "/tmp/CrashReporter.temp.XXXXXX"; 17 | 18 | BOOL fileIsSymbolicated(NSString *filepath, CRCrashReport *report) { 19 | BOOL isSymbolicated = NO; 20 | 21 | // NOTE: Marking and checking for symbolication status via a filename is 22 | // not the best method; however, the filename is checked first 23 | // because: 24 | // * It allows checking without having to load the crash log file. 25 | // * Earlier versions of libsymbolicate did not include the 26 | // "symbolicated" property. 27 | // XXX: Checking the filename may lead to incorrectly marking a file as 28 | // symbolicated if the name of the crashed process includes the text 29 | // ".symbolicated.". The chance of such a process existing, though, is 30 | // considered to be low enough that code for this has not been added. 31 | NSString *pathExtension = nil; 32 | do { 33 | // NOTE: This assumes that symbolicated files have a specific extension, 34 | // which may not be the case if the file was symbolicated by a 35 | // tool other than CrashReporter. 36 | pathExtension = [filepath pathExtension]; 37 | if ([pathExtension isEqualToString:@"symbolicated"]) { 38 | isSymbolicated = YES; 39 | break; 40 | } 41 | filepath = [filepath stringByDeletingPathExtension]; 42 | } while ([pathExtension length] > 0); 43 | 44 | if (!isSymbolicated) { 45 | // Load crash report if necessary. 46 | BOOL needsRelease = NO; 47 | if (report == nil) { 48 | // Get data for crash log file. 49 | NSData *data = dataForFile(filepath); 50 | if (data != nil) { 51 | // Load crash report. 52 | report = [[CRCrashReport alloc] initWithData:data filterType:CRCrashReportFilterTypePackage]; 53 | if (report != nil) { 54 | needsRelease = YES; 55 | } 56 | } 57 | } 58 | 59 | // Check for "symbolicated" key. 60 | isSymbolicated = [report isSymbolicated]; 61 | 62 | // Cleanup. 63 | if (needsRelease) { 64 | [report release]; 65 | } 66 | } 67 | 68 | return isSymbolicated; 69 | } 70 | 71 | NSData *dataForFile(NSString *filepath) { 72 | NSData *data = nil; 73 | 74 | // If filepath is not readable, copy to temporary file. 75 | NSString *tempFilepath = nil; 76 | NSFileManager *fileMan = [NSFileManager defaultManager]; 77 | if (![fileMan isReadableFileAtPath:filepath]) { 78 | // Copy file to temporary file. 79 | char path[strlen(kTemporaryFilepath) + 1 ]; 80 | memcpy(path, kTemporaryFilepath, sizeof(path)); 81 | if (mktemp(path) == NULL) { 82 | fprintf(stderr, "ERROR: Unable to create temporary filepath.\n"); 83 | return nil; 84 | } 85 | 86 | if (!copy_as_root([filepath UTF8String], path)) { 87 | fprintf(stderr, "ERROR: Failed to move file to temorary filepath.\n"); 88 | return nil; 89 | } 90 | 91 | tempFilepath = [[NSString alloc] initWithCString:path encoding:NSUTF8StringEncoding]; 92 | } 93 | 94 | // Load file data. 95 | NSError *error = nil; 96 | data = [[NSData alloc] initWithContentsOfFile:(tempFilepath ?: filepath) options:0 error:&error]; 97 | if (data == nil) { 98 | fprintf(stderr, "ERROR: Unable to load data from \"%s\": \"%s\".\n", 99 | [(tempFilepath ?: filepath) UTF8String], [[error localizedDescription] UTF8String]); 100 | } 101 | 102 | // Delete temporary file, if necessary. 103 | if (tempFilepath != nil) { 104 | deleteFile(tempFilepath); 105 | [tempFilepath release]; 106 | } 107 | 108 | return data; 109 | } 110 | 111 | BOOL deleteFile(NSString *filepath) { 112 | BOOL didDelete = YES; 113 | 114 | NSError *error = nil; 115 | if (![[NSFileManager defaultManager] removeItemAtPath:filepath error:&error]) { 116 | // Try again using as_root tool. 117 | if (!delete_as_root([filepath UTF8String])) { 118 | fprintf(stderr, "WARNING: Unable to delete file \"%s\": \"%s\".\n", 119 | [filepath UTF8String], [[error localizedDescription] UTF8String]); 120 | didDelete = NO; 121 | } 122 | } 123 | 124 | return didDelete; 125 | } 126 | 127 | BOOL fixFileOwnershipAndPermissions(NSString *filepath) { 128 | BOOL didFix = NO; 129 | 130 | // Determine ownership of containing directory. 131 | NSString *directory = [filepath stringByDeletingLastPathComponent]; 132 | NSError *error = nil; 133 | NSDictionary *attrib = [[NSFileManager defaultManager] attributesOfItemAtPath:directory error:&error]; 134 | if (attrib != nil) { 135 | // Determine owner of filepath. 136 | BOOL isMobile = [[attrib fileOwnerAccountName] isEqualToString:@"mobile"]; 137 | 138 | // Apply same ownership to the filepath. 139 | const char *path = [filepath UTF8String]; 140 | uid_t owner = isMobile ? 501 : 0; 141 | gid_t group = owner; 142 | 143 | if (lchown(path, owner, group) != 0) { 144 | // Try again using as_root tool. 145 | if (!chown_as_root(path, owner, group)) { 146 | fprintf(stderr, "WARNING: Failed to change ownership of file: %s, errno = %d.\n", path, errno); 147 | } 148 | } 149 | 150 | // Update permissions using known values. 151 | mode_t mode = isMobile ? 0644 : 0640; 152 | if (chmod(path, mode) != 0) { 153 | // Try again using as_root tool. 154 | if (!chmod_as_root(path, mode)) { 155 | fprintf(stderr, "WARNING: Failed to change mode of file: %s, errno = %d.\n", path, errno); 156 | } 157 | } 158 | } else { 159 | fprintf(stderr, "ERROR: Unable to determine attributes for file: %s, %s.\n", 160 | [filepath UTF8String], [[error localizedDescription] UTF8String]); 161 | } 162 | 163 | return didFix; 164 | } 165 | 166 | static void replaceSymbolicLink(NSString *linkPath, NSString *oldDestPath, NSString *newDestPath) { 167 | // NOTE: Must check the destination of the links, as the links may have 168 | // been updated since this tool began executing. 169 | NSFileManager *fileMan = [NSFileManager defaultManager]; 170 | NSError *error = nil; 171 | NSString *destPath = [fileMan destinationOfSymbolicLinkAtPath:linkPath error:&error]; 172 | if (destPath != nil) { 173 | if ([destPath isEqualToString:oldDestPath]) { 174 | // Remove old link. 175 | if (deleteFile(linkPath)) { 176 | // Create new link. 177 | if ([fileMan createSymbolicLinkAtPath:linkPath withDestinationPath:newDestPath error:&error]) { 178 | fixFileOwnershipAndPermissions(linkPath); 179 | } else { 180 | fprintf(stderr, "ERROR: Failed to create \"%s\" symbolic link: %s.\n", 181 | [[linkPath lastPathComponent] UTF8String], [[error localizedDescription] UTF8String]); 182 | } 183 | } 184 | } 185 | } else { 186 | fprintf(stderr, "ERROR: Failed to determine destination of \"%s\" symbolic link: %s.\n", 187 | [[linkPath lastPathComponent] UTF8String], [[error localizedDescription] UTF8String]); 188 | } 189 | } 190 | 191 | // NOTE: This functions expects any passed report object to have been loaded 192 | // with filter type CRCrashReportFilterTypePackage. 193 | // FIXME: Ensure that this is the case. 194 | NSString *symbolicateFile(NSString *filepath, CRCrashReport *report) { 195 | NSString *outputFilepath = nil; 196 | 197 | // Load crash report if necessary. 198 | BOOL needsRelease = NO; 199 | if (report == nil) { 200 | // Get data for crash log file. 201 | NSData *data = dataForFile(filepath); 202 | if (data != nil) { 203 | // Load crash report. 204 | report = [[CRCrashReport alloc] initWithData:data filterType:CRCrashReportFilterTypePackage]; 205 | if (report != nil) { 206 | needsRelease = YES; 207 | } 208 | } 209 | } 210 | 211 | // Symbolicate. 212 | if (!fileIsSymbolicated(filepath, report)) { 213 | if ([report symbolicate]) { 214 | // Process blame. 215 | if ([report blame]) { 216 | // Write output to file. 217 | NSString *pathExtension = [filepath pathExtension]; 218 | NSString *path = [NSString stringWithFormat:@"%@.symbolicated.%@", 219 | [filepath stringByDeletingPathExtension], pathExtension]; 220 | if (writeToFile([report stringRepresentation], path)) { 221 | // Fix any "LatestCrash-*" symbolic links for this file. 222 | NSString *oldDestPath = [filepath lastPathComponent]; 223 | NSString *newDestPath = [path lastPathComponent]; 224 | NSString *linkPath; 225 | linkPath = [NSString stringWithFormat:@"%@/LatestCrash.%@", 226 | [filepath stringByDeletingLastPathComponent], pathExtension]; 227 | replaceSymbolicLink(linkPath, oldDestPath, newDestPath); 228 | 229 | NSString *processName = [[report properties] objectForKey:@"name"]; 230 | linkPath = [NSString stringWithFormat:@"%@/LatestCrash-%@.%@", 231 | [filepath stringByDeletingLastPathComponent], processName, pathExtension]; 232 | replaceSymbolicLink(linkPath, oldDestPath, newDestPath); 233 | 234 | // Delete the original (non-symbolicated) crash log file. 235 | if (!deleteFile(filepath)) { 236 | fprintf(stderr, "WARNING: Failed to delete original log file \"%s\".\n", [filepath UTF8String]); 237 | } 238 | 239 | // Update file ownership and permissions. 240 | fixFileOwnershipAndPermissions(path); 241 | 242 | // Save write path. 243 | outputFilepath = path; 244 | } 245 | } else { 246 | fprintf(stderr, "ERROR: Failed to process blame.\n"); 247 | } 248 | } else { 249 | fprintf(stderr, "ERROR: Unable to symbolicate file \"%s\"\n.", [filepath UTF8String]); 250 | } 251 | } else { 252 | // Already symbolicated. 253 | outputFilepath = filepath; 254 | } 255 | 256 | // Cleanup. 257 | if (needsRelease) { 258 | [report release]; 259 | } 260 | 261 | return outputFilepath; 262 | } 263 | 264 | NSString *syslogPathForFile(NSString *filepath) { 265 | NSString *syslogPath = filepath; 266 | 267 | // Strip known path extensions. 268 | NSString *pathExtension = [syslogPath pathExtension]; 269 | while ( 270 | [pathExtension isEqualToString:@"ips"] || 271 | [pathExtension isEqualToString:@"plist"] || 272 | [pathExtension isEqualToString:@"symbolicated"] || 273 | [pathExtension isEqualToString:@"synced"] 274 | ) { 275 | syslogPath = [syslogPath stringByDeletingPathExtension]; 276 | pathExtension = [syslogPath pathExtension]; 277 | } 278 | 279 | return [syslogPath stringByAppendingPathExtension:@"syslog"]; 280 | } 281 | 282 | BOOL writeToFile(NSString *string, NSString *outputFilepath) { 283 | BOOL didWrite = NO; 284 | 285 | NSString *outputDirectory = [outputFilepath stringByDeletingLastPathComponent]; 286 | NSFileManager *fileMan = [NSFileManager defaultManager]; 287 | 288 | // If filepath is not writable, will write to temporary file. 289 | NSString *tempFilepath = nil; 290 | if (![fileMan isWritableFileAtPath:outputDirectory]) { 291 | // Copy file to temporary file. 292 | char path[strlen(kTemporaryFilepath) + 1 ]; 293 | memcpy(path, kTemporaryFilepath, sizeof(path)); 294 | if (mktemp(path) == NULL) { 295 | fprintf(stderr, "ERROR: Unable to create temporary filepath.\n"); 296 | return NO; 297 | } 298 | tempFilepath = [[NSString alloc] initWithCString:path encoding:NSUTF8StringEncoding]; 299 | } 300 | 301 | // Write to file. 302 | NSError *error = nil; 303 | if ([string writeToFile:(tempFilepath ?: outputFilepath) atomically:YES encoding:NSUTF8StringEncoding error:&error]) { 304 | // Delete temporary file, if necessary. 305 | if (tempFilepath != nil) { 306 | // Move symbolicated file to actual directory. 307 | if (!move_as_root([tempFilepath UTF8String], [outputFilepath UTF8String])) { 308 | fprintf(stderr, "ERROR: Failed to move symbolicated log file back to original directory.\n"); 309 | goto exit; 310 | } 311 | } 312 | 313 | // Note that write succeeded. 314 | didWrite = YES; 315 | } else { 316 | fprintf(stderr, "ERROR: Unable to write to file \"%s\": %s.\n", 317 | [(tempFilepath ?: outputFilepath) UTF8String], [[error localizedDescription] UTF8String]); 318 | } 319 | 320 | exit: 321 | [tempFilepath release]; 322 | return didWrite; 323 | } 324 | 325 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 326 | -------------------------------------------------------------------------------- /common/exec_as_root.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Desc: Collection of utility functions for calling as_root command line tool. 3 | * 4 | * Author: Lance Fetters (aka. ashikase) 5 | * License: GPL v3 (See LICENSE file for details) 6 | */ 7 | 8 | BOOL chmod_as_root(const char *filepath, mode_t mode); 9 | BOOL chown_as_root(const char *filepath, uid_t owner, gid_t group); 10 | BOOL copy_as_root(const char *from_filepath, const char *to_filepath); 11 | BOOL delete_as_root(const char *filepath); 12 | BOOL move_as_root(const char *from_filepath, const char *to_filepath); 13 | 14 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 15 | -------------------------------------------------------------------------------- /common/exec_as_root.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Desc: Collection of utility functions for calling as_root command line tool. 3 | * 4 | * Author: Lance Fetters (aka. ashikase) 5 | * License: GPL v3 (See LICENSE file for details) 6 | */ 7 | 8 | #include "exec_as_root.h" 9 | 10 | static NSString *as_root_path$ = nil; 11 | 12 | static const char *as_root_path() { 13 | if (as_root_path$ == nil) { 14 | as_root_path$ = [[[NSBundle mainBundle] pathForResource:@"as_root" ofType:nil] retain]; 15 | if (as_root_path$ == nil) { 16 | fprintf(stderr, "ERROR: Unable to determine path for \"as_root\" tool.\n"); 17 | } 18 | } 19 | return [as_root_path$ UTF8String]; 20 | } 21 | 22 | static BOOL as_root(const char *action, const char *param1, const char *param2, const char *param3) { 23 | BOOL succeeded = NO; 24 | 25 | pid_t pid = fork(); 26 | const char *path = as_root_path(); 27 | if (pid == 0) { 28 | // Execute the process. 29 | if (strcmp(action, "chown") == 0) { 30 | execl(path, path, action, param1, param2, param3, NULL); 31 | } else if ((strcmp(action, "chmod") == 0) || (strcmp(action, "copy") == 0) || (strcmp(action, "move") == 0)) { 32 | execl(path, path, action, param1, param2, NULL); 33 | } else if (strcmp(action, "delete") == 0) { 34 | execl(path, path, action, param1, NULL); 35 | } 36 | _exit(0); 37 | } else if (pid != -1) { 38 | // Wait for process to finish. 39 | int stat_loc; 40 | waitpid(pid, &stat_loc, 0); 41 | 42 | // Check the exit status to determine if the operation was successful. 43 | if (WIFEXITED(stat_loc)) { 44 | if (WEXITSTATUS(stat_loc) == 0) { 45 | succeeded = YES; 46 | } 47 | } 48 | } 49 | 50 | return succeeded; 51 | } 52 | 53 | BOOL chmod_as_root(const char *filepath, mode_t mode) { 54 | char mode_buf[5]; 55 | snprintf(mode_buf, 5, "%o", mode); 56 | return as_root("chmod", filepath, mode_buf, NULL); 57 | } 58 | 59 | BOOL chown_as_root(const char *filepath, uid_t owner, gid_t group) { 60 | char owner_buf[17]; 61 | char group_buf[17]; 62 | snprintf(owner_buf, 17, "%u", owner); 63 | snprintf(group_buf, 17, "%u", owner); 64 | return as_root("chown", filepath, owner_buf, group_buf); 65 | } 66 | 67 | BOOL copy_as_root(const char *from_filepath, const char *to_filepath) { 68 | return as_root("copy", from_filepath, to_filepath, NULL); 69 | } 70 | 71 | BOOL delete_as_root(const char *filepath) { 72 | return as_root("delete", filepath, NULL, NULL); 73 | } 74 | 75 | BOOL move_as_root(const char *from_filepath, const char *to_filepath) { 76 | return as_root("move", from_filepath, to_filepath, NULL); 77 | } 78 | 79 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 80 | -------------------------------------------------------------------------------- /common/paths.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Lance Fetters (aka. ashikase) 3 | * License: GPL v3 (See LICENSE file for details) 4 | */ 5 | 6 | #ifndef COMMON_PATHS_H_ 7 | #define COMMON_PATHS_H_ 8 | 9 | #define kCrashLogDirectoryForMobile "/var/mobile/Library/Logs/CrashReporter" 10 | #define kCrashLogDirectoryForRoot "/Library/Logs/CrashReporter" 11 | #define kTemporaryPath "/tmp/" 12 | 13 | #define kIsRunningFilepath "/tmp/crashreporter_is_running" 14 | 15 | #endif // COMMON_PATHS_H_ 16 | 17 | /* vim: set ft=c ff=unix sw=4 ts=4 expandtab tw=80: */ 18 | -------------------------------------------------------------------------------- /common/preferences.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Lance Fetters (aka. ashikase) 3 | * License: GPL v3 (See LICENSE file for details) 4 | */ 5 | 6 | #ifndef COMMON_PREFERENCES_H_ 7 | #define COMMON_PREFERENCES_H_ 8 | 9 | #define kCrashesSinceLastLaunch "crashesSinceLastLaunch" 10 | 11 | #endif // COMMON_PREFERENCES_H_ 12 | 13 | /* vim: set ft=c ff=unix sw=4 ts=4 expandtab tw=80: */ 14 | -------------------------------------------------------------------------------- /extrainst_/Entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | platform-application 6 | 7 | com.apple.private.skip-library-validation 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /extrainst_/Makefile: -------------------------------------------------------------------------------- 1 | TOOL_NAME = extrainst_ 2 | extrainst__INSTALL_PATH = /DEBIAN 3 | 4 | extrainst__OBJC_FILES = main.mm 5 | extrainst__CODESIGN_FLAGS="-SEntitlements.plist" 6 | 7 | ARCHS := armv6 arm64 8 | 9 | include $(THEOS)/makefiles/common.mk 10 | include $(THEOS)/makefiles/tool.mk 11 | -------------------------------------------------------------------------------- /extrainst_/main.mm: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2018 Lance Fetters (a.k.a. ashikase) 3 | */ 4 | 5 | int main(int argc, char *argv[], char *envp[]) { 6 | @autoreleasepool { 7 | // NOTE: In iOS 11 jailbroken via Electra, CrashReporter cannot be 8 | // launched via the "CrashReporter_" safe mode script. 9 | // NOTE: This is believed to also be true for all current jailbreaks 10 | // for 10.3, though is untested due to lack of a 10.3 device. 11 | if (IOS_GTE(10_3)) { 12 | NSBundle *bundle = [NSBundle bundleWithPath:@"/Applications/CrashReporter.app"]; 13 | if (bundle == nil) { 14 | fprintf(stderr, "ERROR: App bundle not found.\n"); 15 | return 1; 16 | } 17 | 18 | NSURL *url = [bundle URLForResource:@"Info" withExtension:@"plist"]; 19 | if (url == nil) { 20 | fprintf(stderr, "ERROR: Info.plist not found.\n"); 21 | return 1; 22 | } 23 | 24 | NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfURL:url]; 25 | if (dict == nil) { 26 | fprintf(stderr, "ERROR: Unable to load contents of Info.plist.\n"); 27 | return 1; 28 | } 29 | 30 | dict[@"CFBundleExecutable"] = @"CrashReporter"; 31 | 32 | if (![dict writeToURL:url atomically:YES]) { 33 | fprintf(stderr, "ERROR: Failed to write updated Info.plist.\n"); 34 | return 1; 35 | } 36 | } 37 | } 38 | 39 | return 0; 40 | } 41 | 42 | /* vim: set ft=logos ff=unix sw=4 ts=4 tw=80 expandtab: */ 43 | -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/CrashReporter_: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -f "/tmp/crashreporter_is_running" ]; then 4 | # Previous launch did not shutdown properly; invoke Safe Mode. 5 | unset DYLD_INSERT_LIBRARIES 6 | fi 7 | 8 | # NOTE: The following is modified from Cydia's launch script. 9 | C=/${0} 10 | C=${C%/*} 11 | exec "${C:-.}"/CrashReporter 2>>/tmp/crashreporter.log 12 | -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Default-568h@2x.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Default-700-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Default-700-568h@2x.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Default-700-Landscape@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Default-700-Landscape@2x.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Default-700-Portrait@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Default-700-Portrait@2x.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Default-700@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Default-700@2x.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Default-800-667h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Default-800-667h@2x.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Default-800-667h@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Default-800-667h@3x.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Default-800-Landscape-736h@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Default-800-Landscape-736h@3x.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Default-800-Portrait-736h@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Default-800-Portrait-736h@3x.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Default-Landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Default-Landscape.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Default-Portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Default-Portrait.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Default.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Default@2x.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Documentation/APPS.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

Apps are the most common type of program on iOS, and the type that users are most familiar with.

11 |

Most apps have an icon on the home screen, and display a full-screen window when launched.

12 |

iOS includes several built-in apps, such as Notes, Phone, and Weather. The home screen, called SpringBoard, is also an app (albeit a very special one).

13 |

Everything that can be installed via the App Store is an app. Apps can also be installed via Cydia, though most of the packages available via Cydia are not apps.

14 | 15 | 16 | -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Documentation/APP_EXTENSIONS.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

Introduced by Apple in iOS 8, app extensions are small programs, included with some apps, that provide functionality to other apps.

11 |

Types of app extensions include keyboard, share and Today extensions.

12 |

For more information, see Apple's App Extensions page.

13 | 14 | 15 | -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Documentation/CRASHED_PROCESS.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

This is the process - the instance of the program - that crashed.

11 | 12 | 13 | -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Documentation/EARLIER.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

Reports in this list are from earlier crashes.

11 |

Note that the reports may be for older versions of the program that crashed. You may wish to confirm that the same crash occurs with the most recent version of this program before bothering to report the issue to the developer.

12 | 13 | 14 | -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Documentation/LATEST.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

This is the most recent crash report available for this program.

11 |

Note that this may not represent the most recent crash; the report for the most recent crash may have already been deleted, or it may not have been recorded.

12 |

iOS limits the number of reports recorded for a given program. Once that limit is exceeded, any new reports will not be recorded. To resolve this, delete older crash reports for this program.

13 | 14 | 15 | -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Documentation/LOADED_BINARIES.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

This is a list of libraries that were loaded into the process that crashed.

11 |

On jailbroken devices, these libraries will most likely be tweaks, though they could be libraries that are used by tweaks or by the (crashed) program itself.

12 |

It is possible that one or more of these libraries were partly or completely responsible for the crash. However, CrashReporter's suspect-detection routine did not find any such evidence.

13 |

This list only includes libraries that were installed with the "dpkg" program (e.g. via Cydia). Libraries (and frameworks) included in iOS and libraries installed in any other way are not listed here (though these libraries can be found in the crash log itself).

14 |

Furthermore, trusted libraries, specifically the libraries that make up Substrate, are also not listed.

15 | 16 | 17 | -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Documentation/MAIN_SUSPECT.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

CrashReporter determined that the listed library was most likely responsible, partly or completely, for this crash.

11 |

Note the use of the term "suspect". CrashReporter's detection is not perfect, and it may be that this library - and its author - is innocent. Please keep that in mind when contacting the author.

12 | 13 | 14 | -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Documentation/OTHER_SUSPECTS.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

CrashReporter determined that the listed library may bear some responsibility for this crash.

11 |

As mentioned in the help for "Main Suspect", CrashReporter's detection is not perfect, and it may be that this library - and its author - is innocent. Please keep that in mind when contacting the author.

12 | 13 | 14 | -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Documentation/REPORT_OVERVIEW.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

What is a Crash?

11 |

A crash is when a program ends unexpectedly.

12 |

What Causes a Crash?

13 |

A crash can occur for any number of reasons, including:

14 |
    15 |
  • An issue (a bug) in the program itself.
  • 16 |
  • An issue in a library that the program depends upon.
  • 17 |
  • An issue in the operating system (iOS).
  • 18 |
  • An issue in a tweak (a type of library) loaded into the program.
  • 19 |
20 |

Reasons that a tweak might cause a crash include:

21 |
    22 |
  • An incompatibility with the installed version of iOS.
  • 23 |
  • An incompatibility with the version of the program that the tweak is being loaded into.
  • 24 |
  • An incompatibility with another tweak that is being loaded into the program.
  • 25 |
26 |

How to Resolve

27 |

Whether or not CrashReporter was able to determine any suspects, there are steps that should be taken before contacting a program or library's author.

28 |

First, it is important to determine exactly when a crash occurs. If the author is unable to replicate the crash, it may not be possible for him or her to provide a solution.

29 |

Second, if the crash occurs often and/or is easily repeatable, causes for the crash can be narrowed down by temporarily uninstalling any tweaks that may have been responsible and testing to see if the crash still occurs. Start with the main suspect, if any was found; uninstall the library, reboot the device, and test to see if the crash still occurs. Once the crashing stops, reinstall all but the last uninstalled tweak, reboot once more, and confirm that the crashing does not return. The last uninstalled library can then be assumed to be the most likely cause, or at least the trigger, for the crash.

30 |

If the crash still occurs even when all tweaks have been uninstalled, then the crash is most likely due to an issue in the program itself.

31 |

Contacting the Author

32 |

To contact the author of the program that crashed or of any of the listed libraries, tap on the desired item and choose "Contact author".

33 |

A contact form will be displayed with the crash log, syslog, and other information attached. A space is provided for entering information about the crash; this is where information regarding when the crash occurs and how to replicate it should be listed. Remember, the more information provided, the more likely the author will be able to help.

34 |

When finished filling out the form, an email will be generated. Please do not remove any of the information included in the email. Some authors may not respond if any of this information is missing.

35 |

A Note on Piracy

36 |

Please think twice before contacting an author if the installed program or library is pirated.

37 |

Users of pirated software may experience issues that would not occur with non-pirated versions. This may be intentional - the author may have included anti-piracy measures in the software. It may also be due to a conflict with another tweak; pirated packages break dpkg's conflict-detection.

38 | 39 | 40 | -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Documentation/SERVICES.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

Services are programs that do not have a graphical user interface (GUI).

11 |

The Services category includes agents and daemons, small programs that launch as a background process to perform a specific task. Such programs are often designed to run only when needed.

12 |

While there are tools available to disable services, it is generally a bad idea to do so. These services exist for a reason; disabling them may remove functionality or cause other programs to perform incorrectly. When experiencing trouble with a service, it is better to track down and solve the problem rather than just disable the service.

13 | 14 | 15 | -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Documentation/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #efefef; 3 | font-family: sans-serif; 4 | } 5 | 6 | h1 { 7 | font-size: 1.5em; 8 | } 9 | -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/FontAwesome.otf -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Icon-60.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Icon-60@2x.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Icon-60@3x.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Icon-76@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Icon-76@2x~ipad.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Icon-76~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Icon-76~ipad.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Icon-Small-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Icon-Small-40.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Icon-Small-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Icon-Small-40@2x.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Icon-Small-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Icon-Small-50.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Icon-Small-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Icon-Small-50@2x.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Icon-Small.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/Icon-Small@2x.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | 8 | CFBundleDisplayName 9 | CrashReporter 10 | 11 | CFBundleExecutable 12 | CrashReporter_ 13 | 14 | CFBundleIconFiles 15 | 16 | 17 | icon.png 18 | icon@2x.png 19 | 20 | 21 | icon-72.png 22 | icon-72@2x.png 23 | 24 | 25 | Icon-60 26 | 27 | 28 | Icon-76 29 | 30 | 31 | Icon-Small-40 32 | 33 | 34 | 35 | Icon-Small.png 36 | Icon-Small@2x.png 37 | 38 | 39 | Icon-Small-50.png 40 | Icon-Small-50@2x.png 41 | 42 | 43 | CFBundleIdentifier 44 | crash-reporter 45 | 46 | CFBundleInfoDictionaryVersion 47 | 6.0 48 | 49 | CFBundlePackageType 50 | APPL 51 | 52 | CFBundleSignature 53 | ???? 54 | 55 | CFBundleSupportedPlatforms 56 | 57 | iPhoneOS 58 | 59 | 60 | CFBundleURLTypes 61 | 62 | 63 | CFBundleURLName 64 | CrashReporter URL 65 | CFBundleURLSchemes 66 | 67 | crashreporter 68 | 69 | 70 | 71 | 72 | CFBundleVersion 73 | 1.16.0 74 | 75 | DTPlatformName 76 | iphoneos 77 | 78 | DTPlatformVersion 79 | 7.0 80 | 81 | DTSDKName 82 | iphoneos7.1 83 | 84 | LSRequiresIPhoneOS 85 | 1 86 | 87 | MinimumOSVersion 88 | 2.0 89 | 90 | PastieAuth 91 | burger 92 | 93 | SBAppUsesLocalNotifications 94 | 95 | 96 | UIAppFonts 97 | 98 | FontAwesome.otf 99 | 100 | 101 | UIDeviceFamily 102 | 103 | 1 104 | 2 105 | 106 | 107 | UILaunchImages 108 | 109 | 110 | UILaunchImageMinimumOSVersion 111 | 7.0 112 | UILaunchImageName 113 | Default-700 114 | UILaunchImageOrientation 115 | Portrait 116 | UILaunchImageSize 117 | {320, 480} 118 | 119 | 120 | UILaunchImageMinimumOSVersion 121 | 7.0 122 | UILaunchImageName 123 | Default-700-568h 124 | UILaunchImageOrientation 125 | Portrait 126 | UILaunchImageSize 127 | {320, 568} 128 | 129 | 130 | UILaunchImageMinimumOSVersion 131 | 8.0 132 | UILaunchImageName 133 | Default-800-667h 134 | UILaunchImageOrientation 135 | Portrait 136 | UILaunchImageSize 137 | {375, 667} 138 | 139 | 140 | UILaunchImageMinimumOSVersion 141 | 8.0 142 | UILaunchImageName 143 | Default-800-Portrait-736h 144 | UILaunchImageOrientation 145 | Portrait 146 | UILaunchImageSize 147 | {414, 736} 148 | 149 | 150 | UILaunchImageMinimumOSVersion 151 | 8.0 152 | UILaunchImageName 153 | Default-800-Landscape-736h 154 | UILaunchImageOrientation 155 | Landscape 156 | UILaunchImageSize 157 | {414, 736} 158 | 159 | 160 | 161 | UILaunchImages~ipad 162 | 163 | 164 | UILaunchImageName 165 | Default-700-Portrait 166 | UILaunchImageMinimumOSVersion 167 | 7.0 168 | UILaunchImageOrientation 169 | Portrait 170 | UILaunchImageSize 171 | {768, 1024} 172 | 173 | 174 | UILaunchImageName 175 | Default-700-Landscape 176 | UILaunchImageMinimumOSVersion 177 | 7.0 178 | UILaunchImageOrientation 179 | Landscape 180 | UILaunchImageSize 181 | {768, 1024} 182 | 183 | 184 | 185 | UISupportedInterfaceOrientations 186 | 187 | UIInterfaceOrientationPortrait 188 | UIInterfaceOrientationPortraitUpsideDown 189 | UIInterfaceOrientationLandscapeLeft 190 | UIInterfaceOrientationLandscapeRight 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/help_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/help_button.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/help_button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/help_button@2x.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/help_button@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/help_button@3x.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/icon-72.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/icon-72@2x.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/icon.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/icon@2x.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/navicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/navicon.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/navicon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Applications/CrashReporter.app/navicon@2x.png -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/notifier_: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Prevent loading of Substrate tweaks into the launched process. 4 | unset DYLD_INSERT_LIBRARIES 5 | 6 | # Get the absolute path to this script. 7 | CURRENT_DIR=$(cd $(dirname "$0") && pwd) 8 | 9 | # Execute the actual tool. 10 | exec "${CURRENT_DIR}"/notifier $* 11 | -------------------------------------------------------------------------------- /layout/Applications/CrashReporter.app/whitelist.plist: -------------------------------------------------------------------------------- 1 | { 2 | SignalFilters = ( 3 | SIGQUIT, 4 | SIGEMT, 5 | SIGTRAP, 6 | ); 7 | "ReverseFunctionFilters" = ( 8 | "__kill", 9 | ); 10 | FunctionFilters = ( 11 | "mach_msg_trap", 12 | "semaphore_wait_signal_trap", 13 | "semaphore_timedwait_signal_trap", 14 | "__semwait_signal", 15 | "select$DARWIN_EXTSN", 16 | accept, 17 | recvfrom, 18 | read, 19 | kevent, 20 | ); 21 | Filters = ( 22 | "/Applications/AppStore.app/AppStore", 23 | "/Applications/Calculator.app/Calculator", 24 | "/Applications/DemoApp.app/DemoApp", 25 | "/Applications/Maps.app/Maps", 26 | "/Applications/MobileAddressBook.app/MobileAddressBook", 27 | "/Applications/MobileCal.app/MobileCal", 28 | "/Applications/MobileMail.app/MobileMail", 29 | "/Applications/MobileMusicPlayer.app/MobileMusicPlayer", 30 | "/Applications/MobileNotes.app/MobileNotes", 31 | "/Applications/MobileSafari.app/MobileSafari", 32 | "/Applications/MobileSlideShow.app/MobileSlideShow", 33 | "/Applications/MobileStore.app/MobileStore", 34 | "/Applications/MobileTimer.app/MobileTimer", 35 | "/Applications/Preferences.app/Preferences", 36 | "/Applications/Stocks.app/Stocks", 37 | "/Applications/Weather.app/Weather", 38 | "/Applications/Web.app/Web", 39 | "/Applications/WebSheet.app/WebSheet", 40 | "/Applications/YouTube.app/YouTube", 41 | "/Library/MobileSubstrate/MobileSafety.dylib", 42 | "/Library/MobileSubstrate/MobileSubstrate.dylib", 43 | "/System/Library/CoreServices/SpringBoard.app/SpringBoard", 44 | "/usr/lib/libAccessibility.dylib", 45 | "/usr/lib/libafc.dylib", 46 | "/usr/lib/libbsm.0.dylib", 47 | "/usr/lib/libbz2.1.0.4.dylib", 48 | "/usr/lib/libcharset.1.0.0.dylib", 49 | "/usr/lib/libcharset.1.dylib", 50 | "/usr/lib/libedit.2.dylib", 51 | "/usr/lib/libexslt.0.dylib", 52 | "/usr/lib/libform.5.4.dylib", 53 | "/usr/lib/libgcc_s.1.dylib", 54 | "/usr/lib/libgermantok.dylib", 55 | "/usr/lib/libiconv.2.4.0.dylib", 56 | "/usr/lib/libiconv.2.dylib", 57 | "/usr/lib/libicucore.A.dylib", 58 | "/usr/lib/libIOAccessoryManager.dylib", 59 | "/usr/lib/libIOKit.A.dylib", 60 | "/usr/lib/libipsec.A.dylib", 61 | "/usr/lib/liblangid.dylib", 62 | "/usr/lib/liblockdown.dylib", 63 | "/usr/lib/libmecab_em.dylib", 64 | "/usr/lib/libmecabra.dylib", 65 | "/usr/lib/libmis.dylib", 66 | "/usr/lib/libncurses.5.4.dylib", 67 | "/usr/lib/libobjc.A.dylib", 68 | "/usr/lib/libQLCharts.dylib", 69 | "/usr/lib/libresolv.9.dylib", 70 | "/usr/lib/libsqlite3.dylib", 71 | "/usr/lib/libstdc++.6.0.9.dylib", 72 | "/usr/lib/libstdc++.6.dylib", 73 | "/usr/lib/libsubstrate.dylib", 74 | "/usr/lib/libSystem.B.dylib", 75 | "/usr/lib/libSystem.dylib", 76 | "/usr/lib/libtidy.A.dylib", 77 | "/usr/lib/libutil1.0.dylib", 78 | "/usr/lib/libxml2.2.dylib", 79 | "/usr/lib/libxslt.1.dylib", 80 | "/usr/lib/libz.1.2.3.dylib", 81 | "/usr/lib/libz.1.dylib", 82 | "/usr/lib/system/libkxld.dylib", 83 | "/usr/lib/libcycript.dylib", 84 | "/usr/lib/libffi.dylib", 85 | "/Library/MobileSubstrate/DynamicLibraries/CydgetLoader.dylib", 86 | ); 87 | PrefixFilters = ( 88 | "/System/Library/Frameworks/", 89 | "/System/Library/PrivateFrameworks/", 90 | "/System/Library/TextInput/", 91 | ); 92 | } -------------------------------------------------------------------------------- /layout/DEBIAN/control: -------------------------------------------------------------------------------- 1 | Package: crash-reporter 2 | Version: 1.16.0 3 | Architecture: iphoneos-arm 4 | Pre-Depends: firmware (>=4.0) 5 | Depends: coreutils, dpkg, grep, jp.ashikase.libcrashreport (>= 1.1.0), jp.ashikase.libpackageinfo (>= 1.1.0), jp.ashikase.techsupport (>= 1.5.0), mobilesubstrate, preferenceloader 6 | Section: System 7 | Tag: purpose::uikit, role::enduser 8 | Name: CrashReporter 9 | Description: Send useful crash info to developers. 10 | Homepage: http://github/ashikase/CrashReporter/ 11 | Depiction: http://moreinfo.thebigboss.org/moreinfo/depiction.php?file=crashreporterData 12 | dev: ashikase 13 | Author: Lance Fetters (ashikase) 14 | Maintainer: BigBoss 15 | Sponsor: thebigboss.org 16 | -------------------------------------------------------------------------------- /layout/DEBIAN/crash_reporter: -------------------------------------------------------------------------------- 1 | link url http://github.com/ashikase/CrashReporter as "Visit project site" 2 | -------------------------------------------------------------------------------- /layout/Library/MobileSubstrate/DynamicLibraries/CrashReporter.dylib: -------------------------------------------------------------------------------- 1 | /Applications/CrashReporter.app/monitor.dylib -------------------------------------------------------------------------------- /layout/Library/MobileSubstrate/DynamicLibraries/CrashReporter.plist: -------------------------------------------------------------------------------- 1 | { Filter = { Executables = ( "ReportCrash" ); }; } 2 | -------------------------------------------------------------------------------- /layout/Library/MobileSubstrate/DynamicLibraries/CrashReporterScanner.dylib: -------------------------------------------------------------------------------- 1 | /Applications/CrashReporter.app/scanner.dylib -------------------------------------------------------------------------------- /layout/Library/MobileSubstrate/DynamicLibraries/CrashReporterScanner.plist: -------------------------------------------------------------------------------- 1 | { Filter = { Bundles = ( "com.apple.springboard" ); }; } 2 | -------------------------------------------------------------------------------- /layout/Library/PreferenceLoader/Preferences/CrashReporter/CrashReporter.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | title 6 | CrashReporter 7 | entry 8 | 9 | cell 10 | PSLinkCell 11 | label 12 | CrashReporter 13 | icon 14 | icon.png 15 | 16 | items 17 | 18 | 19 | cell 20 | PSGroupCell 21 | label 22 | TITLE_NOTIFY_SEND 23 | 24 | 25 | cell 26 | PSSwitchCell 27 | label 28 | PREF_NOTIFY_EXCESS_CPU 29 | key 30 | notifyExcessiveCPU 31 | default 32 | 33 | defaults 34 | crash-reporter 35 | PostNotification 36 | crash-reporter-settingsChanged 37 | 38 | 39 | cell 40 | PSSwitchCell 41 | label 42 | PREF_NOTIFY_EXCESS_MEMORY 43 | key 44 | notifyExcessiveMemory 45 | default 46 | 47 | defaults 48 | crash-reporter 49 | PostNotification 50 | crash-reporter-settingsChanged 51 | 52 | 68 | 69 | cell 70 | PSSwitchCell 71 | label 72 | PREF_NOTIFY_EXECUTION_TIMEOUTS 73 | key 74 | notifyExecutionTimeouts 75 | default 76 | 77 | defaults 78 | crash-reporter 79 | PostNotification 80 | crash-reporter-settingsChanged 81 | 82 | 83 | cell 84 | PSSwitchCell 85 | label 86 | PREF_NOTIFY_LOW_MEMORY 87 | key 88 | notifyLowMemory 89 | default 90 | 91 | defaults 92 | crash-reporter 93 | PostNotification 94 | crash-reporter-settingsChanged 95 | 96 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /layout/Library/PreferenceLoader/Preferences/CrashReporter/icon.png: -------------------------------------------------------------------------------- 1 | ../../../../Applications/CrashReporter.app/Icon-Small.png -------------------------------------------------------------------------------- /layout/Library/PreferenceLoader/Preferences/CrashReporter/icon@2x.png: -------------------------------------------------------------------------------- 1 | ../../../../Applications/CrashReporter.app/Icon-Small@2x.png -------------------------------------------------------------------------------- /layout/Library/PreferenceLoader/Preferences/CrashReporter/icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashikase/CrashReporter/03e37d05aa8dcc24ec372137caf9452b348f1809/layout/Library/PreferenceLoader/Preferences/CrashReporter/icon@3x.png -------------------------------------------------------------------------------- /monitor/Makefile: -------------------------------------------------------------------------------- 1 | TWEAK_NAME = monitor 2 | monitor_INSTALL_PATH = /Applications/CrashReporter.app 3 | monitor_FILES = Tweak.mm 4 | monitor_PRIVATE_FRAMEWORKS = SpringBoardServices 5 | 6 | ARCHS = armv6 armv7 armv7s arm64 7 | 8 | include $(THEOS_MAKE_PATH)/common.mk 9 | include $(THEOS_MAKE_PATH)/tweak.mk 10 | -------------------------------------------------------------------------------- /monitor/Tweak.xm: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: monitor 3 | * Type: iOS extension 4 | * Desc: Detects when ReportCrash writes a crash log file and launches 5 | * notifier in order to send a local notification to the user. 6 | * 7 | * Author: Lance Fetters (aka. ashikase) 8 | * License: GPL v3 (See LICENSE file for details) 9 | */ 10 | 11 | #include 12 | 13 | #include "paths.h" 14 | 15 | @interface NSTask : NSObject 16 | + (NSTask *)launchedTaskWithLaunchPath:(NSString *)path arguments:(NSArray *)arguments; 17 | @end 18 | 19 | static void launchNotifierWithPath(NSString *filepath) { 20 | // NOTE: Must be done via a separate binary as a certain entitlement 21 | // is required for sending local notifications by proxy. 22 | NSString *launchPath = @"/Applications/CrashReporter.app/notifier_"; 23 | if ([[NSFileManager defaultManager] isExecutableFileAtPath:launchPath]) { 24 | NSArray *arguments = [NSArray arrayWithObject:filepath]; 25 | if ([NSTask launchedTaskWithLaunchPath:launchPath arguments:arguments] == nil) { 26 | NSLog(@"ERROR: Unable to launch notifier task."); 27 | } 28 | } else { 29 | NSLog(@"ERROR: notifier binary is missing or is not executable."); 30 | } 31 | } 32 | 33 | static BOOL crashLogFound$ = NO; 34 | static BOOL crashLogWritten$ = NO; 35 | static NSString *filepath$ = nil; 36 | static int fd$ = -1; 37 | 38 | static void cleanup() { 39 | [filepath$ release]; 40 | filepath$ = nil; 41 | } 42 | 43 | static BOOL hasPrefix(const char *string, const char *suffix) { 44 | return (strncmp(string, suffix, strlen(suffix)) == 0); 45 | } 46 | 47 | static BOOL hasSuffix(const char *string, const char *suffix) { 48 | if ((string != NULL) && (suffix != NULL)) { 49 | size_t lenString = strlen(string); 50 | size_t lenSuffix = strlen(suffix); 51 | if (lenSuffix <= lenString) { 52 | return (strncmp(string + lenString - lenSuffix, suffix, lenSuffix) == 0); 53 | } 54 | } 55 | 56 | return NO; 57 | } 58 | 59 | // NOTE: Third parameter is technically variadic (...), but the only time when 60 | // more than two parameters are passed is when oflag includes O_CREAT, 61 | // in which case three parameters are passed (the third being mode_t). 62 | int (*_open)(const char *, int, mode_t) = NULL; 63 | int $open(const char *path, int oflag, mode_t mode) { 64 | int fd = _open(path, oflag, mode); 65 | if (fd >= 0) { 66 | if (!crashLogFound$) { 67 | if ((hasPrefix(path, kCrashLogDirectoryForMobile) || hasPrefix(path, kCrashLogDirectoryForRoot)) && 68 | (hasSuffix(path, "plist") || hasSuffix(path, "ips"))) { 69 | crashLogFound$ = YES; 70 | filepath$ = [[NSString alloc] initWithCString:path encoding:NSUTF8StringEncoding]; 71 | fd$ = fd; 72 | } 73 | } 74 | } 75 | return fd; 76 | } 77 | 78 | ssize_t (*_write)(int, const void *, size_t) = NULL; 79 | ssize_t $write(int fildes, const void *buf, size_t nbyte) { 80 | ssize_t bytesWritten = _write(fildes, buf, nbyte); 81 | if ((fd$ != -1) && (fd$ == fildes)) { 82 | if (bytesWritten == nbyte) { 83 | crashLogWritten$ = YES; 84 | } 85 | } 86 | return bytesWritten; 87 | } 88 | 89 | int (*_close)(int) = NULL; 90 | int $close(int fildes) { 91 | int result = _close(fildes); 92 | if ((fd$ != -1) && (fd$ == fildes)) { 93 | if (result != -1) { 94 | // NOTE: Must reset file descriptor befoure launching task. 95 | // Failure to do so results in infinite recursion of the 96 | // close() method (for a yet unresearched reason). 97 | fd$ = -1; 98 | 99 | // NOTE: As of iOS 9.3, ReportCrash (CrashReporterSupport) writes 100 | // the log file to a temporary file, which is then renamed 101 | // at a later point. 102 | if (IOS_LT(9_3)) { 103 | if (crashLogWritten$) { 104 | launchNotifierWithPath(filepath$); 105 | } 106 | cleanup(); 107 | } 108 | } 109 | } 110 | return result; 111 | } 112 | 113 | %hook NSFileManager %group GFirmware_GTE_93 114 | 115 | - (BOOL)moveItemAtURL:(NSURL *)srcURL toURL:(NSURL *)dstURL error:(NSError * _Nullable *)error { 116 | BOOL moved = %orig(); 117 | 118 | if ([[srcURL path] isEqualToString:filepath$]) { 119 | if (moved) { 120 | launchNotifierWithPath([dstURL path]); 121 | } 122 | cleanup(); 123 | } 124 | 125 | return moved; 126 | } 127 | 128 | %end %end 129 | 130 | __attribute__((constructor)) static void init() { 131 | NSAutoreleasePool *pool = [NSAutoreleasePool new]; 132 | 133 | // Lookup and hook symbol. 134 | // NOTE: The dynamic lookup is not necessary, but better safe than sorry. 135 | void *handle = dlopen("/usr/lib/system/libsystem_kernel.dylib", (RTLD_LAZY | RTLD_NOLOAD)); 136 | if (handle != NULL) { 137 | int (*openPtr)(const char *, int, mode_t) = reinterpret_cast(dlsym(handle, "open")); 138 | if (openPtr != NULL) { 139 | MSHookFunction(openPtr, &$open, &_open); 140 | } 141 | ssize_t (*writePtr)(int, const void *, size_t) = reinterpret_cast(dlsym(handle, "write")); 142 | if (writePtr != NULL) { 143 | MSHookFunction(writePtr, &$write, &_write); 144 | } 145 | int (*closePtr)(int) = reinterpret_cast(dlsym(handle, "close")); 146 | if (closePtr != NULL) { 147 | MSHookFunction(closePtr, &$close, &_close); 148 | } 149 | 150 | if ((openPtr == NULL) || (writePtr == NULL) || (closePtr == NULL)) { 151 | fprintf(stderr, "ERROR: Was unable to find symbols for required functions.\n"); 152 | } 153 | 154 | dlclose(handle); 155 | } 156 | 157 | if (IOS_GTE(9_3)) { 158 | %init(GFirmware_GTE_93); 159 | } 160 | 161 | [pool release]; 162 | } 163 | 164 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 165 | -------------------------------------------------------------------------------- /monitor/monitor.plist: -------------------------------------------------------------------------------- 1 | ../layout/Library/MobileSubstrate/DynamicLibraries/CrashReporter.plist -------------------------------------------------------------------------------- /notifier/Entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | platform-application 6 | 7 | com.apple.private.skip-library-validation 8 | 9 | com.apple.private.security.no-container 10 | 11 | com.apple.localnotification.schedulingproxy 12 | 13 | com.apple.usernotification.notificationschedulerproxy 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /notifier/Makefile: -------------------------------------------------------------------------------- 1 | TOOL_NAME = notifier 2 | notifier_INSTALL_PATH = /Applications/CrashReporter.app 3 | notifier_FILES = \ 4 | ../common/crashlog_util.m \ 5 | ../common/exec_as_root.m \ 6 | main.m 7 | notifier_LDFLAGS = -lcrashreport 8 | notifier_PRIVATE_FRAMEWORKS = SpringBoardServices 9 | notifier_CODESIGN_FLAGS="-SEntitlements.plist" 10 | 11 | include $(THEOS_MAKE_PATH)/common.mk 12 | include $(THEOS_MAKE_PATH)/tool.mk 13 | 14 | after-clean:: 15 | - rm -rf $(THEOS_PROJECT_DIR)/notifier/Common 16 | - rm -rf $(THEOS_PROJECT_DIR)/notifier/common 17 | -------------------------------------------------------------------------------- /scanner/CRAlertItem.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: scanner 3 | * Type: iOS extension 4 | * Desc: Scans tweaks for known issues. 5 | * 6 | * Author: Lance Fetters (aka. ashikase) 7 | * License: GPL v3 (See LICENSE file for details) 8 | */ 9 | 10 | @interface SBAlertItem : NSObject 11 | - (void)dismiss; 12 | @end 13 | 14 | @interface SBAlertItem (Firmware_LT_100) 15 | - (UIAlertView *)alertSheet; 16 | @end 17 | 18 | @interface SBAlertItem (Firmware_GTE_100) 19 | - (id)alertController; 20 | - (void)deactivateForButton; 21 | @end 22 | 23 | @interface SBAlertItemsController : NSObject 24 | + (id)sharedInstance; 25 | - (void)activateAlertItem:(id)item; 26 | @end 27 | 28 | typedef NS_ENUM(NSInteger, UIAlertActionStyle) { 29 | UIAlertActionStyleDefault = 0, 30 | UIAlertActionStyleCancel, 31 | UIAlertActionStyleDestructive 32 | }; 33 | 34 | @interface UIAlertAction : NSObject 35 | + (instancetype)actionWithTitle:(NSString *)title style:(UIAlertActionStyle)style handler:(void (^)(UIAlertAction *action))handler; 36 | @end 37 | 38 | @interface UIAlertController : UIViewController 39 | - (void)addAction:(UIAlertAction *)action; 40 | @property (nonatomic, copy) NSString *title; 41 | @property (nonatomic, copy) NSString *message; 42 | @end 43 | 44 | @interface CRAlertItem : SBAlertItem @end 45 | 46 | void init_CRAlertItem(); 47 | 48 | /* vim: set ft=logos ff=unix sw=4 ts=4 expandtab tw=80: */ 49 | -------------------------------------------------------------------------------- /scanner/CRAlertItem.xm: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: scanner 3 | * Type: iOS extension 4 | * Desc: Scans tweaks for known issues. 5 | * 6 | * Author: Lance Fetters (aka. ashikase) 7 | * License: GPL v3 (See LICENSE file for details) 8 | */ 9 | 10 | #import "CRAlertItem.h" 11 | 12 | %hook CRAlertItem 13 | 14 | %new 15 | + (void)show { 16 | CRAlertItem *alert = [[self alloc] init]; 17 | [[objc_getClass("SBAlertItemsController") sharedInstance] activateAlertItem:alert]; 18 | [alert release]; 19 | } 20 | 21 | // Prevent alert from showing on lock screen. 22 | - (BOOL)shouldShowInLockScreen { return NO; } 23 | 24 | // Prevent automatic dismissal of alert due to locking device. 25 | // NOTE: Requires different hooks for different firmware versions. 26 | %group GFirmware_GTE_60 27 | - (BOOL)behavesSuperModally { return YES; } 28 | %end 29 | 30 | %group GFirmware_GTE_50_LT_60 31 | - (BOOL)reappearsAfterLock { return YES; } 32 | %end 33 | 34 | %group GFirmware_GTE_40_LT_50 35 | 36 | - (void)didDeactivateForReason:(int)reason { 37 | %orig(); 38 | 39 | if (reason == 0) { 40 | // Was deactivated due to lock, not user interaction 41 | // FIXME: Is there no better way to get the alert to reappear? 42 | [[objc_getClass("SBAlertItemsController") sharedInstance] activateAlertItem:self]; 43 | } 44 | } 45 | 46 | %end 47 | 48 | %end 49 | 50 | void init_CRAlertItem() { 51 | @autoreleasepool { 52 | // Make sure class has not already been initialized 53 | if (objc_getClass("CRAlertItem") != Nil) return; 54 | 55 | // Register new subclass 56 | Class $SuperClass = objc_getClass("SBAlertItem"); 57 | if ($SuperClass != Nil) { 58 | Class klass = objc_allocateClassPair($SuperClass, "CRAlertItem", 0); 59 | if (klass != Nil) { 60 | objc_registerClassPair(klass); 61 | 62 | %init(); 63 | 64 | if (IOS_LT(5_0)) { 65 | %init(GFirmware_GTE_40_LT_50); 66 | } else if (IOS_LT(6_0)) { 67 | %init(GFirmware_GTE_50_LT_60); 68 | } else { 69 | %init(GFirmware_GTE_60); 70 | } 71 | } 72 | } 73 | } 74 | } 75 | 76 | /* vim: set ft=logos ff=unix sw=4 ts=4 expandtab tw=80: */ 77 | -------------------------------------------------------------------------------- /scanner/CRCannotEmailAlertItem.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: scanner 3 | * Type: iOS extension 4 | * Desc: Scans tweaks for known issues. 5 | * 6 | * Author: Lance Fetters (aka. ashikase) 7 | * License: GPL v3 (See LICENSE file for details) 8 | */ 9 | 10 | #import "CRAlertItem.h" 11 | 12 | @interface CRCannotEmailAlertItem : CRAlertItem 13 | @end 14 | 15 | /* vim: set ft=logos ff=unix sw=4 ts=4 expandtab tw=80: */ 16 | -------------------------------------------------------------------------------- /scanner/CRCannotEmailAlertItem.xm: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: scanner 3 | * Type: iOS extension 4 | * Desc: Scans tweaks for known issues. 5 | * 6 | * Author: Lance Fetters (aka. ashikase) 7 | * License: GPL v3 (See LICENSE file for details) 8 | */ 9 | 10 | #import "CRCannotEmailAlertItem.h" 11 | 12 | %hook CRCannotEmailAlertItem 13 | 14 | #pragma mark - Overrides 15 | 16 | - (void)configure:(BOOL)configure requirePasscodeForActions:(BOOL)require { 17 | 18 | NSString *title = @"CrashReporter"; 19 | NSString *message = @"Cannot send email from this device."; 20 | NSString *buttonTitle = @"Dismiss"; 21 | 22 | if (IOS_LT(10_0)) { 23 | UIAlertView *alertView = [self alertSheet]; 24 | [alertView setTitle:title]; 25 | [alertView setMessage:message]; 26 | [alertView addButtonWithTitle:buttonTitle]; 27 | } else { 28 | UIAlertController *alertController = [self alertController]; 29 | [alertController setTitle:title]; 30 | [alertController setMessage:message]; 31 | [alertController addAction:[objc_getClass("UIAlertAction") actionWithTitle:buttonTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { 32 | [self deactivateForButton]; 33 | }]]; 34 | } 35 | } 36 | 37 | %end 38 | 39 | %ctor { 40 | @autoreleasepool { 41 | // Initialize super class, if necessary. 42 | init_CRAlertItem(); 43 | 44 | // Register new subclass. 45 | Class $SuperClass = objc_getClass("CRAlertItem"); 46 | if ($SuperClass != Nil) { 47 | Class klass = objc_allocateClassPair($SuperClass, "CRCannotEmailAlertItem", 0); 48 | if (klass != Nil) { 49 | objc_registerClassPair(klass); 50 | 51 | %init(); 52 | } 53 | } 54 | } 55 | } 56 | 57 | /* vim: set ft=logos ff=unix sw=4 ts=4 expandtab tw=80: */ 58 | -------------------------------------------------------------------------------- /scanner/CRMailViewController.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: scanner 3 | * Type: iOS extension 4 | * Desc: Scans tweaks for known issues. 5 | * 6 | * Author: Lance Fetters (aka. ashikase) 7 | * License: GPL v3 (See LICENSE file for details) 8 | */ 9 | 10 | #import 11 | 12 | typedef enum : NSUInteger { 13 | CRMailReasonMissingFilter 14 | } CRMailReason; 15 | 16 | @class PIPackage; 17 | 18 | @interface CRMailViewController : UIViewController 19 | + (void)showWithPackage:(PIPackage *)package reason:(CRMailReason)reason; 20 | @end 21 | 22 | /* vim: set ft=objc ff=unix sw=4 ts=4 expandtab tw=80: */ 23 | -------------------------------------------------------------------------------- /scanner/CRMailViewController.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: scanner 3 | * Type: iOS extension 4 | * Desc: Scans tweaks for known issues. 5 | * 6 | * Author: Lance Fetters (aka. ashikase) 7 | * License: GPL v3 (See LICENSE file for details) 8 | */ 9 | 10 | #import "CRMailViewController.h" 11 | 12 | #import 13 | #import 14 | #include 15 | #import "CRCannotEmailAlertItem.h" 16 | 17 | static NSString *addressFromString(NSString *string) { 18 | NSString *address = nil; 19 | 20 | if (string != nil) { 21 | NSRange leftAngleRange = [string rangeOfString:@"<" options:NSBackwardsSearch]; 22 | if (leftAngleRange.location != NSNotFound) { 23 | NSRange rightAngleRange = [string rangeOfString:@">" options:NSBackwardsSearch]; 24 | if (rightAngleRange.location != NSNotFound) { 25 | if (leftAngleRange.location < rightAngleRange.location) { 26 | NSRange range = NSMakeRange(leftAngleRange.location + 1, rightAngleRange.location - leftAngleRange.location - 1); 27 | address = [string substringWithRange:range]; 28 | } 29 | } 30 | } 31 | } 32 | 33 | return address; 34 | } 35 | 36 | @interface CRMailViewController () 37 | @property (nonatomic, retain) UIWindow *window; 38 | @end 39 | 40 | @implementation CRMailViewController { 41 | PIPackage *package_; 42 | CRMailReason reason_; 43 | 44 | BOOL hasAlreadyAppeared_; 45 | } 46 | 47 | @synthesize window = window_; 48 | 49 | + (void)showWithPackage:(PIPackage *)package reason:(CRMailReason)reason { 50 | BOOL canSendMail = [MFMailComposeViewController canSendMail]; 51 | if (canSendMail) { 52 | CRMailViewController *viewController = [[CRMailViewController alloc] initWithPackage:package reason:reason]; 53 | 54 | UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 55 | window.rootViewController = viewController; 56 | [window makeKeyAndVisible]; 57 | 58 | viewController.window = window; 59 | [viewController release]; 60 | [window release]; 61 | } else { 62 | [objc_getClass("CRCannotEmailAlertItem") show]; 63 | } 64 | } 65 | 66 | - (instancetype)initWithPackage:(PIPackage *)package reason:(CRMailReason)reason { 67 | self = [super init]; 68 | if (self != nil) { 69 | package_ = [package retain]; 70 | reason_ = reason; 71 | } 72 | return self; 73 | } 74 | 75 | - (void)dealloc { 76 | [package_ release]; 77 | [window_ release]; 78 | [super dealloc]; 79 | } 80 | 81 | - (void)viewDidAppear:(BOOL)animated { 82 | if (!hasAlreadyAppeared_) { 83 | // Setup mail controller. 84 | MFMailComposeViewController *controller = [[MFMailComposeViewController alloc] init]; 85 | [controller setMailComposeDelegate:self]; 86 | [controller setMessageBody:[self body] isHTML:NO]; 87 | [controller setSubject:[self subject]]; 88 | 89 | NSString *author = addressFromString(package_.author); 90 | if (author != nil) { 91 | [controller setToRecipients:[NSArray arrayWithObject:author]]; 92 | } 93 | NSString *maintainer = addressFromString(package_.maintainer); 94 | if (maintainer != nil) { 95 | [controller setCcRecipients:[NSArray arrayWithObject:maintainer]]; 96 | } 97 | 98 | // Present the mail controller for confirmation. 99 | if (IOS_LT(5_0)) { 100 | [self presentModalViewController:controller animated:YES]; 101 | } else { 102 | [self presentViewController:controller animated:YES completion:nil]; 103 | } 104 | [controller release]; 105 | 106 | hasAlreadyAppeared_ = YES; 107 | } 108 | } 109 | 110 | - (NSString *)subject { 111 | NSString *string = nil; 112 | switch (reason_) { 113 | case CRMailReasonMissingFilter: 114 | string = @"Missing Filter File"; 115 | break; 116 | default: 117 | string = @""; 118 | break; 119 | } 120 | return [NSString stringWithFormat:@"CrashReporter: %@: %@", package_.name, string]; 121 | } 122 | 123 | - (NSString *)body { 124 | NSString *string = nil; 125 | switch (reason_) { 126 | case CRMailReasonMissingFilter: 127 | string = [NSString stringWithFormat: 128 | @"Your tweak, \"%@\" (%@, version %@) is missing a filter file.\n\n" 129 | "Without a filter file, your tweak will be loaded into *every* process controlled by launchd, not only apps but daemons as well. This can lead to crashing and other issues.\n\n" 130 | "Even if you absolutely require that your tweak be loaded into all processes, please do so via an appropriately-constructed filter file.\n\n" 131 | "Note that even if your tweak operates properly when loaded into daemons, it may cause other tweaks to also be loaded, and those other tweaks may *not* be designed to work with daemons. This is especially a problem if your tweak links to UIKit. If your tweak uses UIKit, be sure to either avoid targetting non-apps (e.g. daemons), or avoid directly linking to UIKit (use dlopen() instead, making sure to do so outside of the tweak's constructor).", 132 | package_.name, package_.identifier, package_.version]; 133 | break; 134 | default: 135 | string = @""; 136 | break; 137 | } 138 | return [string stringByAppendingString:@"\n\n/* Generated by CrashReporter - cydia://package/crash-reporter */"]; 139 | } 140 | 141 | #pragma mark - Delegate (MFMailComposeViewControllerDelegate) 142 | 143 | - (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error { 144 | // Dismiss controller and presenting window. 145 | if (IOS_LT(5_0)) { 146 | [self dismissModalViewControllerAnimated:NO]; 147 | UIWindow *window = self.window; 148 | window.hidden = YES; 149 | window.rootViewController = nil; 150 | } else { 151 | [self dismissViewControllerAnimated:YES completion:^{ 152 | UIWindow *window = self.window; 153 | window.hidden = YES; 154 | window.rootViewController = nil; 155 | }]; 156 | } 157 | } 158 | 159 | @end 160 | 161 | /* vim: set ft=logos ff=unix sw=4 ts=4 expandtab tw=80: */ 162 | -------------------------------------------------------------------------------- /scanner/CRMissingFilterAlertItem.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: scanner 3 | * Type: iOS extension 4 | * Desc: Scans tweaks for known issues. 5 | * 6 | * Author: Lance Fetters (aka. ashikase) 7 | * License: GPL v3 (See LICENSE file for details) 8 | */ 9 | 10 | #import "CRAlertItem.h" 11 | 12 | @interface CRMissingFilterAlertItem : CRAlertItem 13 | + (void)showForPath:(NSString *)path; 14 | @end 15 | 16 | /* vim: set ft=logos ff=unix sw=4 ts=4 expandtab tw=80: */ 17 | -------------------------------------------------------------------------------- /scanner/CRMissingFilterAlertItem.xm: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: scanner 3 | * Type: iOS extension 4 | * Desc: Scans tweaks for known issues. 5 | * 6 | * Author: Lance Fetters (aka. ashikase) 7 | * License: GPL v3 (See LICENSE file for details) 8 | */ 9 | 10 | #import "CRMissingFilterAlertItem.h" 11 | 12 | #import 13 | #import "CRMailViewController.h" 14 | 15 | @interface CRMissingFilterAlertItem () 16 | @property (nonatomic, copy) NSString *path; 17 | @end 18 | 19 | static void presentEmailForPath(NSString *path) { 20 | PIDebianPackage *package = [PIDebianPackage packageForFile:path]; 21 | [CRMailViewController showWithPackage:package reason:CRMailReasonMissingFilter]; 22 | } 23 | 24 | %hook CRMissingFilterAlertItem 25 | 26 | %new 27 | + (void)showForPath:(NSString *)path { 28 | CRMissingFilterAlertItem *alert = [[self alloc] init]; 29 | alert.path = path; 30 | [[objc_getClass("SBAlertItemsController") sharedInstance] activateAlertItem:alert]; 31 | [alert release]; 32 | } 33 | 34 | #pragma mark - Overrides 35 | 36 | - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { 37 | if (alertView.tag == 1) { 38 | if (buttonIndex == 0) { 39 | presentEmailForPath(self.path); 40 | } 41 | } 42 | 43 | // Call original implementation to dismiss the alert item. 44 | %orig(); 45 | } 46 | 47 | - (void)configure:(BOOL)configure requirePasscodeForActions:(BOOL)require { 48 | NSString *title = @"CrashReporter"; 49 | NSString *message = nil; 50 | NSString *buttonTitle = @"Dismiss"; 51 | NSString *otherButtonTitle = @"Contact Developer"; 52 | 53 | NSString *path = self.path; 54 | PIDebianPackage *package = [PIDebianPackage packageForFile:path]; 55 | if (package != nil) { 56 | message = [NSString stringWithFormat: 57 | @"The following tweak has no filter file:\n\n" 58 | "%@\n\n" 59 | "This can lead to crashing and other issues on your device.\n\n" 60 | "It is strongly recommended that you report this to the developer of the tweak.", 61 | package.name]; 62 | } else { 63 | message = [NSString stringWithFormat: 64 | @"The following tweak has no filter file:\n\n" 65 | "%@\n\n" 66 | "This can lead to crashing and other issues on your device.\n\n" 67 | "It is strongly recommended that you report this to the developer of the tweak.\n\n" 68 | "(The package that this tweak is from cannot be found or is no longer installed. You will need to determine for yourself whom to contact.)", 69 | [path lastPathComponent]]; 70 | } 71 | 72 | if (IOS_LT(10_0)) { 73 | UIAlertView *alertView = [self alertSheet]; 74 | [alertView setDelegate:self]; 75 | [alertView setTitle:title]; 76 | [alertView setMessage:message]; 77 | 78 | if (package != nil) { 79 | [alertView setTag:1]; 80 | [alertView addButtonWithTitle:otherButtonTitle]; 81 | } 82 | 83 | [alertView addButtonWithTitle:buttonTitle]; 84 | } else { 85 | UIAlertController *alertController = [self alertController]; 86 | [alertController setTitle:title]; 87 | [alertController setMessage:message]; 88 | 89 | if (package != nil) { 90 | [alertController addAction:[objc_getClass("UIAlertAction") actionWithTitle:otherButtonTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { 91 | presentEmailForPath(self.path); 92 | [self deactivateForButton]; 93 | }]]; 94 | } 95 | 96 | [alertController addAction:[objc_getClass("UIAlertAction") actionWithTitle:buttonTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { 97 | [self deactivateForButton]; 98 | }]]; 99 | } 100 | } 101 | 102 | - (void)dealloc { 103 | NSString *path_ = nil; 104 | (void)object_getInstanceVariable(self, "path_", (void **)&path_); 105 | [path_ release]; 106 | 107 | %orig(); 108 | } 109 | 110 | #pragma mark - Properties 111 | 112 | %new 113 | - (NSString *)path { 114 | NSString *path_ = nil; 115 | (void)object_getInstanceVariable(self, "path_", (void **)&path_); 116 | return path_; 117 | } 118 | 119 | %new 120 | - (void)setPath:(NSString *)path { 121 | NSString *path_ = nil; 122 | (void)object_getInstanceVariable(self, "path_", (void **)&path_); 123 | if (path_ != path) { 124 | [path_ release]; 125 | [path copy]; 126 | (void)object_setInstanceVariable(self, "path_", path); 127 | } 128 | } 129 | 130 | %end 131 | 132 | %ctor { 133 | @autoreleasepool { 134 | // Initialize super class, if necessary. 135 | init_CRAlertItem(); 136 | 137 | // Register new subclass. 138 | Class $SuperClass = objc_getClass("CRAlertItem"); 139 | if ($SuperClass != Nil) { 140 | Class klass = objc_allocateClassPair($SuperClass, "CRMissingFilterAlertItem", 0); 141 | if (klass != Nil) { 142 | // Add instance variables. 143 | const char *type = "@"; 144 | NSUInteger size, align; 145 | NSGetSizeAndAlignment(type, &size, &align); 146 | class_addIvar(klass, "path_", size, align, type); 147 | 148 | // Finish registering subclass. 149 | objc_registerClassPair(klass); 150 | 151 | %init(); 152 | } 153 | } 154 | } 155 | } 156 | 157 | /* vim: set ft=logos ff=unix sw=4 ts=4 expandtab tw=80: */ 158 | -------------------------------------------------------------------------------- /scanner/Makefile: -------------------------------------------------------------------------------- 1 | TWEAK_NAME = scanner 2 | scanner_INSTALL_PATH = /Applications/CrashReporter.app 3 | scanner_FILES = \ 4 | CRAlertItem.mm \ 5 | CRCannotEmailAlertItem.mm \ 6 | CRMailViewController.m \ 7 | CRMissingFilterAlertItem.mm \ 8 | Tweak.mm 9 | scanner_FRAMEWORKS = MessageUI UIKit 10 | scanner_LIBRARIES = packageinfo 11 | 12 | ARCHS = armv6 armv7 armv7s arm64 13 | 14 | include $(THEOS_MAKE_PATH)/common.mk 15 | include $(THEOS_MAKE_PATH)/tweak.mk 16 | -------------------------------------------------------------------------------- /scanner/Tweak.xm: -------------------------------------------------------------------------------- 1 | /** 2 | * Name: scanner 3 | * Type: iOS extension 4 | * Desc: Scans tweaks for known issues. 5 | * 6 | * Author: Lance Fetters (aka. ashikase) 7 | * License: GPL v3 (See LICENSE file for details) 8 | */ 9 | 10 | #import 11 | #import 12 | #include 13 | #include 14 | 15 | #import "CRMissingFilterAlertItem.h" 16 | 17 | #ifdef PKG_ID 18 | #undef PKG_ID 19 | #endif 20 | #define PKG_ID "jp.ashikase.crashreporter" 21 | #define TWEAK_ID PKG_ID".scanner" 22 | 23 | static const char * const kDefaultTweakPath = "/Library/MobileSubstrate/DynamicLibraries"; 24 | 25 | CFArrayRef substrate_createListOfDylibs() { 26 | CFArrayRef dylibs = NULL; 27 | 28 | // Create URL for default path that Substrate searches for tweaks. 29 | CFURLRef libraries = 30 | CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)kDefaultTweakPath, strlen(kDefaultTweakPath), TRUE); 31 | 32 | // Create a bundle representing said path. 33 | CFBundleRef folder = CFBundleCreate(kCFAllocatorDefault, libraries); 34 | CFRelease(libraries); 35 | 36 | if (folder != NULL) { 37 | // Get a list of dylibs at said path. 38 | dylibs = CFBundleCopyResourceURLsOfType(folder, CFSTR("dylib"), NULL); 39 | CFRelease(folder); 40 | } 41 | 42 | return dylibs; 43 | } 44 | 45 | static NSString *md5(NSString *path) { 46 | NSMutableString *string = nil; 47 | 48 | NSData *data = [[NSData alloc] initWithContentsOfFile:path]; 49 | if (data != nil) { 50 | unsigned char digest[CC_MD5_DIGEST_LENGTH]; 51 | CC_MD5(data.bytes, data.length, digest); 52 | 53 | // Convert unsigned char buffer to NSString of hex values 54 | string = [NSMutableString stringWithCapacity:(CC_MD5_DIGEST_LENGTH * 2)]; 55 | for (unsigned i = 0; i < CC_MD5_DIGEST_LENGTH; ++i) { 56 | [string appendFormat:@"%02x", digest[i]]; 57 | } 58 | 59 | [data release]; 60 | } 61 | 62 | return string; 63 | } 64 | 65 | static void processDylibs() { 66 | static NSString * const kCrashReporterScanned = @"scanned"; 67 | 68 | // Retrieve list of dylib files. 69 | CFArrayRef dylibs = substrate_createListOfDylibs(); 70 | if (dylibs != NULL) { 71 | // Create list to track which dylibs have been scanned. 72 | // NOTE: This list is recreated each time so that only tweaks that are 73 | // currently installed are remembered. If a user uninstalls a 74 | // tweak and then reinstalls that same version at a later point, 75 | // they should be reminded again of any issues with said tweak. 76 | NSMutableDictionary *scannedDylibs = [[NSMutableDictionary alloc] init]; 77 | 78 | // Retrieve list of previously-scanned dylibs. 79 | // NOTE: Must synchronize preferences in case they have changed on disk. 80 | NSDictionary *prevScannedDylibs = nil; 81 | CFPreferencesAppSynchronize(CFSTR(TWEAK_ID)); 82 | CFPropertyListRef propList = CFPreferencesCopyAppValue((CFStringRef)kCrashReporterScanned, CFSTR(TWEAK_ID)); 83 | if (propList != NULL) { 84 | if (CFGetTypeID(propList) == CFDictionaryGetTypeID()) { 85 | // NOTE: Don't forget to release when finished using. 86 | prevScannedDylibs = (NSDictionary *)propList; 87 | } else { 88 | CFRelease(propList); 89 | } 90 | } 91 | 92 | for (NSURL *url in (NSArray *)dylibs) { 93 | NSString *path = [url path]; 94 | if (path != nil) { 95 | NSString *filename = [path lastPathComponent]; 96 | 97 | // Determine MD5 digest for dylib file. 98 | // NOTE: This is used to differentiate the file from other 99 | // versions (or other dylibs with the same filename). 100 | NSString *digest = md5(path); 101 | if (digest == nil) { 102 | // Failed to calculate MD5 digest. 103 | // NOTE: This can occur if the dylib is a dead symbolic link. 104 | // TODO: Consider displaying a notification about the dead link. 105 | NSLog(@"WARNING: Possible dead symbolic link: %@", path); 106 | continue; 107 | } 108 | 109 | // Record that dylib has been scanned. 110 | [scannedDylibs setObject:digest forKey:filename]; 111 | 112 | // Determine if dylib was previously scanned. 113 | id object = [prevScannedDylibs objectForKey:filename]; 114 | if ([object isKindOfClass:[NSString class]]) { 115 | if ([object isEqualToString:digest]) { 116 | // Previously scanned. 117 | continue; 118 | } 119 | } 120 | 121 | // Determine if dylib is missing filter file. 122 | NSString *filterPath = [NSString stringWithFormat:@"%s/%@.plist", kDefaultTweakPath, [filename stringByDeletingPathExtension]]; 123 | struct stat st; 124 | if (stat([filterPath UTF8String], &st) != 0) { 125 | // Filter is missing. 126 | dispatch_async(dispatch_get_main_queue(), ^{ 127 | [objc_getClass("CRMissingFilterAlertItem") showForPath:path]; 128 | }); 129 | } 130 | } else { 131 | NSLog(@"ERROR: Failed to obtain path for dylib URL: %@", [url relativeString]); 132 | } 133 | } 134 | 135 | // Update stored list of scanned dylibs. 136 | CFPreferencesSetAppValue((CFStringRef)kCrashReporterScanned, scannedDylibs, CFSTR(TWEAK_ID)); 137 | CFPreferencesAppSynchronize(CFSTR(TWEAK_ID)); 138 | 139 | // Clean-up. 140 | [scannedDylibs release]; 141 | [prevScannedDylibs release]; 142 | CFRelease(dylibs); 143 | } 144 | } 145 | 146 | %hook SpringBoard 147 | 148 | - (void)applicationDidFinishLaunching:(UIApplication *)application { 149 | %orig(); 150 | 151 | dispatch_queue_t queue; 152 | if (IOS_LT(5_0)) { 153 | queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); 154 | } else { 155 | queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); 156 | } 157 | 158 | dispatch_async(queue, ^{ 159 | processDylibs(); 160 | }); 161 | } 162 | 163 | %end 164 | 165 | %ctor { 166 | @autoreleasepool { 167 | // Make certain that hooks are installed only when loaded into SpringBoard. 168 | NSString *identifier = [[NSBundle mainBundle] bundleIdentifier]; 169 | if ([identifier isEqualToString:@"com.apple.springboard"]) { 170 | if (IOS_GTE(6_0)) { 171 | %init(); 172 | } 173 | } 174 | } 175 | } 176 | 177 | /* vim: set ft=objc ff=unix sw=4 ts=4 tw=80 expandtab: */ 178 | -------------------------------------------------------------------------------- /scanner/scanner.plist: -------------------------------------------------------------------------------- 1 | ../layout/Library/MobileSubstrate/DynamicLibraries/CrashReporterScanner.plist --------------------------------------------------------------------------------