4 Commits

Author SHA1 Message Date
√(noham)²
47d2f2ba89 Add BGSessionFix tweak for simulator sessions 2026-08-24 16:54:19 +02:00
√(noham)²
58990b8ecc Up 2026-08-21 19:35:22 +02:00
√(noham)²
24dc5267ca Refactor Canard dumper save/share flow 2026-08-21 16:33:55 +02:00
√(noham)²
a4dc5a9968 Add CanardDumper tweak 2026-08-21 16:30:42 +02:00
14 changed files with 251 additions and 0 deletions

3
.gitignore vendored
View File

@@ -5,3 +5,6 @@ Build.md
/App /App
/GoodnotesPro /GoodnotesPro
/StravaPremium /StravaPremium
scripts/build_install_loop.sh
CanardDumper/Tweak.x.bak
CanardDumper/Tweak_POC.x

3
BGSessionFix/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.theos/
packages/
.DS_Store

View File

@@ -0,0 +1,7 @@
{
Filter = {
Bundles = (
"app.bundle.id",
);
};
}

14
BGSessionFix/Makefile Normal file
View File

@@ -0,0 +1,14 @@
TARGET = iphone:latest:15.0
ARCHS = arm64
INSTALL_TARGET_PROCESSES = app.bundle.id
include $(THEOS)/makefiles/common.mk
TWEAK_NAME = BGSessionFix
BGSessionFix_FILES = Tweak.x
BGSessionFix_CFLAGS = -fobjc-arc -Wno-deprecated-declarations -std=c++11 -x objective-c++
BGSessionFix_CXXFLAGS = -std=c++11
BGSessionFix_FRAMEWORKS = Foundation UIKit
BGSessionFix_LDFLAGS = -lstdc++
include $(THEOS_MAKE_PATH)/tweak.mk

16
BGSessionFix/Tweak.x Normal file
View File

@@ -0,0 +1,16 @@
#import <Foundation/Foundation.h>
%hook NSURLSessionConfiguration
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier {
NSLog(@"[BGSessionFix] Redirecting background session '%@' to a default (foreground) config", identifier);
return [NSURLSessionConfiguration defaultSessionConfiguration];
}
// // Deprecated pre-iOS8 entry point
// + (NSURLSessionConfiguration *)backgroundSessionConfiguration:(NSString *)identifier {
// NSLog(@"[BGSessionFix] backgroundSessionConfiguration:'%@' (deprecated API) -> forcing default config", identifier);
// return [NSURLSessionConfiguration defaultSessionConfiguration];
// }
%end

9
BGSessionFix/control Normal file
View File

@@ -0,0 +1,9 @@
Package: xyz.nohamr.bgsessionfix
Name: BGSessionFix
Version: 1.0.0
Architecture: iphoneos-arm
Description: Redirects background NSURLSession configs to foreground configs (Simulator dev use)
Maintainer: NohamR
Author: NohamR
Section: Tweaks
Depends: mobilesubstrate (>= 0.9.5000)

12
BGSessionFix/index.md Normal file
View File

@@ -0,0 +1,12 @@
Tweak that redirects background NSURLSession configurations to foreground (default) configurations, for use when developing/debugging apps in the iOS Simulator.
The iOS Simulator does not run `nsurlsessiond`, the system daemon that backs `NSURLSession` background transfers. Any app that uses `backgroundSessionConfigurationWithIdentifier:` to download or upload files (so the transfer can survive app suspension/termination on a real device) will fail in the Simulator with errors such as:
```
No XPC connection in Simulator
BackgroundSession <...> an error occurred on the xpc connection to setup
the background session: ... connection to service named com.apple.nsurlsessiond
BackgroundSession <...> failed to create a background NSURLSessionDownloadTask,
as remote session is unavailable
Task <...> finished with error [-1] "unknown error"
```

3
CanardDumper/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.theos/
packages/
.DS_Store

View File

@@ -0,0 +1,7 @@
{
Filter = {
Bundles = (
"fr.lecanardenchaine.app",
);
};
}

14
CanardDumper/Makefile Normal file
View File

@@ -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

