|
60 | 60 | #import <GameController/GCMouse.h> |
61 | 61 | #endif |
62 | 62 |
|
| 63 | +#ifdef HAVE_KSCRASH |
| 64 | +#import <KSCrash.h> |
| 65 | +#import <KSCrashConfiguration.h> |
| 66 | +#import <KSCrashReportStore.h> |
| 67 | +#import <KSCrashInstallation.h> |
| 68 | +#import <KSCrashReport.h> |
| 69 | +#endif |
| 70 | + |
63 | 71 | #ifdef HAVE_SDL2 |
64 | 72 | #define SDL_MAIN_HANDLED |
65 | 73 | #include "SDL.h" |
@@ -629,8 +637,136 @@ - (void)handleAudioSessionInterruption:(NSNotification *)notification |
629 | 637 | } |
630 | 638 | } |
631 | 639 |
|
| 640 | +#ifdef HAVE_KSCRASH |
| 641 | +- (NSString *)crashReportsPath |
| 642 | +{ |
| 643 | + /* Store crash reports in Documents directory for user access */ |
| 644 | + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); |
| 645 | + NSString *documentsPath = [paths firstObject]; |
| 646 | + return [documentsPath stringByAppendingPathComponent:@"CrashReports"]; |
| 647 | +} |
| 648 | + |
| 649 | +- (void)initKSCrash |
| 650 | +{ |
| 651 | + NSString *crashReportsPath = [self crashReportsPath]; |
| 652 | + |
| 653 | + /* Create the crash reports directory if it doesn't exist */ |
| 654 | + NSFileManager *fileManager = [NSFileManager defaultManager]; |
| 655 | + NSError *createError = nil; |
| 656 | + if (![fileManager fileExistsAtPath:crashReportsPath]) |
| 657 | + { |
| 658 | + [fileManager createDirectoryAtPath:crashReportsPath |
| 659 | + withIntermediateDirectories:YES |
| 660 | + attributes:nil |
| 661 | + error:&createError]; |
| 662 | + if (createError) |
| 663 | + { |
| 664 | + NSLog(@"[KSCrash] Failed to create crash reports directory: %@\n", createError); |
| 665 | + return; |
| 666 | + } |
| 667 | + } |
| 668 | + |
| 669 | + /* Configure KSCrash for local storage only */ |
| 670 | + KSCrashConfiguration *config = [KSCrashConfiguration new]; |
| 671 | + config.installPath = crashReportsPath; |
| 672 | + KSCrashReportStoreConfiguration *storeConfig = [KSCrashReportStoreConfiguration new]; |
| 673 | + storeConfig.reportsPath = crashReportsPath; |
| 674 | + storeConfig.appName = @"RetroArch"; |
| 675 | + storeConfig.maxReportCount = 10; /* Keep last 10 crash reports */ |
| 676 | + config.reportStoreConfiguration = storeConfig; |
| 677 | + |
| 678 | + /* Set appropriate monitors */ |
| 679 | + if (jit_available()) |
| 680 | + config.monitors = KSCrashMonitorTypeDebuggerSafe; |
| 681 | + else |
| 682 | + config.monitors = KSCrashMonitorTypeProductionSafe; |
| 683 | + /* Enable useful debugging features */ |
| 684 | + config.enableMemoryIntrospection = YES; |
| 685 | + config.enableQueueNameSearch = YES; |
| 686 | + config.addConsoleLogToReport = YES; |
| 687 | + |
| 688 | + /* Install KSCrash without any network sink */ |
| 689 | + NSError *installError = nil; |
| 690 | + if (![[KSCrash sharedInstance] installWithConfiguration:config error:&installError]) |
| 691 | + { |
| 692 | + NSLog(@"[KSCrash] Failed to install crash reporter: %@\n", installError); |
| 693 | + return; |
| 694 | + } |
| 695 | + |
| 696 | + NSLog(@"[KSCrash] reports will be stored in: %@\n", crashReportsPath); |
| 697 | +} |
| 698 | + |
| 699 | +- (void)processKSCrashReports |
| 700 | +{ |
| 701 | + /* Check if we crashed last launch */ |
| 702 | + if (![[KSCrash sharedInstance] crashedLastLaunch]) |
| 703 | + return; |
| 704 | + |
| 705 | + if ([[[KSCrash sharedInstance] reportStore] reportCount] <= 0) |
| 706 | + return; |
| 707 | + |
| 708 | + RARCH_LOG("[KSCrash] crash report available in Documents/CrashReports\n"); |
| 709 | + |
| 710 | + /* Process crash reports to strip binary_images section */ |
| 711 | + KSCrashReportStore *store = [[KSCrash sharedInstance] reportStore]; |
| 712 | + NSArray<NSNumber *> *reportIDs = [store reportIDs]; |
| 713 | + for (NSNumber *reportIDNum in reportIDs) |
| 714 | + { |
| 715 | + int64_t reportID = [reportIDNum longLongValue]; |
| 716 | + KSCrashReportDictionary *report = [store reportForID:reportID]; |
| 717 | + if (!report) |
| 718 | + continue; |
| 719 | + |
| 720 | + NSMutableDictionary *mutableReport = [report.value mutableCopy]; |
| 721 | + |
| 722 | + /* Remove binary_images to reduce file size */ |
| 723 | + if ([mutableReport objectForKey:@"binary_images"]) |
| 724 | + [mutableReport removeObjectForKey:@"binary_images"]; |
| 725 | + |
| 726 | + /* Save pretty-printed version as standalone file */ |
| 727 | + NSData *prettyData = [NSJSONSerialization dataWithJSONObject:mutableReport |
| 728 | + options:NSJSONWritingPrettyPrinted |
| 729 | + error:nil]; |
| 730 | + if (prettyData) |
| 731 | + { |
| 732 | + NSString *reportPath = [NSString stringWithFormat:@"%@/report-%lld.json", |
| 733 | + [self crashReportsPath], reportID]; |
| 734 | + [prettyData writeToFile:reportPath options:NSDataWritingAtomic error:nil]; |
| 735 | + RARCH_LOG("[KSCrash] Saved stripped report %lld to: %s\n", |
| 736 | + reportID, [reportPath UTF8String]); |
| 737 | + } |
| 738 | + |
| 739 | + /* Log minified JSON on a single line for easy extraction */ |
| 740 | + NSData *minifiedData = [NSJSONSerialization dataWithJSONObject:mutableReport |
| 741 | + options:0 /* no pretty printing */ |
| 742 | + error:nil]; |
| 743 | + if (minifiedData) |
| 744 | + { |
| 745 | + NSString *jsonString = [[NSString alloc] initWithData:minifiedData |
| 746 | + encoding:NSUTF8StringEncoding]; |
| 747 | + if (jsonString) |
| 748 | + { |
| 749 | + /* Log with a unique marker that can be extracted with grep/sed */ |
| 750 | + RARCH_LOG("[KSCrash] Report %lld follows on next line\n", reportID); |
| 751 | + RARCH_LOG("%s\n", [jsonString UTF8String]); |
| 752 | + } |
| 753 | + } |
| 754 | + |
| 755 | + /* Delete the report from KSCrash store to prevent re-logging on next launch */ |
| 756 | + [store deleteReportWithID:reportID]; |
| 757 | + } |
| 758 | + |
| 759 | + if (reportIDs.count > 0) |
| 760 | + RARCH_LOG("[KSCrash] Processed and removed %lu report(s) from store\n", (unsigned long)reportIDs.count); |
| 761 | +} |
| 762 | +#endif |
| 763 | + |
632 | 764 | - (void)applicationDidFinishLaunching:(UIApplication *)application |
633 | 765 | { |
| 766 | +#ifdef HAVE_KSCRASH |
| 767 | + [self initKSCrash]; |
| 768 | +#endif |
| 769 | + |
634 | 770 | char arguments[] = "retroarch"; |
635 | 771 | char *argv[] = {arguments, NULL}; |
636 | 772 | int argc = 1; |
@@ -680,6 +816,10 @@ - (void)applicationDidFinishLaunching:(UIApplication *)application |
680 | 816 |
|
681 | 817 | rarch_main(argc, argv, NULL); |
682 | 818 |
|
| 819 | +#ifdef HAVE_KSCRASH |
| 820 | + [self processKSCrashReports]; |
| 821 | +#endif |
| 822 | + |
683 | 823 | uico_driver_state_t *uico_st = uico_state_get_ptr(); |
684 | 824 | rarch_setting_t *appicon_setting = menu_setting_find_enum(MENU_ENUM_LABEL_APPICON_SETTINGS); |
685 | 825 | struct string_list *icons; |
|
0 commit comments