Archive packaging menu, auto refresh, trash favorite, test.zip/test.ipa
This commit is contained in:
1 parent
718bf032c8
commit
71e5d72561
11 files changed
+954
-83
No files matched your search
@@ -13,3 +13,5 @@ MCMIdentifiers.plist
|
|||||||
|
|
||||||
# release staging
|
# release staging
|
||||||
build/
|
build/
|
||||||
|
!tests/test.ipa
|
||||||
|
!tests/test.zip
|
||||||
@@ -42,6 +42,10 @@ filza_develop.ipa
|
|||||||
appears when the LaunchServices database cannot be read.
|
appears when the LaunchServices database cannot be read.
|
||||||
- App icons are used where iOS returns them (`_applicationIconImageForBundle-`
|
- App icons are used where iOS returns them (`_applicationIconImageForBundle-`
|
||||||
`Identifier:format:scale:`); otherwise Filza's placeholder is shown.
|
`Identifier:format:scale:`); otherwise Filza's placeholder is shown.
|
||||||
|
- Long-press a file or folder for a **压缩 / 打包** menu: create ZIP, DEB or
|
||||||
|
IPA. Extraction/creation refreshes the browser automatically.
|
||||||
|
- `test.zip` and `test.ipa` are copied into the worse_query folder together
|
||||||
|
with `test.deb` and `test`.
|
||||||
- Accessible roots, App Data (by app), App Data (UUID), App Groups and System
|
- Accessible roots, App Data (by app), App Data (UUID), App Groups and System
|
||||||
Data links saved as Filza favorites.
|
Data links saved as Filza favorites.
|
||||||
- Startup progress overlay; first launch indexes installed apps and container
|
- Startup progress overlay; first launch indexes installed apps and container
|
||||||
@@ -55,7 +59,7 @@ filza_develop.ipa
|
|||||||
- `mount` lists mounted filesystems.
|
- `mount` lists mounted filesystems.
|
||||||
- `extract`, `unzip`, `untar`, `un7z`, `unrar` extract zip, tar, tar.gz, gz,
|
- `extract`, `unzip`, `untar`, `un7z`, `unrar` extract zip, tar, tar.gz, gz,
|
||||||
bz2, xz, zst, 7z and rar through libarchive.
|
bz2, xz, zst, 7z and rar through libarchive.
|
||||||
- `test.deb` (official Debian `apt 2.6.1`, arm64) and an executable `test` script
|
- `test.deb` (official Debian `apt 2.6.1`, arm64), `test.zip`, `test.ipa`, and an executable `test` script
|
||||||
are copied into `worse_query` on first launch.
|
are copied into `worse_query` on first launch.
|
||||||
- `respring` uses Erosion's WebKit pressure method.
|
- `respring` uses Erosion's WebKit pressure method.
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ typedef int (*WQAddChdirFn)(posix_spawn_file_actions_t *, const char *);
|
|||||||
#include "WQSSHServer.h"
|
#include "WQSSHServer.h"
|
||||||
#include "WQPackages.h"
|
#include "WQPackages.h"
|
||||||
#import "WQLSStore.h"
|
#import "WQLSStore.h"
|
||||||
|
#import "WQArchive.h"
|
||||||
|
|
||||||
#pragma mark - Root Helper Hooks
|
#pragma mark - Root Helper Hooks
|
||||||
|
|
||||||
@@ -75,12 +76,7 @@ static NSString *hook_connectionUrlWithHostname(id self, SEL _cmd) {
|
|||||||
// so moving a file from an accessible container into this directory is a
|
// so moving a file from an accessible container into this directory is a
|
||||||
// rename, not a cross-volume copy.
|
// rename, not a cross-volume copy.
|
||||||
static id hook_trashDir(id self, SEL _cmd) {
|
static id hook_trashDir(id self, SEL _cmd) {
|
||||||
NSString *root = MCMFilzaVirtualRoot();
|
return WQTrashDirectory();
|
||||||
NSString *trash = [root stringByAppendingPathComponent:@".Trash"];
|
|
||||||
[NSFileManager.defaultManager createDirectoryAtPath:trash
|
|
||||||
withIntermediateDirectories:YES
|
|
||||||
attributes:@{ NSFilePosixPermissions: @0700 } error:nil];
|
|
||||||
return trash;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A jailed Filza otherwise restores its usual /var/mobile start path, which is
|
// A jailed Filza otherwise restores its usual /var/mobile start path, which is
|
||||||
@@ -291,96 +287,276 @@ static void addFileToZip(zipFile64 zf, NSString *basePath, NSString *relativePat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static NSString *WQZipperPath(id value)
|
||||||
|
{
|
||||||
|
if ([value isKindOfClass:NSString.class]) return value;
|
||||||
|
if ([value isKindOfClass:NSURL.class]) return [(NSURL *)value path];
|
||||||
|
SEL selector = NSSelectorFromString(@"filePath");
|
||||||
|
if ([value respondsToSelector:selector])
|
||||||
|
return ((id(*)(id, SEL))objc_msgSend)(value, selector);
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
static id WQArchiveItemAtPath(NSString *path)
|
||||||
|
{
|
||||||
|
Class itemClass = NSClassFromString(@"FileItem");
|
||||||
|
if (!itemClass || !path.length) return nil;
|
||||||
|
id item = [[itemClass alloc] init];
|
||||||
|
if ([item respondsToSelector:NSSelectorFromString(@"setFilePath:attribute:")])
|
||||||
|
((void(*)(id, SEL, id, id))objc_msgSend)(item,
|
||||||
|
NSSelectorFromString(@"setFilePath:attribute:"), path, nil);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
static id WQArchiveResultForDirectory(NSString *directory)
|
||||||
|
{
|
||||||
|
NSArray<NSString *> *names = [NSFileManager.defaultManager
|
||||||
|
contentsOfDirectoryAtPath:directory error:nil];
|
||||||
|
if (!names.count) {
|
||||||
|
id item = WQArchiveItemAtPath(directory);
|
||||||
|
return item ? @[item] : nil;
|
||||||
|
}
|
||||||
|
NSMutableArray *items = [NSMutableArray array];
|
||||||
|
for (NSString *name in names) {
|
||||||
|
id item = WQArchiveItemAtPath(
|
||||||
|
[directory stringByAppendingPathComponent:name]);
|
||||||
|
if (item) [items addObject:item];
|
||||||
|
}
|
||||||
|
return items.count ? items : nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WQCollectViewControllers(UIViewController *controller,
|
||||||
|
NSMutableArray<UIViewController *> *result)
|
||||||
|
{
|
||||||
|
if (!controller) return;
|
||||||
|
[result addObject:controller];
|
||||||
|
for (UIViewController *child in controller.childViewControllers)
|
||||||
|
WQCollectViewControllers(child, result);
|
||||||
|
if ([controller isKindOfClass:UINavigationController.class])
|
||||||
|
for (UIViewController *child in
|
||||||
|
((UINavigationController *)controller).viewControllers)
|
||||||
|
WQCollectViewControllers(child, result);
|
||||||
|
if ([controller isKindOfClass:UITabBarController.class])
|
||||||
|
for (UIViewController *child in
|
||||||
|
((UITabBarController *)controller).viewControllers)
|
||||||
|
WQCollectViewControllers(child, result);
|
||||||
|
if (controller.presentedViewController)
|
||||||
|
WQCollectViewControllers(controller.presentedViewController, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WQRefreshActiveBrowserOnce(void)
|
||||||
|
{
|
||||||
|
NSMutableArray<UIViewController *> *controllers = [NSMutableArray array];
|
||||||
|
for (UIWindow *window in UIApplication.sharedApplication.windows)
|
||||||
|
if (!window.isHidden) WQCollectViewControllers(window.rootViewController,
|
||||||
|
controllers);
|
||||||
|
for (UIViewController *controller in controllers) {
|
||||||
|
NSString *className = NSStringFromClass(controller.class);
|
||||||
|
BOOL browser = [className containsString:@"PageViewController"] ||
|
||||||
|
[className containsString:@"ApplicationsViewController"] ||
|
||||||
|
[className containsString:@"FileSystemViewController"];
|
||||||
|
SEL currentSelector = NSSelectorFromString(@"currentPath");
|
||||||
|
if (browser && [controller respondsToSelector:currentSelector]) {
|
||||||
|
SEL loadSelector = NSSelectorFromString(@"doLoadingPage");
|
||||||
|
if ([controller respondsToSelector:loadSelector])
|
||||||
|
((void(*)(id, SEL))objc_msgSend)(controller, loadSelector);
|
||||||
|
loadSelector = NSSelectorFromString(@"loadFilesWithSortMode:");
|
||||||
|
if ([controller respondsToSelector:loadSelector])
|
||||||
|
((void(*)(id, SEL, int))objc_msgSend)(controller, loadSelector, 0);
|
||||||
|
}
|
||||||
|
if ([controller isKindOfClass:UITableViewController.class])
|
||||||
|
[((UITableViewController *)controller).tableView reloadData];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WQRefreshActiveBrowser(void)
|
||||||
|
{
|
||||||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 300 * NSEC_PER_MSEC),
|
||||||
|
dispatch_get_main_queue(), ^{
|
||||||
|
WQRefreshActiveBrowserOnce();
|
||||||
|
});
|
||||||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1200 * NSEC_PER_MSEC),
|
||||||
|
dispatch_get_main_queue(), ^{
|
||||||
|
WQRefreshActiveBrowserOnce();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef NS_ENUM(NSInteger, WQPackagingKind) {
|
||||||
|
WQPackagingZip,
|
||||||
|
WQPackagingDeb,
|
||||||
|
WQPackagingIpa,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void WQRunPackaging(id host, NSString *path, WQPackagingKind kind)
|
||||||
|
{
|
||||||
|
if (!path.length) return;
|
||||||
|
NSString *directory = path.stringByDeletingLastPathComponent;
|
||||||
|
NSString *base = path.lastPathComponent.stringByDeletingPathExtension;
|
||||||
|
NSString *suffix = kind == WQPackagingZip ? @".zip" :
|
||||||
|
kind == WQPackagingDeb ? @".deb" : @".ipa";
|
||||||
|
NSString *destination = [directory stringByAppendingPathComponent:
|
||||||
|
[base stringByAppendingString:suffix]];
|
||||||
|
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
||||||
|
NSString *message = nil;
|
||||||
|
BOOL ok = NO;
|
||||||
|
if (kind == WQPackagingZip) {
|
||||||
|
ok = WQCreateZipArchive(@[path], destination, directory, &message);
|
||||||
|
} else if (kind == WQPackagingDeb) {
|
||||||
|
ok = WQCreateDebPackage(path, destination, nil, nil, nil, nil,
|
||||||
|
&message);
|
||||||
|
} else {
|
||||||
|
ok = WQCreateIpaPackage(path, destination, &message);
|
||||||
|
}
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
WQRefreshActiveBrowser();
|
||||||
|
UIAlertController *alert = [UIAlertController
|
||||||
|
alertControllerWithTitle:ok ? @"打包完成" : @"打包失败"
|
||||||
|
message:message ?: (ok ? destination : @"未知错误")
|
||||||
|
preferredStyle:UIAlertControllerStyleAlert];
|
||||||
|
[alert addAction:[UIAlertAction actionWithTitle:@"OK"
|
||||||
|
style:UIAlertActionStyleDefault handler:nil]];
|
||||||
|
UIViewController *presenter = nil;
|
||||||
|
if ([host isKindOfClass:UIViewController.class])
|
||||||
|
presenter = host;
|
||||||
|
if (!presenter) {
|
||||||
|
for (UIWindow *window in UIApplication.sharedApplication.windows)
|
||||||
|
if (window.isKeyWindow && !window.isHidden) {
|
||||||
|
presenter = window.rootViewController;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (presenter.presentedViewController)
|
||||||
|
presenter = presenter.presentedViewController;
|
||||||
|
if (presenter)
|
||||||
|
[presenter presentViewController:alert animated:YES
|
||||||
|
completion:nil];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static id WQZipperExtract(id self, SEL _cmd, id file, id toPath,
|
||||||
|
id currentDirectory, id password, id *outMessage)
|
||||||
|
{
|
||||||
|
NSString *archive = WQZipperPath(file);
|
||||||
|
NSString *destination = WQZipperPath(toPath);
|
||||||
|
if (!destination.length && archive.length)
|
||||||
|
destination = WQDefaultExtractDestination(archive);
|
||||||
|
NSString *message = nil;
|
||||||
|
BOOL isDeb = [archive.pathExtension.lowercaseString isEqualToString:@"deb"];
|
||||||
|
BOOL ok = isDeb ? WQExtractDebPayload(archive, destination, &message)
|
||||||
|
: WQExtractArchive(archive, destination, &message);
|
||||||
|
if (outMessage) *outMessage = message;
|
||||||
|
NSLog(@"[Zipper] %@ -> %@: %@", archive, destination,
|
||||||
|
ok ? @"ok" : (message ?: @"failed"));
|
||||||
|
if (ok) WQRefreshActiveBrowser();
|
||||||
|
return ok ? WQArchiveResultForDirectory(destination) : nil;
|
||||||
|
}
|
||||||
|
|
||||||
// Hook: -[Zipper ZipFiles:toFilePath:currentDirectory:]
|
// Hook: -[Zipper ZipFiles:toFilePath:currentDirectory:]
|
||||||
static id hook_ZipFiles(id self, SEL _cmd, id files, id toFilePath, id currentDirectory) {
|
static id hook_ZipFiles(id self, SEL _cmd, id files, id toFilePath, id currentDirectory) {
|
||||||
@try {
|
@try {
|
||||||
loadMinizip();
|
NSString *destination = WQZipperPath(toFilePath);
|
||||||
if (!g_minizipLoaded) return orig_ZipFiles ? ((id(*)(id,SEL,id,id,id))orig_ZipFiles)(self, _cmd, files, toFilePath, currentDirectory) : nil;
|
NSString *base = WQZipperPath(currentDirectory);
|
||||||
zipFile64 zf = p_zipOpen64(((NSString *)toFilePath).UTF8String, 0); // APPEND_STATUS_CREATE=0
|
NSArray *items = [files isKindOfClass:NSArray.class]
|
||||||
if (!zf) { NSLog(@"[Tweak] zipOpen64 failed"); return nil; }
|
? files : (files ? @[files] : @[]);
|
||||||
|
NSMutableArray<NSString *> *paths = [NSMutableArray array];
|
||||||
for (id fi in files) {
|
for (id item in items) {
|
||||||
NSString *fn = [fi performSelector:NSSelectorFromString(@"fileName")];
|
NSString *path = WQZipperPath(item);
|
||||||
if (fn) addFileToZip(zf, currentDirectory, fn);
|
if (!path.length && [item isKindOfClass:NSDictionary.class])
|
||||||
}
|
path = ((NSDictionary *)item)[@"path"];
|
||||||
p_zipClose(zf, NULL);
|
if (!path.length &&
|
||||||
|
[item respondsToSelector:NSSelectorFromString(@"fileName")]) {
|
||||||
// Return FileItem if zip was created (matching original behavior)
|
NSString *name = ((id(*)(id, SEL))objc_msgSend)(item,
|
||||||
if ([[NSFileManager defaultManager] fileExistsAtPath:toFilePath]) {
|
NSSelectorFromString(@"fileName"));
|
||||||
Class FI = NSClassFromString(@"FileItem");
|
if (name.length && base.length)
|
||||||
if (FI) {
|
path = [base stringByAppendingPathComponent:name];
|
||||||
id item = [[FI alloc] init];
|
|
||||||
((void(*)(id,SEL,id,id))objc_msgSend)(item, NSSelectorFromString(@"setFilePath:attribute:"), toFilePath, nil);
|
|
||||||
return item;
|
|
||||||
}
|
}
|
||||||
|
if (path.length) [paths addObject:path];
|
||||||
}
|
}
|
||||||
|
if (!destination.length || !paths.count) {
|
||||||
|
NSLog(@"[Tweak] Zip: missing destination or inputs");
|
||||||
return nil;
|
return nil;
|
||||||
} @catch (NSException *e) { NSLog(@"[Tweak] Zip error: %@", e); return nil; }
|
}
|
||||||
|
[NSFileManager.defaultManager removeItemAtPath:destination error:nil];
|
||||||
|
NSString *message = nil;
|
||||||
|
BOOL ok = WQCreateZipArchive(paths, destination, base, &message);
|
||||||
|
if (!ok) {
|
||||||
|
NSLog(@"[Tweak] libarchive zip failed: %@", message);
|
||||||
|
return orig_ZipFiles
|
||||||
|
? ((id(*)(id,SEL,id,id,id))orig_ZipFiles)(
|
||||||
|
self, _cmd, files, toFilePath, currentDirectory)
|
||||||
|
: nil;
|
||||||
|
}
|
||||||
|
NSLog(@"[Tweak] Zip: %@", message);
|
||||||
|
WQRefreshActiveBrowser();
|
||||||
|
return WQArchiveItemAtPath(destination);
|
||||||
|
} @catch (NSException *e) {
|
||||||
|
NSLog(@"[Tweak] Zip error: %@", e);
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook: -[Zipper unZipFile:toPath:currentDirectory:outMessage:]
|
// Hook: -[Zipper unZipFile:toPath:currentDirectory:outMessage:]
|
||||||
static id hook_unZipFile(id self, SEL _cmd, id zipPath, id toPath, id currentDir, id *outMsg) {
|
static id hook_unZipFile(id self, SEL _cmd, id zipPath, id toPath, id currentDir, id *outMsg) {
|
||||||
@try {
|
@try {
|
||||||
loadMinizip();
|
NSString *archive = WQZipperPath(zipPath);
|
||||||
if (!g_minizipLoaded) return orig_unZipFile ? ((id(*)(id,SEL,id,id,id,id*))orig_unZipFile)(self, _cmd, zipPath, toPath, currentDir, outMsg) : nil;
|
NSString *destination = WQZipperPath(toPath);
|
||||||
// zipPath is a FileItem, get the actual path string
|
if (!destination.length && archive.length)
|
||||||
NSString *zipPathStr = zipPath;
|
destination = WQDefaultExtractDestination(archive);
|
||||||
if ([zipPath respondsToSelector:NSSelectorFromString(@"filePath")])
|
NSString *message = nil;
|
||||||
zipPathStr = [zipPath performSelector:NSSelectorFromString(@"filePath")];
|
BOOL ok = WQExtractArchive(archive, destination, &message);
|
||||||
|
if (!ok) {
|
||||||
unzFile64 uf = p_unzOpen64(((NSString *)zipPathStr).UTF8String);
|
NSLog(@"[Tweak] libarchive unzip failed: %@", message);
|
||||||
if (!uf) { if (outMsg) *outMsg = @"Failed to open zip"; return nil; }
|
return orig_unZipFile
|
||||||
|
? ((id(*)(id,SEL,id,id,id,id*))orig_unZipFile)(
|
||||||
NSFileManager *fm = [NSFileManager defaultManager];
|
self, _cmd, zipPath, toPath, currentDir, outMsg)
|
||||||
NSString *destPath = toPath;
|
: nil;
|
||||||
[fm createDirectoryAtPath:destPath withIntermediateDirectories:YES attributes:nil error:nil];
|
|
||||||
|
|
||||||
char filename[512];
|
|
||||||
uint8_t buf[32768];
|
|
||||||
int ret = p_unzGoToFirstFile(uf);
|
|
||||||
while (ret == 0) {
|
|
||||||
p_unzGetCurrentFileInfo64(uf, NULL, filename, sizeof(filename), NULL, 0, NULL, 0);
|
|
||||||
NSString *name = [NSString stringWithUTF8String:filename];
|
|
||||||
NSString *fullPath = [destPath stringByAppendingPathComponent:name];
|
|
||||||
|
|
||||||
if ([name hasSuffix:@"/"]) {
|
|
||||||
[fm createDirectoryAtPath:fullPath withIntermediateDirectories:YES attributes:nil error:nil];
|
|
||||||
} else {
|
|
||||||
[fm createDirectoryAtPath:[fullPath stringByDeletingLastPathComponent]
|
|
||||||
withIntermediateDirectories:YES attributes:nil error:nil];
|
|
||||||
|
|
||||||
if (p_unzOpenCurrentFilePassword(uf, NULL) == 0) {
|
|
||||||
NSMutableData *fileData = [NSMutableData data];
|
|
||||||
int bytesRead;
|
|
||||||
while ((bytesRead = p_unzReadCurrentFile(uf, buf, sizeof(buf))) > 0)
|
|
||||||
[fileData appendBytes:buf length:bytesRead];
|
|
||||||
p_unzCloseCurrentFile(uf);
|
|
||||||
[fileData writeToFile:fullPath atomically:YES];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret = p_unzGoToNextFile(uf);
|
|
||||||
}
|
|
||||||
p_unzClose(uf);
|
|
||||||
|
|
||||||
if (outMsg) *outMsg = @"OK";
|
|
||||||
|
|
||||||
// Return array of extracted FileItems (matching original behavior)
|
|
||||||
NSArray *contents = [fm contentsOfDirectoryAtPath:destPath error:nil];
|
|
||||||
if (contents.count > 0) {
|
|
||||||
Class FI = NSClassFromString(@"FileItem");
|
|
||||||
if (FI) {
|
|
||||||
id item = [[FI alloc] init];
|
|
||||||
((void(*)(id,SEL,id,id))objc_msgSend)(item, NSSelectorFromString(@"setFilePath:attribute:"), destPath, nil);
|
|
||||||
return @[item];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (outMsg) *outMsg = message;
|
||||||
|
NSLog(@"[Tweak] Unzip: %@", message);
|
||||||
|
return WQArchiveResultForDirectory(destination);
|
||||||
|
} @catch (NSException *e) {
|
||||||
|
NSLog(@"[Tweak] Unzip error: %@", e);
|
||||||
|
if (outMsg) *outMsg = e.reason;
|
||||||
return nil;
|
return nil;
|
||||||
} @catch (NSException *e) { NSLog(@"[Tweak] Unzip error: %@", e); if (outMsg) *outMsg = [e reason]; return nil; }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook: -[Zipper unZipFile:toPath:currentDirectory:withPassword:outMessage:]
|
// Hook: -[Zipper unZipFile:toPath:currentDirectory:withPassword:outMessage:]
|
||||||
static id hook_unZipFilePassword(id self, SEL _cmd, id zipPath, id toPath, id currentDir, id password, id *outMsg) {
|
static id hook_unZipFilePassword(id self, SEL _cmd, id zipPath, id toPath,
|
||||||
return hook_unZipFile(self, @selector(unZipFile:toPath:currentDirectory:outMessage:), zipPath, toPath, currentDir, outMsg);
|
id currentDir, id password, id *outMsg) {
|
||||||
|
return WQZipperExtract(self, _cmd, zipPath, toPath, currentDir, password,
|
||||||
|
outMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static IMP orig_un7Zip = NULL, orig_unDEB = NULL, orig_unRar = NULL;
|
||||||
|
static IMP orig_unRarPassword = NULL, orig_unBzip2 = NULL, orig_unXZip = NULL;
|
||||||
|
static IMP orig_unTar = NULL, orig_unGzip = NULL;
|
||||||
|
|
||||||
|
static id hook_un7Zip(id self, SEL _cmd, id file, id toPath, id cwd, id *out) {
|
||||||
|
return WQZipperExtract(self, _cmd, file, toPath, cwd, nil, out);
|
||||||
|
}
|
||||||
|
static id hook_unDEB(id self, SEL _cmd, id file, id toPath, id cwd, id *out) {
|
||||||
|
return WQZipperExtract(self, _cmd, file, toPath, cwd, nil, out);
|
||||||
|
}
|
||||||
|
static id hook_unRar(id self, SEL _cmd, id file, id toPath, id cwd, id *out) {
|
||||||
|
return WQZipperExtract(self, _cmd, file, toPath, cwd, nil, out);
|
||||||
|
}
|
||||||
|
static id hook_unRarPassword(id self, SEL _cmd, id file, id toPath, id cwd,
|
||||||
|
id password, id *out) {
|
||||||
|
return WQZipperExtract(self, _cmd, file, toPath, cwd, password, out);
|
||||||
|
}
|
||||||
|
static id hook_unBzip2(id self, SEL _cmd, id file, id toPath, id cwd, id *out) {
|
||||||
|
return WQZipperExtract(self, _cmd, file, toPath, cwd, nil, out);
|
||||||
|
}
|
||||||
|
static id hook_unXZip(id self, SEL _cmd, id file, id toPath, id cwd, id *out) {
|
||||||
|
return WQZipperExtract(self, _cmd, file, toPath, cwd, nil, out);
|
||||||
|
}
|
||||||
|
static id hook_unTar(id self, SEL _cmd, id file, id toPath, id cwd, id *out) {
|
||||||
|
return WQZipperExtract(self, _cmd, file, toPath, cwd, nil, out);
|
||||||
|
}
|
||||||
|
static id hook_unGzip(id self, SEL _cmd, id file, id toPath, id cwd, id *out) {
|
||||||
|
return WQZipperExtract(self, _cmd, file, toPath, cwd, nil, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Apps Manager Fix
|
#pragma mark - Apps Manager Fix
|
||||||
@@ -2152,6 +2328,47 @@ static void hook_installerInstall(id self, SEL _cmd, id sender)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static IMP orig_customMenuItems;
|
||||||
|
|
||||||
|
static id hook_customMenuItems(id self, SEL _cmd, id item, id sourceView,
|
||||||
|
CGRect sourceRect)
|
||||||
|
{
|
||||||
|
NSArray *original = orig_customMenuItems
|
||||||
|
? ((id(*)(id, SEL, id, id, CGRect))orig_customMenuItems)(
|
||||||
|
self, _cmd, item, sourceView, sourceRect) : @[];
|
||||||
|
NSMutableArray *items = [original isKindOfClass:NSArray.class]
|
||||||
|
? [original mutableCopy] : [NSMutableArray array];
|
||||||
|
NSString *path = WQZipperPath(item);
|
||||||
|
if (!path.length) return items;
|
||||||
|
|
||||||
|
__weak id weakSelf = self;
|
||||||
|
UIAction *zip = [UIAction actionWithTitle:@"压缩为 ZIP"
|
||||||
|
image:[UIImage systemImageNamed:@"archivebox"] identifier:nil
|
||||||
|
handler:^(__unused UIAction *action) {
|
||||||
|
WQRunPackaging(weakSelf, path, WQPackagingZip);
|
||||||
|
}];
|
||||||
|
UIAction *deb = [UIAction actionWithTitle:@"打包为 DEB"
|
||||||
|
image:[UIImage systemImageNamed:@"shippingbox"] identifier:nil
|
||||||
|
handler:^(__unused UIAction *action) {
|
||||||
|
WQRunPackaging(weakSelf, path, WQPackagingDeb);
|
||||||
|
}];
|
||||||
|
UIAction *ipa = [UIAction actionWithTitle:@"打包为 IPA"
|
||||||
|
image:[UIImage systemImageNamed:@"app.badge"] identifier:nil
|
||||||
|
handler:^(__unused UIAction *action) {
|
||||||
|
WQRunPackaging(weakSelf, path, WQPackagingIpa);
|
||||||
|
}];
|
||||||
|
BOOL isDirectory = NO;
|
||||||
|
[NSFileManager.defaultManager fileExistsAtPath:path isDirectory:&isDirectory];
|
||||||
|
if (!isDirectory)
|
||||||
|
ipa.attributes = UIMenuElementAttributesDisabled;
|
||||||
|
|
||||||
|
UIMenu *menu = [UIMenu menuWithTitle:@"压缩 / 打包"
|
||||||
|
image:[UIImage systemImageNamed:@"archivebox"] identifier:nil
|
||||||
|
options:0 children:@[zip, deb, ipa]];
|
||||||
|
[items addObject:menu];
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - Hook Installation
|
#pragma mark - Hook Installation
|
||||||
|
|
||||||
static void installHooks(void) {
|
static void installHooks(void) {
|
||||||
@@ -2435,6 +2652,32 @@ static void installHooks(void) {
|
|||||||
if (m) { orig_unZipFile = method_getImplementation(m); method_setImplementation(m, (IMP)hook_unZipFile); }
|
if (m) { orig_unZipFile = method_getImplementation(m); method_setImplementation(m, (IMP)hook_unZipFile); }
|
||||||
m = class_getInstanceMethod(zipper, NSSelectorFromString(@"unZipFile:toPath:currentDirectory:withPassword:outMessage:"));
|
m = class_getInstanceMethod(zipper, NSSelectorFromString(@"unZipFile:toPath:currentDirectory:withPassword:outMessage:"));
|
||||||
if (m) { orig_unZipFilePassword = method_getImplementation(m); method_setImplementation(m, (IMP)hook_unZipFilePassword); }
|
if (m) { orig_unZipFilePassword = method_getImplementation(m); method_setImplementation(m, (IMP)hook_unZipFilePassword); }
|
||||||
|
m = class_getInstanceMethod(zipper, NSSelectorFromString(@"un7ZipFile:toPath:currentDirectory:outMessage:"));
|
||||||
|
if (m) { orig_un7Zip = method_getImplementation(m); method_setImplementation(m, (IMP)hook_un7Zip); }
|
||||||
|
m = class_getInstanceMethod(zipper, NSSelectorFromString(@"unDEBFile:toPath:currentDirectory:outMessage:"));
|
||||||
|
if (m) { orig_unDEB = method_getImplementation(m); method_setImplementation(m, (IMP)hook_unDEB); }
|
||||||
|
m = class_getInstanceMethod(zipper, NSSelectorFromString(@"unRarFile:toPath:currentDirectory:outMessage:"));
|
||||||
|
if (m) { orig_unRar = method_getImplementation(m); method_setImplementation(m, (IMP)hook_unRar); }
|
||||||
|
m = class_getInstanceMethod(zipper, NSSelectorFromString(@"unRarFile:toPath:currentDirectory:withPassword:outMessage:"));
|
||||||
|
if (m) { orig_unRarPassword = method_getImplementation(m); method_setImplementation(m, (IMP)hook_unRarPassword); }
|
||||||
|
m = class_getInstanceMethod(zipper, NSSelectorFromString(@"unBzip2File:toPath:currentDirectory:outMessage:"));
|
||||||
|
if (m) { orig_unBzip2 = method_getImplementation(m); method_setImplementation(m, (IMP)hook_unBzip2); }
|
||||||
|
m = class_getInstanceMethod(zipper, NSSelectorFromString(@"unXZipFile:toPath:currentDirectory:outMessage:"));
|
||||||
|
if (m) { orig_unXZip = method_getImplementation(m); method_setImplementation(m, (IMP)hook_unXZip); }
|
||||||
|
m = class_getInstanceMethod(zipper, NSSelectorFromString(@"unTarFile:toPath:currentDirectory:outMessage:"));
|
||||||
|
if (m) { orig_unTar = method_getImplementation(m); method_setImplementation(m, (IMP)hook_unTar); }
|
||||||
|
m = class_getInstanceMethod(zipper, NSSelectorFromString(@"unGzipFile:toPath:currentDirectory:outMessage:"));
|
||||||
|
if (m) { orig_unGzip = method_getImplementation(m); method_setImplementation(m, (IMP)hook_unGzip); }
|
||||||
|
}
|
||||||
|
|
||||||
|
Class pageVC = NSClassFromString(@"TGPageViewController");
|
||||||
|
if (pageVC) {
|
||||||
|
Method m = class_getInstanceMethod(pageVC,
|
||||||
|
NSSelectorFromString(@"customMenuElementItemsForItem:sourceView:sourceRect:"));
|
||||||
|
if (m) {
|
||||||
|
orig_customMenuItems = method_getImplementation(m);
|
||||||
|
method_setImplementation(m, (IMP)hook_customMenuItems);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// License/integrity bypass
|
// License/integrity bypass
|
||||||
|
|||||||
+35
@@ -12,4 +12,39 @@ FOUNDATION_EXPORT BOOL WQExtractArchive(NSString *archivePath,
|
|||||||
/// (archive name with the archive suffix removed).
|
/// (archive name with the archive suffix removed).
|
||||||
FOUNDATION_EXPORT NSString *WQDefaultExtractDestination(NSString *archivePath);
|
FOUNDATION_EXPORT NSString *WQDefaultExtractDestination(NSString *archivePath);
|
||||||
|
|
||||||
|
/// Creates a ZIP archive from the given file/directory paths. Paths are
|
||||||
|
/// stored relative to `baseDirectory` when possible.
|
||||||
|
FOUNDATION_EXPORT BOOL WQCreateZipArchive(NSArray<NSString *> *paths,
|
||||||
|
NSString *destination,
|
||||||
|
NSString *_Nullable baseDirectory,
|
||||||
|
NSString *_Nullable *_Nullable message);
|
||||||
|
|
||||||
|
/// Creates a ZIP with every stored path prefixed (for .ipa Payload/...).
|
||||||
|
FOUNDATION_EXPORT BOOL WQCreateZipArchiveWithPrefix(NSArray<NSString *> *paths,
|
||||||
|
NSString *destination,
|
||||||
|
NSString *_Nullable baseDirectory,
|
||||||
|
NSString *_Nullable prefix,
|
||||||
|
NSString *_Nullable *_Nullable message);
|
||||||
|
|
||||||
|
/// Creates a .deb from a folder (or .app). A DEBIAN/control file inside the
|
||||||
|
/// source is reused; otherwise one is generated.
|
||||||
|
FOUNDATION_EXPORT BOOL WQCreateDebPackage(NSString *source,
|
||||||
|
NSString *destination,
|
||||||
|
NSString *_Nullable packageID,
|
||||||
|
NSString *_Nullable displayName,
|
||||||
|
NSString *_Nullable version,
|
||||||
|
NSString *_Nullable description,
|
||||||
|
NSString *_Nullable *_Nullable message);
|
||||||
|
|
||||||
|
/// Creates an .ipa from a .app folder or a folder containing Payload/.
|
||||||
|
FOUNDATION_EXPORT BOOL WQCreateIpaPackage(NSString *source,
|
||||||
|
NSString *destination,
|
||||||
|
NSString *_Nullable *_Nullable message);
|
||||||
|
|
||||||
|
/// Extracts the data.tar payload of a .deb into destination (not the ar
|
||||||
|
/// container members).
|
||||||
|
FOUNDATION_EXPORT BOOL WQExtractDebPayload(NSString *debPath,
|
||||||
|
NSString *destination,
|
||||||
|
NSString *_Nullable *_Nullable message);
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
+480
@@ -43,6 +43,24 @@ extern const char *archive_error_string(WQArchiveReader *);
|
|||||||
extern int archive_read_close(WQArchiveReader *);
|
extern int archive_read_close(WQArchiveReader *);
|
||||||
extern int archive_read_free(WQArchiveReader *);
|
extern int archive_read_free(WQArchiveReader *);
|
||||||
|
|
||||||
|
typedef struct archive WQArchiveWriter;
|
||||||
|
extern WQArchiveWriter *archive_write_new(void);
|
||||||
|
extern int archive_write_set_format_zip(WQArchiveWriter *);
|
||||||
|
extern int archive_write_set_format_pax_restricted(WQArchiveWriter *);
|
||||||
|
extern int archive_write_add_filter_gzip(WQArchiveWriter *);
|
||||||
|
extern int archive_write_open_filename(WQArchiveWriter *, const char *);
|
||||||
|
extern int archive_write_header(WQArchiveWriter *, WQArchiveEntry *);
|
||||||
|
extern ssize_t archive_write_data(WQArchiveWriter *, const void *, size_t);
|
||||||
|
extern int archive_write_close(WQArchiveWriter *);
|
||||||
|
extern int archive_write_free(WQArchiveWriter *);
|
||||||
|
extern WQArchiveEntry *archive_entry_new(void);
|
||||||
|
extern void archive_entry_free(WQArchiveEntry *);
|
||||||
|
extern void archive_entry_set_pathname(WQArchiveEntry *, const char *);
|
||||||
|
extern void archive_entry_set_filetype(WQArchiveEntry *, unsigned int);
|
||||||
|
extern void archive_entry_set_perm(WQArchiveEntry *, unsigned int);
|
||||||
|
extern void archive_entry_set_size(WQArchiveEntry *, long long);
|
||||||
|
extern void archive_entry_set_symlink(WQArchiveEntry *, const char *);
|
||||||
|
|
||||||
static NSString *WQArchiveSafeRelativePath(const char *name)
|
static NSString *WQArchiveSafeRelativePath(const char *name)
|
||||||
{
|
{
|
||||||
if (!name) return nil;
|
if (!name) return nil;
|
||||||
@@ -173,6 +191,468 @@ BOOL WQExtractArchive(NSString *archivePath, NSString *destination,
|
|||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static NSString *WQZipRelativePath(NSString *path, NSString *base)
|
||||||
|
{
|
||||||
|
if (!path.length) return nil;
|
||||||
|
if (base.length) {
|
||||||
|
NSString *canonicalPath = path.stringByStandardizingPath;
|
||||||
|
NSString *canonicalBase = base.stringByStandardizingPath;
|
||||||
|
NSString *prefix = [canonicalBase hasSuffix:@"/"] ? canonicalBase :
|
||||||
|
[canonicalBase stringByAppendingString:@"/"];
|
||||||
|
if ([canonicalPath hasPrefix:prefix])
|
||||||
|
return [canonicalPath substringFromIndex:prefix.length];
|
||||||
|
if ([canonicalPath isEqualToString:canonicalBase])
|
||||||
|
return canonicalPath.lastPathComponent;
|
||||||
|
}
|
||||||
|
if (![path isAbsolutePath]) {
|
||||||
|
NSString *relative = path.stringByStandardizingPath;
|
||||||
|
while ([relative hasPrefix:@"./"])
|
||||||
|
relative = [relative substringFromIndex:2];
|
||||||
|
return relative;
|
||||||
|
}
|
||||||
|
return path.lastPathComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BOOL WQZipAddPath(WQArchiveWriter *writer, NSString *path,
|
||||||
|
NSString *relative, NSString *prefix,
|
||||||
|
NSString *destination, NSString **message)
|
||||||
|
{
|
||||||
|
if ([path isEqualToString:destination]) return YES;
|
||||||
|
struct stat st = {0};
|
||||||
|
if (lstat(path.fileSystemRepresentation, &st) != 0) {
|
||||||
|
if (message) *message = [NSString stringWithFormat:@"zip: %@: %s",
|
||||||
|
path, strerror(errno)];
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
if (!relative.length || [relative hasPrefix:@"/"] ||
|
||||||
|
[relative containsString:@".."]) {
|
||||||
|
if (message) *message = @"zip: unsafe archive path";
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
WQArchiveEntry *entry = archive_entry_new();
|
||||||
|
if (!entry) {
|
||||||
|
if (message) *message = @"zip: cannot allocate entry";
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
NSString *entryPath = prefix.length
|
||||||
|
? [prefix stringByAppendingPathComponent:relative] : relative;
|
||||||
|
if (!entryPath.length) entryPath = relative;
|
||||||
|
if (S_ISDIR(st.st_mode)) {
|
||||||
|
NSString *name = [entryPath hasSuffix:@"/"] ? entryPath :
|
||||||
|
[entryPath stringByAppendingString:@"/"];
|
||||||
|
archive_entry_set_pathname(entry, name.UTF8String);
|
||||||
|
archive_entry_set_filetype(entry, WQ_AE_IFDIR);
|
||||||
|
archive_entry_set_perm(entry, st.st_mode & 0777);
|
||||||
|
archive_entry_set_size(entry, 0);
|
||||||
|
int result = archive_write_header(writer, entry);
|
||||||
|
archive_entry_free(entry);
|
||||||
|
if (result != WQ_ARCHIVE_OK) {
|
||||||
|
if (message) *message = @"zip: unable to write directory";
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
NSArray<NSString *> *children = [NSFileManager.defaultManager
|
||||||
|
contentsOfDirectoryAtPath:path error:nil];
|
||||||
|
for (NSString *child in [children sortedArrayUsingSelector:
|
||||||
|
@selector(localizedStandardCompare:)]) {
|
||||||
|
NSString *childPath = [path stringByAppendingPathComponent:child];
|
||||||
|
NSString *childRelative = [relative stringByAppendingPathComponent:child];
|
||||||
|
if (!WQZipAddPath(writer, childPath, childRelative, prefix,
|
||||||
|
destination, message))
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
if (S_ISLNK(st.st_mode)) {
|
||||||
|
char target[PATH_MAX];
|
||||||
|
ssize_t length = readlink(path.fileSystemRepresentation, target,
|
||||||
|
sizeof(target) - 1);
|
||||||
|
if (length <= 0) {
|
||||||
|
archive_entry_free(entry);
|
||||||
|
if (message) *message = [NSString stringWithFormat:@"zip: %@: %s",
|
||||||
|
path, strerror(errno)];
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
target[length] = 0;
|
||||||
|
NSString *symlinkPath = prefix.length
|
||||||
|
? [prefix stringByAppendingPathComponent:relative] : relative;
|
||||||
|
archive_entry_set_pathname(entry, symlinkPath.UTF8String);
|
||||||
|
archive_entry_set_filetype(entry, WQ_AE_IFLNK);
|
||||||
|
archive_entry_set_perm(entry, 0755);
|
||||||
|
archive_entry_set_symlink(entry, target);
|
||||||
|
int result = archive_write_header(writer, entry);
|
||||||
|
archive_entry_free(entry);
|
||||||
|
if (result != WQ_ARCHIVE_OK) {
|
||||||
|
if (message) *message = @"zip: unable to write symlink";
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
if (!S_ISREG(st.st_mode)) {
|
||||||
|
archive_entry_free(entry);
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
archive_entry_set_pathname(entry, entryPath.UTF8String);
|
||||||
|
archive_entry_set_filetype(entry, WQ_AE_IFREG);
|
||||||
|
archive_entry_set_perm(entry, st.st_mode & 0777);
|
||||||
|
archive_entry_set_size(entry, st.st_size);
|
||||||
|
int result = archive_write_header(writer, entry);
|
||||||
|
if (result != WQ_ARCHIVE_OK) {
|
||||||
|
archive_entry_free(entry);
|
||||||
|
if (message) *message = @"zip: unable to write header";
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
int descriptor = open(path.fileSystemRepresentation,
|
||||||
|
O_RDONLY | O_CLOEXEC);
|
||||||
|
if (descriptor < 0) {
|
||||||
|
archive_entry_free(entry);
|
||||||
|
if (message) *message = [NSString stringWithFormat:@"zip: %@: %s",
|
||||||
|
path, strerror(errno)];
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
char buffer[65536];
|
||||||
|
BOOL ok = YES;
|
||||||
|
ssize_t got;
|
||||||
|
while ((got = read(descriptor, buffer, sizeof(buffer))) > 0) {
|
||||||
|
ssize_t written = 0;
|
||||||
|
while (written < got) {
|
||||||
|
ssize_t step = archive_write_data(writer, buffer + written,
|
||||||
|
(size_t)(got - written));
|
||||||
|
if (step <= 0) {
|
||||||
|
ok = NO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
written += step;
|
||||||
|
}
|
||||||
|
if (!ok) break;
|
||||||
|
}
|
||||||
|
if (got < 0) ok = NO;
|
||||||
|
close(descriptor);
|
||||||
|
archive_entry_free(entry);
|
||||||
|
if (!ok && message) *message = [NSString stringWithFormat:@"zip: %@: %s",
|
||||||
|
path, strerror(errno)];
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL WQCreateZipArchive(NSArray<NSString *> *paths, NSString *destination,
|
||||||
|
NSString *baseDirectory, NSString **message)
|
||||||
|
{
|
||||||
|
if (!paths.count || !destination.length) {
|
||||||
|
if (message) *message = @"zip: missing input or destination";
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
WQArchiveWriter *writer = archive_write_new();
|
||||||
|
if (!writer) {
|
||||||
|
if (message) *message = @"zip: cannot allocate archive";
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
if (archive_write_set_format_zip(writer) != WQ_ARCHIVE_OK ||
|
||||||
|
archive_write_open_filename(writer,
|
||||||
|
destination.fileSystemRepresentation) != WQ_ARCHIVE_OK) {
|
||||||
|
if (message) *message = @"zip: cannot open destination";
|
||||||
|
archive_write_free(writer);
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
BOOL ok = YES;
|
||||||
|
for (NSString *path in paths) {
|
||||||
|
NSString *relative = WQZipRelativePath(path, baseDirectory);
|
||||||
|
if (!WQZipAddPath(writer, path, relative, nil, destination, message)) {
|
||||||
|
ok = NO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
archive_write_close(writer);
|
||||||
|
archive_write_free(writer);
|
||||||
|
if (ok && message)
|
||||||
|
*message = [NSString stringWithFormat:@"zip: %lu item(s) -> %@",
|
||||||
|
(unsigned long)paths.count, destination];
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BOOL WQCreateArchiveWithPrefix(NSArray<NSString *> *paths,
|
||||||
|
NSString *destination,
|
||||||
|
NSString *baseDirectory,
|
||||||
|
NSString *prefix,
|
||||||
|
BOOL zip,
|
||||||
|
NSString **message)
|
||||||
|
{
|
||||||
|
if (!paths.count || !destination.length) {
|
||||||
|
if (message) *message = @"archive: missing input or destination";
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
[NSFileManager.defaultManager removeItemAtPath:destination error:nil];
|
||||||
|
WQArchiveWriter *writer = archive_write_new();
|
||||||
|
if (!writer) {
|
||||||
|
if (message) *message = @"archive: cannot allocate writer";
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
int result = zip ? archive_write_set_format_zip(writer)
|
||||||
|
: archive_write_set_format_pax_restricted(writer);
|
||||||
|
if (result != WQ_ARCHIVE_OK ||
|
||||||
|
(!zip && archive_write_add_filter_gzip(writer) != WQ_ARCHIVE_OK) ||
|
||||||
|
archive_write_open_filename(writer,
|
||||||
|
destination.fileSystemRepresentation) != WQ_ARCHIVE_OK) {
|
||||||
|
if (message) *message = @"archive: cannot open destination";
|
||||||
|
archive_write_free(writer);
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
BOOL ok = YES;
|
||||||
|
for (NSString *path in paths) {
|
||||||
|
NSString *relative = WQZipRelativePath(path, baseDirectory);
|
||||||
|
if (!WQZipAddPath(writer, path, relative, prefix, destination,
|
||||||
|
message)) {
|
||||||
|
ok = NO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
archive_write_close(writer);
|
||||||
|
archive_write_free(writer);
|
||||||
|
if (ok && message)
|
||||||
|
*message = [NSString stringWithFormat:@"archive: %lu item(s) -> %@",
|
||||||
|
(unsigned long)paths.count, destination];
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL WQCreateZipArchiveWithPrefix(NSArray<NSString *> *paths,
|
||||||
|
NSString *destination,
|
||||||
|
NSString *baseDirectory,
|
||||||
|
NSString *prefix,
|
||||||
|
NSString **message)
|
||||||
|
{
|
||||||
|
return WQCreateArchiveWithPrefix(paths, destination, baseDirectory,
|
||||||
|
prefix, YES, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BOOL WQCreateTarGz(NSArray<NSString *> *paths, NSString *destination,
|
||||||
|
NSString *baseDirectory, NSString *prefix,
|
||||||
|
NSString **message)
|
||||||
|
{
|
||||||
|
return WQCreateArchiveWithPrefix(paths, destination, baseDirectory,
|
||||||
|
prefix, NO, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WQArAppendMember(NSMutableData *archive, NSString *name,
|
||||||
|
NSData *data)
|
||||||
|
{
|
||||||
|
char header[61] = {0};
|
||||||
|
snprintf(header, sizeof(header),
|
||||||
|
"%-16s%-12s%-6s%-6s%-8s%-10s`\n",
|
||||||
|
name.UTF8String, "0", "0", "0", "100644",
|
||||||
|
[[NSString stringWithFormat:@"%lu", (unsigned long)data.length]
|
||||||
|
UTF8String]);
|
||||||
|
[archive appendBytes:header length:60];
|
||||||
|
[archive appendData:data];
|
||||||
|
if (data.length & 1) {
|
||||||
|
char padding = '\n';
|
||||||
|
[archive appendBytes:&padding length:1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static NSString *WQSafePackageIdentifier(NSString *value)
|
||||||
|
{
|
||||||
|
NSMutableString *result = [NSMutableString string];
|
||||||
|
for (NSUInteger i = 0; i < value.length; i++) {
|
||||||
|
unichar c = [value characterAtIndex:i];
|
||||||
|
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
|
||||||
|
(c >= '0' && c <= '9') || c == '.' || c == '-' || c == '_')
|
||||||
|
[result appendFormat:@"%C", c];
|
||||||
|
else
|
||||||
|
[result appendString:@"-"];
|
||||||
|
}
|
||||||
|
NSString *trimmed = [result stringByTrimmingCharactersInSet:
|
||||||
|
[NSCharacterSet characterSetWithCharactersInString:@"-._"]];
|
||||||
|
return trimmed.length ? trimmed.lowercaseString : @"com.filzaslop.package";
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL WQCreateDebPackage(NSString *source, NSString *destination,
|
||||||
|
NSString *packageID, NSString *displayName,
|
||||||
|
NSString *version, NSString *description,
|
||||||
|
NSString **message)
|
||||||
|
{
|
||||||
|
NSFileManager *fm = NSFileManager.defaultManager;
|
||||||
|
BOOL isDirectory = NO;
|
||||||
|
if (![fm fileExistsAtPath:source isDirectory:&isDirectory]) {
|
||||||
|
if (message) *message = @"deb: source not found";
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
NSString *name = displayName.length ? displayName :
|
||||||
|
source.lastPathComponent.stringByDeletingPathExtension;
|
||||||
|
NSString *package = packageID.length ? packageID :
|
||||||
|
[@"com.filzaslop." stringByAppendingString:
|
||||||
|
WQSafePackageIdentifier(name)];
|
||||||
|
NSString *controlText = nil;
|
||||||
|
NSString *existingControl = [[source stringByAppendingPathComponent:@"DEBIAN"]
|
||||||
|
stringByAppendingPathComponent:@"control"];
|
||||||
|
if (isDirectory)
|
||||||
|
controlText = [NSString stringWithContentsOfFile:existingControl
|
||||||
|
encoding:NSUTF8StringEncoding error:nil];
|
||||||
|
if (!controlText.length) {
|
||||||
|
controlText = [NSString stringWithFormat:
|
||||||
|
@"Package: %@\n"
|
||||||
|
@"Name: %@\n"
|
||||||
|
@"Version: %@\n"
|
||||||
|
@"Architecture: iphoneos-arm64\n"
|
||||||
|
@"Maintainer: FilzaSlop <filzaslop@localhost>\n"
|
||||||
|
@"Section: Tweaks\n"
|
||||||
|
@"Priority: optional\n"
|
||||||
|
@"Description: %@\n",
|
||||||
|
package, name, version.length ? version : @"1.0",
|
||||||
|
description.length ? description :
|
||||||
|
[NSString stringWithFormat:@"Packaged from %@ by Filza 27.2.",
|
||||||
|
name]];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *temp = [NSTemporaryDirectory() stringByAppendingPathComponent:
|
||||||
|
[NSString stringWithFormat:@"WQDeb-%@", NSUUID.UUID.UUIDString]];
|
||||||
|
NSString *controlDir = [temp stringByAppendingPathComponent:@"control"];
|
||||||
|
NSString *controlPath = [controlDir stringByAppendingPathComponent:@"control"];
|
||||||
|
NSString *controlTar = [temp stringByAppendingPathComponent:@"control.tar.gz"];
|
||||||
|
NSString *dataTar = [temp stringByAppendingPathComponent:@"data.tar.gz"];
|
||||||
|
NSString *debianBinary = [temp stringByAppendingPathComponent:@"debian-binary"];
|
||||||
|
[fm createDirectoryAtPath:controlDir withIntermediateDirectories:YES
|
||||||
|
attributes:nil error:nil];
|
||||||
|
[controlText writeToFile:controlPath atomically:YES
|
||||||
|
encoding:NSUTF8StringEncoding error:nil];
|
||||||
|
[@"2.0\n" writeToFile:debianBinary atomically:YES
|
||||||
|
encoding:NSUTF8StringEncoding error:nil];
|
||||||
|
|
||||||
|
NSString *error = nil;
|
||||||
|
if (!WQCreateTarGz(@[controlPath], controlTar, controlDir, nil, &error)) {
|
||||||
|
if (message) *message = [@"deb control: " stringByAppendingString:
|
||||||
|
(error ?: @"failed")];
|
||||||
|
[fm removeItemAtPath:temp error:nil];
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSArray<NSString *> *dataPaths = nil;
|
||||||
|
NSString *dataBase = nil;
|
||||||
|
NSString *dataPrefix = nil;
|
||||||
|
if (isDirectory && [source.pathExtension.lowercaseString isEqualToString:@"app"]) {
|
||||||
|
dataPaths = @[source];
|
||||||
|
dataBase = source.stringByDeletingLastPathComponent;
|
||||||
|
dataPrefix = @"Applications";
|
||||||
|
} else if (isDirectory) {
|
||||||
|
NSArray *children = [fm contentsOfDirectoryAtPath:source error:nil];
|
||||||
|
NSMutableArray *paths = [NSMutableArray array];
|
||||||
|
for (NSString *child in children ?: @[]) {
|
||||||
|
if ([child isEqualToString:@"DEBIAN"]) continue;
|
||||||
|
[paths addObject:[source stringByAppendingPathComponent:child]];
|
||||||
|
}
|
||||||
|
dataPaths = paths;
|
||||||
|
dataBase = source;
|
||||||
|
} else {
|
||||||
|
dataPaths = @[source];
|
||||||
|
dataBase = source.stringByDeletingLastPathComponent;
|
||||||
|
}
|
||||||
|
if (!WQCreateTarGz(dataPaths ?: @[], dataTar, dataBase, dataPrefix,
|
||||||
|
&error)) {
|
||||||
|
if (message) *message = [@"deb data: " stringByAppendingString:
|
||||||
|
(error ?: @"failed")];
|
||||||
|
[fm removeItemAtPath:temp error:nil];
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSData *controlData = [NSData dataWithContentsOfFile:controlTar];
|
||||||
|
NSData *dataData = [NSData dataWithContentsOfFile:dataTar];
|
||||||
|
NSData *binaryData = [NSData dataWithContentsOfFile:debianBinary];
|
||||||
|
if (!controlData.length || !dataData.length || !binaryData.length) {
|
||||||
|
if (message) *message = @"deb: temporary archive read failed";
|
||||||
|
[fm removeItemAtPath:temp error:nil];
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
NSMutableData *ar = [NSMutableData dataWithBytes:"!<arch>\n" length:8];
|
||||||
|
WQArAppendMember(ar, @"debian-binary", binaryData);
|
||||||
|
WQArAppendMember(ar, @"control.tar.gz", controlData);
|
||||||
|
WQArAppendMember(ar, @"data.tar.gz", dataData);
|
||||||
|
BOOL ok = [ar writeToFile:destination atomically:YES];
|
||||||
|
[fm removeItemAtPath:temp error:nil];
|
||||||
|
if (ok && message)
|
||||||
|
*message = [NSString stringWithFormat:@"deb: %@ -> %@", package,
|
||||||
|
destination];
|
||||||
|
else if (!ok && message)
|
||||||
|
*message = @"deb: cannot write destination";
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL WQCreateIpaPackage(NSString *source, NSString *destination,
|
||||||
|
NSString **message)
|
||||||
|
{
|
||||||
|
NSFileManager *fm = NSFileManager.defaultManager;
|
||||||
|
BOOL isDirectory = NO;
|
||||||
|
if (![fm fileExistsAtPath:source isDirectory:&isDirectory] || !isDirectory) {
|
||||||
|
if (message) *message = @"ipa: source must be a folder";
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
NSArray<NSString *> *paths = nil;
|
||||||
|
NSString *base = nil;
|
||||||
|
NSString *prefix = nil;
|
||||||
|
NSString *payload = [source stringByAppendingPathComponent:@"Payload"];
|
||||||
|
BOOL payloadIsDirectory = NO;
|
||||||
|
if ([fm fileExistsAtPath:payload isDirectory:&payloadIsDirectory] &&
|
||||||
|
payloadIsDirectory) {
|
||||||
|
paths = @[payload];
|
||||||
|
base = source;
|
||||||
|
} else if ([source.pathExtension.lowercaseString isEqualToString:@"app"]) {
|
||||||
|
paths = @[source];
|
||||||
|
base = source.stringByDeletingLastPathComponent;
|
||||||
|
prefix = @"Payload";
|
||||||
|
} else {
|
||||||
|
NSString *appPath = nil;
|
||||||
|
for (NSString *child in [fm contentsOfDirectoryAtPath:source error:nil]) {
|
||||||
|
if ([child.pathExtension.lowercaseString isEqualToString:@"app"]) {
|
||||||
|
appPath = [source stringByAppendingPathComponent:child];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (appPath) {
|
||||||
|
paths = @[appPath];
|
||||||
|
base = source;
|
||||||
|
prefix = @"Payload";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!paths.count) {
|
||||||
|
if (message) *message = @"ipa: no Payload/ or .app found";
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
BOOL ok = WQCreateZipArchiveWithPrefix(paths, destination, base, prefix,
|
||||||
|
message);
|
||||||
|
if (ok && message)
|
||||||
|
*message = [NSString stringWithFormat:@"ipa: -> %@", destination];
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL WQExtractDebPayload(NSString *debPath, NSString *destination,
|
||||||
|
NSString **message)
|
||||||
|
{
|
||||||
|
NSFileManager *fm = NSFileManager.defaultManager;
|
||||||
|
NSString *temp = [NSTemporaryDirectory() stringByAppendingPathComponent:
|
||||||
|
[NSString stringWithFormat:@"WQDebX-%@", NSUUID.UUID.UUIDString]];
|
||||||
|
[fm createDirectoryAtPath:temp withIntermediateDirectories:YES
|
||||||
|
attributes:nil error:nil];
|
||||||
|
NSString *error = nil;
|
||||||
|
if (!WQExtractArchive(debPath, temp, &error)) {
|
||||||
|
[fm removeItemAtPath:temp error:nil];
|
||||||
|
if (message) *message = error ?: @"deb: cannot unpack container";
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
NSString *dataArchive = nil;
|
||||||
|
for (NSString *name in [fm contentsOfDirectoryAtPath:temp error:nil]) {
|
||||||
|
if ([name hasPrefix:@"data.tar"]) {
|
||||||
|
dataArchive = [temp stringByAppendingPathComponent:name];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!dataArchive.length) {
|
||||||
|
[fm removeItemAtPath:temp error:nil];
|
||||||
|
if (message) *message = @"deb: data.tar not found";
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
BOOL ok = WQExtractArchive(dataArchive, destination, &error);
|
||||||
|
[fm removeItemAtPath:temp error:nil];
|
||||||
|
if (message) *message = ok ? [NSString stringWithFormat:
|
||||||
|
@"deb: payload -> %@", destination] : (error ?: @"deb: payload failed");
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
NSString *WQDefaultExtractDestination(NSString *archivePath)
|
NSString *WQDefaultExtractDestination(NSString *archivePath)
|
||||||
{
|
{
|
||||||
NSString *name = archivePath.lastPathComponent;
|
NSString *name = archivePath.lastPathComponent;
|
||||||
|
|||||||
@@ -56,3 +56,5 @@ FOUNDATION_EXPORT NSString * _Nonnull WQFallbackDisplayName(NSString * _Nonnull
|
|||||||
/// Clears the cached app list and requests a fresh per-container traversal the
|
/// Clears the cached app list and requests a fresh per-container traversal the
|
||||||
/// next time the App Manager asks for applications.
|
/// next time the App Manager asks for applications.
|
||||||
FOUNDATION_EXPORT void WQRequestAppManagerRescan(void);
|
FOUNDATION_EXPORT void WQRequestAppManagerRescan(void);
|
||||||
|
/// The writable trash directory used by Filza under normal signing.
|
||||||
|
FOUNDATION_EXPORT NSString * _Nonnull WQTrashDirectory(void);
|
||||||
+49
-1
@@ -455,6 +455,16 @@ static NSArray<NSDictionary<NSString *, NSString *> *> *WQFavoriteEntries(void)
|
|||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NSString *WQTrashDirectory(void)
|
||||||
|
{
|
||||||
|
NSString *trash = [MCMFilzaVirtualRoot()
|
||||||
|
stringByAppendingPathComponent:@".Trash"];
|
||||||
|
[NSFileManager.defaultManager createDirectoryAtPath:trash
|
||||||
|
withIntermediateDirectories:YES
|
||||||
|
attributes:@{ NSFilePosixPermissions: @0700 } error:nil];
|
||||||
|
return trash;
|
||||||
|
}
|
||||||
|
|
||||||
static void WQInstallFavorites(void)
|
static void WQInstallFavorites(void)
|
||||||
{
|
{
|
||||||
Class prefsClass = NSClassFromString(@"TGPreferences");
|
Class prefsClass = NSClassFromString(@"TGPreferences");
|
||||||
@@ -471,6 +481,43 @@ static void WQInstallFavorites(void)
|
|||||||
? (NSMutableArray *)existing : nil;
|
? (NSMutableArray *)existing : nil;
|
||||||
NSMutableIndexSet *stale = [NSMutableIndexSet indexSet];
|
NSMutableIndexSet *stale = [NSMutableIndexSet indexSet];
|
||||||
NSFileManager *fm = NSFileManager.defaultManager;
|
NSFileManager *fm = NSFileManager.defaultManager;
|
||||||
|
|
||||||
|
// Filza's built-in Trash favorite is /var/mobile/Library/Filza/.Trash,
|
||||||
|
// which does not exist in a normally signed app. Repoint it at the
|
||||||
|
// writable trash directory inside the app container.
|
||||||
|
NSString *trash = WQTrashDirectory();
|
||||||
|
NSMutableArray *links = mutableLinks;
|
||||||
|
if (!links && [existing isKindOfClass:NSArray.class]) {
|
||||||
|
SEL setter = NSSelectorFromString(@"setFavoritedLinks:");
|
||||||
|
if ([prefs respondsToSelector:setter]) {
|
||||||
|
links = [existing mutableCopy];
|
||||||
|
((void(*)(id, SEL, id))objc_msgSend)(prefs, setter, links);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BOOL trashChanged = NO;
|
||||||
|
if (links) {
|
||||||
|
for (NSUInteger index = 0; index < links.count; index++) {
|
||||||
|
if (![links[index] isKindOfClass:NSDictionary.class]) continue;
|
||||||
|
NSDictionary *item = links[index];
|
||||||
|
NSString *name = item[@"name"];
|
||||||
|
NSString *path = item[@"path"];
|
||||||
|
BOOL isTrash = [name isEqualToString:@"Trash"] ||
|
||||||
|
[path isEqualToString:@"/var/mobile/Library/Filza/.Trash"];
|
||||||
|
if (!isTrash) continue;
|
||||||
|
if ([path isEqualToString:trash] && [fm fileExistsAtPath:path])
|
||||||
|
continue;
|
||||||
|
NSMutableDictionary *updated = [item mutableCopy];
|
||||||
|
updated[@"path"] = trash;
|
||||||
|
links[index] = updated;
|
||||||
|
trashChanged = YES;
|
||||||
|
}
|
||||||
|
if (trashChanged) {
|
||||||
|
((void(*)(id, SEL))objc_msgSend)(prefs,
|
||||||
|
NSSelectorFromString(@"saveFavoritedLinks"));
|
||||||
|
NSLog(@"[WQ] Trash favorite -> %@", trash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mutableLinks) {
|
if (mutableLinks) {
|
||||||
[mutableLinks enumerateObjectsUsingBlock:^(id item, NSUInteger idx, BOOL *stop) {
|
[mutableLinks enumerateObjectsUsingBlock:^(id item, NSUInteger idx, BOOL *stop) {
|
||||||
if (![item isKindOfClass:NSDictionary.class]) return;
|
if (![item isKindOfClass:NSDictionary.class]) return;
|
||||||
@@ -533,7 +580,8 @@ static void WQInstallBundledTestFiles(void)
|
|||||||
NSString *folder = WQVirtualRootFolder();
|
NSString *folder = WQVirtualRootFolder();
|
||||||
NSString *source = [NSBundle.mainBundle.bundlePath
|
NSString *source = [NSBundle.mainBundle.bundlePath
|
||||||
stringByAppendingPathComponent:@"WQTestFiles"];
|
stringByAppendingPathComponent:@"WQTestFiles"];
|
||||||
NSArray<NSString *> *names = @[@"test.deb", @"test"];
|
NSArray<NSString *> *names = @[@"test.deb", @"test", @"test.zip",
|
||||||
|
@"test.ipa"];
|
||||||
for (NSString *name in names) {
|
for (NSString *name in names) {
|
||||||
NSString *from = [source stringByAppendingPathComponent:name];
|
NSString *from = [source stringByAppendingPathComponent:name];
|
||||||
NSString *to = [folder stringByAppendingPathComponent:name];
|
NSString *to = [folder stringByAppendingPathComponent:name];
|
||||||
|
|||||||
+52
-1
@@ -593,7 +593,9 @@ static BOOL WQRunArchiveCommand(NSArray<NSString *> *words,
|
|||||||
destination = WQDefaultExtractDestination(archive);
|
destination = WQDefaultExtractDestination(archive);
|
||||||
|
|
||||||
NSString *message = nil;
|
NSString *message = nil;
|
||||||
BOOL ok = WQExtractArchive(archive, destination, &message);
|
BOOL isDeb = [archive.pathExtension.lowercaseString isEqualToString:@"deb"];
|
||||||
|
BOOL ok = isDeb ? WQExtractDebPayload(archive, destination, &message)
|
||||||
|
: WQExtractArchive(archive, destination, &message);
|
||||||
WQShellPrintf(context, @"%@\r\n", message ?:
|
WQShellPrintf(context, @"%@\r\n", message ?:
|
||||||
(ok ? @"extract: ok" : @"extract: failed"));
|
(ok ? @"extract: ok" : @"extract: failed"));
|
||||||
return YES;
|
return YES;
|
||||||
@@ -775,6 +777,9 @@ static void WQPrintSBXHelp(WQShellContext *context)
|
|||||||
" sbx_id <container_path> identify a container\r\n"
|
" sbx_id <container_path> identify a container\r\n"
|
||||||
" mount list mounted filesystems\r\n"
|
" mount list mounted filesystems\r\n"
|
||||||
" extract <archive> [-C dir] unzip/tar/gz/7z/rar/xz/bz2/zst\r\n"
|
" extract <archive> [-C dir] unzip/tar/gz/7z/rar/xz/bz2/zst\r\n"
|
||||||
|
" zip <output.zip> <paths> create a ZIP archive\r\n"
|
||||||
|
" mkdeb <folder> [out.deb] build a Debian package\r\n"
|
||||||
|
" mkipa <folder> [out.ipa] build an IPA from .app/Payload\r\n"
|
||||||
" neofetch system information\r\n"
|
" neofetch system information\r\n"
|
||||||
" respring restart SpringBoard\r\n"
|
" respring restart SpringBoard\r\n"
|
||||||
" apt / apt-get update|list|search|show|install|remove\r\n"
|
" apt / apt-get update|list|search|show|install|remove\r\n"
|
||||||
@@ -850,6 +855,52 @@ static void WQShellLoop(WQShellContext *context)
|
|||||||
[first isEqualToString:@"dpkg-deb"]) {
|
[first isEqualToString:@"dpkg-deb"]) {
|
||||||
WQRunPackages(words, context.output, context.error);
|
WQRunPackages(words, context.output, context.error);
|
||||||
fflush(context.output);
|
fflush(context.output);
|
||||||
|
} else if ([first isEqualToString:@"mkdeb"] ||
|
||||||
|
[first isEqualToString:@"mkipa"]) {
|
||||||
|
if (words.count < 2) {
|
||||||
|
WQShellPrintf(context,
|
||||||
|
@"usage: %@ <source-folder> [output]\r\n",
|
||||||
|
first);
|
||||||
|
} else {
|
||||||
|
NSString *source = WQExpandPath(words[1]);
|
||||||
|
NSString *suffix = [first isEqualToString:@"mkdeb"]
|
||||||
|
? @".deb" : @".ipa";
|
||||||
|
NSString *destination = words.count > 2
|
||||||
|
? WQExpandPath(words[2])
|
||||||
|
: [[source stringByDeletingLastPathComponent]
|
||||||
|
stringByAppendingPathComponent:
|
||||||
|
[source.lastPathComponent
|
||||||
|
stringByAppendingString:suffix]];
|
||||||
|
NSString *message = nil;
|
||||||
|
BOOL ok = [first isEqualToString:@"mkdeb"]
|
||||||
|
? WQCreateDebPackage(source, destination,
|
||||||
|
nil, nil, nil, nil, &message)
|
||||||
|
: WQCreateIpaPackage(source, destination,
|
||||||
|
&message);
|
||||||
|
WQShellPrintf(context, @"%@\r\n", message ?:
|
||||||
|
(ok ? @"ok" : @"failed"));
|
||||||
|
}
|
||||||
|
} else if ([first isEqualToString:@"zip"] ||
|
||||||
|
[first isEqualToString:@"compress"]) {
|
||||||
|
if (words.count < 3) {
|
||||||
|
WQShellWrite(context,
|
||||||
|
"usage: zip <output.zip> <file-or-folder>...\r\n");
|
||||||
|
} else {
|
||||||
|
NSString *destination = WQExpandPath(words[1]);
|
||||||
|
NSMutableArray *paths = [NSMutableArray array];
|
||||||
|
for (NSUInteger index = 2; index < words.count; index++)
|
||||||
|
[paths addObject:WQExpandPath(words[index])];
|
||||||
|
char cwdBuffer[PATH_MAX];
|
||||||
|
NSString *base = getcwd(cwdBuffer, sizeof(cwdBuffer))
|
||||||
|
? @(cwdBuffer) : nil;
|
||||||
|
[NSFileManager.defaultManager removeItemAtPath:
|
||||||
|
destination error:nil];
|
||||||
|
NSString *message = nil;
|
||||||
|
BOOL ok = WQCreateZipArchive(paths, destination,
|
||||||
|
base, &message);
|
||||||
|
WQShellPrintf(context, @"%@\r\n", message ?:
|
||||||
|
(ok ? @"zip: ok" : @"zip: failed"));
|
||||||
|
}
|
||||||
} else if (WQRunArchiveCommand(words, context)) {
|
} else if (WQRunArchiveCommand(words, context)) {
|
||||||
// extracted with libarchive
|
// extracted with libarchive
|
||||||
} else if (WQRunExternalExecutable(words, context)) {
|
} else if (WQRunExternalExecutable(words, context)) {
|
||||||
|
|||||||
@@ -106,6 +106,12 @@ if [[ -f "$REPO_ROOT/tests/test" ]]; then
|
|||||||
cp "$REPO_ROOT/tests/test" "$APP/WQTestFiles/test"
|
cp "$REPO_ROOT/tests/test" "$APP/WQTestFiles/test"
|
||||||
chmod 0755 "$APP/WQTestFiles/test"
|
chmod 0755 "$APP/WQTestFiles/test"
|
||||||
fi
|
fi
|
||||||
|
if [[ -f "$REPO_ROOT/tests/test.zip" ]]; then
|
||||||
|
cp "$REPO_ROOT/tests/test.zip" "$APP/WQTestFiles/test.zip"
|
||||||
|
fi
|
||||||
|
if [[ -f "$REPO_ROOT/tests/test.ipa" ]]; then
|
||||||
|
cp "$REPO_ROOT/tests/test.ipa" "$APP/WQTestFiles/test.ipa"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "==> Injecting dylib"
|
echo "==> Injecting dylib"
|
||||||
mkdir -p "$APP/Frameworks"
|
mkdir -p "$APP/Frameworks"
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user