153
CanardDumper/Tweak.x Normal file
View File

@@ -0,0 +1,153 @@
#import <substrate.h>
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#pragma mark - Constants
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) {
if (!viewController) return nil;
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(), ^{
UIWindow *keyWindow = nil;
for (UIWindow *window in [UIApplication sharedApplication].windows) {
if (window.isKeyWindow) {
keyWindow = window;
break;
}
}
UIViewController *topVC = CanardTopViewController(keyWindow.rootViewController);
if (!topVC) {
NSLog(@"%@ Unable to present share sheet", kCanardLogTag);
return;
}
NSURL *fileURL = [NSURL fileURLWithPath:filePath];
UIActivityViewController *shareController = [[UIActivityViewController alloc] initWithActivityItems:@[fileURL] applicationActivities:nil];
shareController.popoverPresentationController.sourceView = topVC.view;
shareController.popoverPresentationController.sourceRect = CGRectMake(
CGRectGetMidX(topVC.view.bounds),
CGRectGetMidY(topVC.view.bounds),
0, 0
);
[topVC presentViewController:shareController animated:YES completion:nil];
});
}
#pragma mark - File Helpers
static NSString *CanardSanitizedFilename(NSString *password) {
if (password.length == 0) 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];
}
NSString *fileName = [NSString stringWithFormat:@"file_%@.pdf", CanardSanitizedFilename(password)];
NSString *filePath = [dumpDir stringByAppendingPathComponent:fileName];
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
- (bool)setUpArchiveError:(id *)error {
NSLog(@"%@ setUpArchiveError: %p", kCanardLogTag, error);
return %orig;
}
- (NSString *)password {
NSString *pwd = %orig;
CanardArchivePassword = [pwd copy];
NSLog(@"%@ password: %@", kCanardLogTag, pwd);
return pwd;
}
- (int)getDocumentSize {
int size = %orig;
NSLog(@"%@ getDocumentSize: %d", kCanardLogTag, size);
return size;
}
- (NSData *)readDataAt:(int)offset withSize:(int)size {
NSLog(@"%@ readDataAt: offset=%d size=%d", kCanardLogTag, offset, size);
NSLog(@"%@ %@", kCanardLogTag, [[NSThread callStackSymbols] componentsJoinedByString:@"\n"]);
NSData *data = %orig;
if (data.length > 0 && size != CanardLastSavedReadSize) {
CanardLastSavedReadSize = size;
CanardSaveData(data, CanardArchivePassword);
}
return data;
}
%end
#pragma mark - DlyCoreArchive Hooks
%hook DlyCoreArchive
- (instancetype)initWithArchivePath:(NSString *)path error:(NSError **)error {
NSLog(@"%@ initWithArchivePath: %@ error:%p", kCanardLogTag, path, error);
return %orig;
}
+ (instancetype)newWithArchivePath:(NSString *)path error:(NSError **)error {
NSLog(@"%@ newWithArchivePath: %@ error:%p", kCanardLogTag, path, error);
return %orig;
}
- (bool)openArchiveWithType:(NSUInteger)type error:(id *)error {
NSLog(@"%@ openArchiveWithType: %lu error:%p", kCanardLogTag, (unsigned long)type, error);
return %orig;
}
%end

9
CanardDumper/control Normal file
View File

@@ -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)

0
CanardDumper/index.md Normal file
View File

View File

@@ -20,6 +20,7 @@ iOS tweaks built with [Theos](https://theos.dev), injected into IPAs via [cyan](
| [GPXViewer2](GPXViewer2) | GPXViewer 2 | iOS 14+ | | [GPXViewer2](GPXViewer2) | GPXViewer 2 | iOS 14+ |
| [RMHook](RMHook/index.md) | reMarkable | iOS | | [RMHook](RMHook/index.md) | reMarkable | iOS |
| [HatchDragons](HatchDragons/index.md) | HatchDragons 1.2.1 | iOS 18+ | | [HatchDragons](HatchDragons/index.md) | HatchDragons 1.2.1 | iOS 18+ |
| [BGSessionFix](BGSessionFix/index.md) | BGSessionFix | iOS 15+ |
## Build ## Build