Refactor Canard dumper save/share flow

This commit is contained in:
√(noham)²
2026-08-21 16:33:55 +02:00
parent a4dc5a9968
commit 24dc5267ca
2 changed files with 86 additions and 61 deletions

View File

@@ -1,5 +1,5 @@
TARGET = iphone:latest:15.0 TARGET = iphone:latest:15.0
ARCHS = arm64 arm64e ARCHS = arm64
INSTALL_TARGET_PROCESSES = fr.lecanardenchaine.app INSTALL_TARGET_PROCESSES = fr.lecanardenchaine.app
include $(THEOS)/makefiles/common.mk include $(THEOS)/makefiles/common.mk

View File

@@ -1,11 +1,22 @@
#import <substrate.h> #import <substrate.h>
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import <dlfcn.h>
#import <mach-o/dyld.h> #pragma mark - Constants
#import <string>
static NSString *const kCanardLogTag = @"[CanardDumper]";
static NSString *const kCanardDumpDirectoryName = @"CanardDumps";
#pragma mark - State
static NSString *CanardArchivePassword;
static int CanardLastSavedReadSize = -1;
#pragma mark - UI Helpers
static UIViewController *CanardTopViewController(UIViewController *viewController) { static UIViewController *CanardTopViewController(UIViewController *viewController) {
if (!viewController) return nil;
if (viewController.presentedViewController) { if (viewController.presentedViewController) {
return CanardTopViewController(viewController.presentedViewController); return CanardTopViewController(viewController.presentedViewController);
} }
@@ -20,108 +31,122 @@ static UIViewController *CanardTopViewController(UIViewController *viewControlle
static void CanardPresentShareSheet(NSString *filePath) { static void CanardPresentShareSheet(NSString *filePath) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
UIApplication *application = [UIApplication sharedApplication]; UIWindow *keyWindow = nil;
UIWindow *window = application.keyWindow; for (UIWindow *window in [UIApplication sharedApplication].windows) {
if (!window) { if (window.isKeyWindow) {
for (UIWindow *candidate in application.windows) { keyWindow = window;
if (candidate.isKeyWindow) { break;
window = candidate;
break;
}
} }
} }
UIViewController *viewController = CanardTopViewController(window.rootViewController); UIViewController *topVC = CanardTopViewController(keyWindow.rootViewController);
if (!viewController) { if (!topVC) {
NSLog(@"[CanardDumper] Unable to present file share sheet"); NSLog(@"%@ Unable to present share sheet", kCanardLogTag);
return; return;
} }
NSURL *fileURL = [NSURL fileURLWithPath:filePath]; NSURL *fileURL = [NSURL fileURLWithPath:filePath];
UIActivityViewController *shareController = [[UIActivityViewController alloc] initWithActivityItems:@[fileURL] applicationActivities:nil]; UIActivityViewController *shareController = [[UIActivityViewController alloc] initWithActivityItems:@[fileURL] applicationActivities:nil];
UIPopoverPresentationController *popover = shareController.popoverPresentationController; shareController.popoverPresentationController.sourceView = topVC.view;
popover.sourceView = viewController.view; shareController.popoverPresentationController.sourceRect = CGRectMake(
popover.sourceRect = CGRectMake(CGRectGetMidX(viewController.view.bounds), CGRectGetMidY(viewController.view.bounds), 0, 0); CGRectGetMidX(topVC.view.bounds),
[viewController presentViewController:shareController animated:YES completion:nil]; CGRectGetMidY(topVC.view.bounds),
0, 0
);
[topVC presentViewController:shareController animated:YES completion:nil];
}); });
} }
static NSString *CanardArchivePassword; #pragma mark - File Helpers
static int CanardLastSavedReadSize = -1;
static NSString *CanardPasswordFileComponent(NSString *password) { static NSString *CanardSanitizedFilename(NSString *password) {
if (password.length == 0) { if (password.length == 0) return @"unknown";
return @"unknown";
NSCharacterSet *invalidChars = [NSCharacterSet characterSetWithCharactersInString:@"/:\\?%*|\"<>\n\r\t"];
NSString *sanitized = [[password componentsSeparatedByCharactersInSet:invalidChars] componentsJoinedByString:@"_"];
return sanitized.length > 0 ? sanitized : @"unknown";
}
static NSString *CanardDumpDirectory() {
NSString *docsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
return [docsPath stringByAppendingPathComponent:kCanardDumpDirectoryName];
}
static bool CanardSaveData(NSData *data, NSString *password) {
NSFileManager *fm = [NSFileManager defaultManager];
NSString *dumpDir = CanardDumpDirectory();
if (![fm fileExistsAtPath:dumpDir]) {
[fm createDirectoryAtPath:dumpDir withIntermediateDirectories:YES attributes:nil error:nil];
} }
NSCharacterSet *invalidCharacters = [NSCharacterSet characterSetWithCharactersInString:@"/:\\?%*|\"<>\n\r\t"]; NSString *fileName = [NSString stringWithFormat:@"file_%@.pdf", CanardSanitizedFilename(password)];
NSString *safePassword = [[password componentsSeparatedByCharactersInSet:invalidCharacters] componentsJoinedByString:@"_"]; NSString *filePath = [dumpDir stringByAppendingPathComponent:fileName];
return safePassword.length > 0 ? safePassword : @"unknown";
if ([data writeToFile:filePath atomically:YES]) {
NSLog(@"%@ Saved to %@ (%lu bytes)", kCanardLogTag, filePath, (unsigned long)data.length);
CanardPresentShareSheet(filePath);
return YES;
}
NSLog(@"%@ Failed to save to %@", kCanardLogTag, filePath);
return NO;
} }
#pragma mark - DlyArchiveReader Hooks
%hook DlyArchiveReader %hook DlyArchiveReader
-(BOOL)setUpArchiveError:(id *)error { - (bool)setUpArchiveError:(id *)error {
NSLog(@"[CanardDumper] DlyArchiveReader setUpArchiveError: error=%p", error); NSLog(@"%@ setUpArchiveError: %p", kCanardLogTag, error);
return %orig; return %orig;
} }
-(NSString *)password { - (NSString *)password {
NSString *pwd = %orig; NSString *pwd = %orig;
CanardArchivePassword = [pwd copy]; CanardArchivePassword = [pwd copy];
NSLog(@"[CanardDumper] DlyArchiveReader password: %@", pwd); NSLog(@"%@ password: %@", kCanardLogTag, pwd);
return pwd; return pwd;
} }
-(int)getDocumentSize { - (int)getDocumentSize {
int size = %orig; int size = %orig;
NSLog(@"[CanardDumper] DlyArchiveReader getDocumentSize: %d", size); NSLog(@"%@ getDocumentSize: %d", kCanardLogTag, size);
return size; return size;
} }
-(NSData *)readDataAt:(int)offset withSize:(int)size { - (NSData *)readDataAt:(int)offset withSize:(int)size {
NSLog(@"[CanardDumper] DlyArchiveReader readDataAt: offset=%d withSize=%d", offset, size); NSLog(@"%@ readDataAt: offset=%d size=%d", kCanardLogTag, offset, size);
NSArray<NSString *> *callStack = [NSThread callStackSymbols]; NSLog(@"%@ %@", kCanardLogTag, [[NSThread callStackSymbols] componentsJoinedByString:@"\n"]);
NSLog(@"[CanardDumper] Backtrace:\n%@", [callStack componentsJoinedByString:@"\n"]);
NSData *data = %orig;
if (data.length > 0 && size != CanardLastSavedReadSize) {
NSFileManager *fm = [NSFileManager defaultManager];
NSString *docsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *dumpDir = [docsPath stringByAppendingPathComponent:@"CanardDumps"];
if (![fm fileExistsAtPath:dumpDir]) {
[fm createDirectoryAtPath:dumpDir withIntermediateDirectories:YES attributes:nil error:nil];
}
NSString *passwordComponent = CanardPasswordFileComponent(CanardArchivePassword); NSData *data = %orig;
NSString *fileName = [NSString stringWithFormat:@"file_%@.pdf", passwordComponent];
NSString *filePath = [dumpDir stringByAppendingPathComponent:fileName]; if (data.length > 0 && size != CanardLastSavedReadSize) {
if ([data writeToFile:filePath atomically:YES]) { CanardLastSavedReadSize = size;
CanardLastSavedReadSize = size; CanardSaveData(data, CanardArchivePassword);
NSLog(@"[CanardDumper] Saved readDataAt data to %@ (%lu bytes)", filePath, (unsigned long)data.length);
CanardPresentShareSheet(filePath);
} else {
NSLog(@"[CanardDumper] Failed to save readDataAt data to %@", filePath);
}
} }
return data; return data;
} }
%end %end
#pragma mark - DlyCoreArchive Hooks
%hook DlyCoreArchive %hook DlyCoreArchive
-(instancetype)initWithArchivePath:(NSString *)path error:(NSError **)error { - (instancetype)initWithArchivePath:(NSString *)path error:(NSError **)error {
NSLog(@"[CanardDumper] DlyCoreArchive initWithArchivePath: %@ error=%p", path, error); NSLog(@"%@ initWithArchivePath: %@ error:%p", kCanardLogTag, path, error);
return %orig; return %orig;
} }
+(instancetype)newWithArchivePath:(NSString *)path error:(NSError **)error { + (instancetype)newWithArchivePath:(NSString *)path error:(NSError **)error {
NSLog(@"[CanardDumper] DlyCoreArchive newWithArchivePath: %@ error=%p", path, error); NSLog(@"%@ newWithArchivePath: %@ error:%p", kCanardLogTag, path, error);
return %orig; return %orig;
} }
-(BOOL)openArchiveWithType:(NSUInteger)type error:(id *)error { - (bool)openArchiveWithType:(NSUInteger)type error:(id *)error {
NSLog(@"[CanardDumper] DlyCoreArchive openArchiveWithType: %lu error=%p", (unsigned long)type, error); NSLog(@"%@ openArchiveWithType: %lu error:%p", kCanardLogTag, (unsigned long)type, error);
return %orig; return %orig;
} }