From a4dc5a996838ba393fe884c6afed3babb2325a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=88=9A=28noham=29=C2=B2?= <100566912+NohamR@users.noreply.github.com> Date: Fri, 21 Aug 2026 16:30:42 +0200 Subject: [PATCH] Add CanardDumper tweak --- CanardDumper/.gitignore | 3 + CanardDumper/CanardDumper.plist | 7 ++ CanardDumper/Makefile | 14 ++++ CanardDumper/Tweak.x | 128 ++++++++++++++++++++++++++++++++ CanardDumper/control | 9 +++ CanardDumper/index.md | 0 6 files changed, 161 insertions(+) create mode 100644 CanardDumper/.gitignore create mode 100644 CanardDumper/CanardDumper.plist create mode 100644 CanardDumper/Makefile create mode 100644 CanardDumper/Tweak.x create mode 100644 CanardDumper/control create mode 100644 CanardDumper/index.md diff --git a/CanardDumper/.gitignore b/CanardDumper/.gitignore new file mode 100644 index 0000000..faf8687 --- /dev/null +++ b/CanardDumper/.gitignore @@ -0,0 +1,3 @@ +.theos/ +packages/ +.DS_Store diff --git a/CanardDumper/CanardDumper.plist b/CanardDumper/CanardDumper.plist new file mode 100644 index 0000000..d47deb6 --- /dev/null +++ b/CanardDumper/CanardDumper.plist @@ -0,0 +1,7 @@ +{ + Filter = { + Bundles = ( + "fr.lecanardenchaine.app", + ); + }; +} diff --git a/CanardDumper/Makefile b/CanardDumper/Makefile new file mode 100644 index 0000000..8079fcc --- /dev/null +++ b/CanardDumper/Makefile @@ -0,0 +1,14 @@ +TARGET = iphone:latest:15.0 +ARCHS = arm64 arm64e +INSTALL_TARGET_PROCESSES = fr.lecanardenchaine.app + +include $(THEOS)/makefiles/common.mk + +TWEAK_NAME = CanardDumper +CanardDumper_FILES = Tweak.x +CanardDumper_CFLAGS = -fobjc-arc -Wno-deprecated-declarations -std=c++11 -x objective-c++ +CanardDumper_CXXFLAGS = -std=c++11 +CanardDumper_FRAMEWORKS = Foundation UIKit +CanardDumper_LDFLAGS = -lstdc++ + +include $(THEOS_MAKE_PATH)/tweak.mk \ No newline at end of file diff --git a/CanardDumper/Tweak.x b/CanardDumper/Tweak.x new file mode 100644 index 0000000..a1badee --- /dev/null +++ b/CanardDumper/Tweak.x @@ -0,0 +1,128 @@ +#import +#import +#import +#import +#import +#import + +static UIViewController *CanardTopViewController(UIViewController *viewController) { + if (viewController.presentedViewController) { + return CanardTopViewController(viewController.presentedViewController); + } + if ([viewController isKindOfClass:[UINavigationController class]]) { + return CanardTopViewController([(UINavigationController *)viewController visibleViewController]); + } + if ([viewController isKindOfClass:[UITabBarController class]]) { + return CanardTopViewController([(UITabBarController *)viewController selectedViewController]); + } + return viewController; +} + +static void CanardPresentShareSheet(NSString *filePath) { + dispatch_async(dispatch_get_main_queue(), ^{ + UIApplication *application = [UIApplication sharedApplication]; + UIWindow *window = application.keyWindow; + if (!window) { + for (UIWindow *candidate in application.windows) { + if (candidate.isKeyWindow) { + window = candidate; + break; + } + } + } + + UIViewController *viewController = CanardTopViewController(window.rootViewController); + if (!viewController) { + NSLog(@"[CanardDumper] Unable to present file share sheet"); + return; + } + + NSURL *fileURL = [NSURL fileURLWithPath:filePath]; + UIActivityViewController *shareController = [[UIActivityViewController alloc] initWithActivityItems:@[fileURL] applicationActivities:nil]; + UIPopoverPresentationController *popover = shareController.popoverPresentationController; + popover.sourceView = viewController.view; + popover.sourceRect = CGRectMake(CGRectGetMidX(viewController.view.bounds), CGRectGetMidY(viewController.view.bounds), 0, 0); + [viewController presentViewController:shareController animated:YES completion:nil]; + }); +} + +static NSString *CanardArchivePassword; +static int CanardLastSavedReadSize = -1; + +static NSString *CanardPasswordFileComponent(NSString *password) { + if (password.length == 0) { + return @"unknown"; + } + + NSCharacterSet *invalidCharacters = [NSCharacterSet characterSetWithCharactersInString:@"/:\\?%*|\"<>\n\r\t"]; + NSString *safePassword = [[password componentsSeparatedByCharactersInSet:invalidCharacters] componentsJoinedByString:@"_"]; + return safePassword.length > 0 ? safePassword : @"unknown"; +} + +%hook DlyArchiveReader + +-(BOOL)setUpArchiveError:(id *)error { + NSLog(@"[CanardDumper] DlyArchiveReader setUpArchiveError: error=%p", error); + return %orig; +} + +-(NSString *)password { + NSString *pwd = %orig; + CanardArchivePassword = [pwd copy]; + NSLog(@"[CanardDumper] DlyArchiveReader password: %@", pwd); + return pwd; +} + +-(int)getDocumentSize { + int size = %orig; + NSLog(@"[CanardDumper] DlyArchiveReader getDocumentSize: %d", size); + return size; +} + +-(NSData *)readDataAt:(int)offset withSize:(int)size { + NSLog(@"[CanardDumper] DlyArchiveReader readDataAt: offset=%d withSize=%d", offset, size); + NSArray *callStack = [NSThread callStackSymbols]; + 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); + NSString *fileName = [NSString stringWithFormat:@"file_%@.pdf", passwordComponent]; + NSString *filePath = [dumpDir stringByAppendingPathComponent:fileName]; + if ([data writeToFile:filePath atomically:YES]) { + CanardLastSavedReadSize = size; + 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; +} + +%end + +%hook DlyCoreArchive + +-(instancetype)initWithArchivePath:(NSString *)path error:(NSError **)error { + NSLog(@"[CanardDumper] DlyCoreArchive initWithArchivePath: %@ error=%p", path, error); + return %orig; +} + ++(instancetype)newWithArchivePath:(NSString *)path error:(NSError **)error { + NSLog(@"[CanardDumper] DlyCoreArchive newWithArchivePath: %@ error=%p", path, error); + return %orig; +} + +-(BOOL)openArchiveWithType:(NSUInteger)type error:(id *)error { + NSLog(@"[CanardDumper] DlyCoreArchive openArchiveWithType: %lu error=%p", (unsigned long)type, error); + return %orig; +} + +%end \ No newline at end of file diff --git a/CanardDumper/control b/CanardDumper/control new file mode 100644 index 0000000..a44281a --- /dev/null +++ b/CanardDumper/control @@ -0,0 +1,9 @@ +Package: xyz.nohamr.canarddumper +Name: CanardDumper +Version: 1.0.0 +Architecture: iphoneos-arm +Description: Dumps extracted files from DlyCore DlyArchiveFile::GetFile to Documents/CanardDumps for Le Canard Enchaîné app analysis +Maintainer: NohamR +Author: NohamR +Section: Tweaks +Depends: mobilesubstrate (>= 0.9.5000) diff --git a/CanardDumper/index.md b/CanardDumper/index.md new file mode 100644 index 0000000..e69de29