Browse Source

add path resilience and iOS/tvOS support, add ability to create packages from status and dpkg info files

Kevin Bradley 7 months ago
parent
commit
5a8d0097aa

+ 3 - 0
bootstrapTool/Classes/HelperClass.h

@@ -5,7 +5,9 @@
 + (NSArray *)returnForProcess:(NSString *)call;
 + (InputPackage *)packageForDeb:(NSString *)debFile;
 + (NSString *)octalFromSymbols:(NSString *)theSymbols;
++ (NSString *)octalFromGroupSymbols:(NSString *)theSymbols;
 + (NSArray <StatusPackageModel*>*)statusInstalledPackagesFromFile:(NSString *)statusFile;
++ (NSArray <StatusPackageModel*>*)statusInstalledPackagesFromFile:(NSString *)statusFile bootstrapPath:(NSString *)bootstrapPath;
 + (NSString *)singleLineReturnForProcess:(NSString *)format, ...;
 + (NSArray *)arrayReturnForTask:(NSString *)taskBinary withArguments:(NSArray *)taskArguments;
 + (void)runProcess:(NSString *)call environment:(NSDictionary *)env currentPath:(NSString *)currentPath completion:(void(^)(NSString *output, NSInteger returnStatus))block;
@@ -16,4 +18,5 @@
 + (int)cleanBootstrapAtPath:(NSString *)bootstrapPath;
 + (int)deletePackage:(NSString *)deletePackage inBootstrap:(NSString *)bootstrapPath;
 + (int)createNewBootstrap:(NSString *)bootstrapPath withPackages:(NSString *)packagesFolder;
++ (NSString *)getNumberWithLimit:(NSInteger)limit;
 @end

+ 31 - 8
bootstrapTool/Classes/HelperClass.m

@@ -1,7 +1,9 @@
 
 #import "HelperClass.h"
 #import "StatusPackageModel.h"
-
+#if TARGET_OS_IOS || TARGET_OS_TV
+#import "NSTask.h"
+#endif
 @implementation HelperClass
 
 /*
@@ -190,7 +192,11 @@
     return 0;
 }
 
-+ (NSArray <StatusPackageModel*>*)statusInstalledPackagesFromFile:(NSString *)statusFile {
++ (NSArray <StatusPackageModel*>*)statusInstalledPackagesFromFile:(NSString *)statusFile  {
+    return [self statusInstalledPackagesFromFile:statusFile bootstrapPath:nil];
+}
+
++ (NSArray <StatusPackageModel*>*)statusInstalledPackagesFromFile:(NSString *)statusFile bootstrapPath:(NSString *)bootstrapPath {
     
     if (![FM fileExistsAtPath:statusFile]) {
         return nil;
@@ -202,19 +208,36 @@
     //NSMutableDictionary *mutableDict = [[NSMutableDictionary alloc] init];
     for (id currentItem in lineArray) {
         StatusPackageModel *debModel = [[StatusPackageModel alloc] initWithRawControlString:currentItem];
-        if (debModel != nil)
+        if (debModel != nil) {
+            if (bootstrapPath) {
+                debModel.bootstrapPath = bootstrapPath;
+            }
             [mutableList addObject:debModel];
+        }
     }
     NSSortDescriptor *nameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES
                                                                     selector:@selector(localizedCaseInsensitiveCompare:)];
     NSSortDescriptor *packageDescriptor = [[NSSortDescriptor alloc] initWithKey:@"package" ascending:YES
                                                                        selector:@selector(localizedCaseInsensitiveCompare:)];
-    NSArray *descriptors = [NSArray arrayWithObjects:nameDescriptor, packageDescriptor, nil];
+    NSArray *descriptors = [NSArray arrayWithObjects:packageDescriptor, nameDescriptor, nil];
     NSArray *sortedArray = [mutableList sortedArrayUsingDescriptors:descriptors];
     mutableList = nil;
     return sortedArray;
 }
 
++ (NSString *)getNumberWithLimit:(NSInteger)limit {
+    NSString *errorString = @"\nChoose a number, 0 to exit. [1]: ";
+    char c;
+    printf("%s", [errorString UTF8String] );
+    c=getchar();
+    while (c > limit) {
+        printf("c = %c", c);
+        c=getchar();
+    }
+    printf("c = %c", c);
+    return @"";
+}
+
 + (BOOL)shouldContinueWithError:(NSString *)errorMessage {
     NSString *errorString = [NSString stringWithFormat:@"\n%@ Are you sure you want to continue? [y/n]?", errorMessage];
     char c;
@@ -240,7 +263,6 @@
     NSTask *task = [[NSTask alloc] init];
     NSPipe *pipe = [[NSPipe alloc] init];
     NSFileHandle *handle = [pipe fileHandleForReading];
-    
     [task setLaunchPath:taskBinary];
     [task setArguments:taskArguments];
     [task setStandardOutput:pipe];
@@ -485,9 +507,10 @@
 
 + (InputPackage *)packageForDeb:(NSString *)debFile {
     
-    NSString *packageName = [self singleLineReturnForProcess:@"/usr/local/bin/dpkg -f %@ Package", debFile];
-    NSString *packageVersion = [self singleLineReturnForProcess:@"/usr/local/bin/dpkg -f %@ Version", debFile];
-    NSArray <InputPackageFile *> *fileList = [self returnForProcess:[NSString stringWithFormat:@"/usr/local/bin/dpkg -c %@", debFile]];
+    NSString *dpkg = [@"dpkg" runPathForSearchPath];
+    NSString *packageName = [self singleLineReturnForProcess:@"%@ -f %@ Package",dpkg, debFile];
+    NSString *packageVersion = [self singleLineReturnForProcess:@"%@ -f %@ Version", dpkg, debFile];
+    NSArray <InputPackageFile *> *fileList = [self returnForProcess:[NSString stringWithFormat:@"%@ -c %@", dpkg, debFile]];
     
     __block NSMutableArray *finalArray = [NSMutableArray new];
     

+ 40 - 17
bootstrapTool/Classes/InputPackage.m

@@ -52,12 +52,17 @@
 
 - (BOOL)validateFileList:(NSArray *)fileList {
     __block BOOL retv = true;
+    __block int invalidCount = 0; //make sure its greater than 1 before we bail...
     [fileList enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
         if ([obj length] > 1) {
             NSString *sub = [obj substringToIndex:2];
+            DLog(@"sub: %@", sub);
             if ([sub isEqualToString:@"./"]) {
-                *stop = true;
-                retv = false;
+                invalidCount++;
+                if (invalidCount > 1){
+                    *stop = true;
+                    retv = false;
+                }
             }
         }
     }];
@@ -65,11 +70,12 @@
 }
 
 - (NSArray *)getFileListing {
-    NSString *listProcess = [NSString stringWithFormat:@"/usr/local/bin/dpkg-deb -c %@ | awk '{ print $6 }'" , self.path];
+    NSString *dpkgDeb = [@"dpkg-deb" runPathForSearchPath];
+    NSString *listProcess = [NSString stringWithFormat:@"%@ -c %@ | awk '{ print $6 }'" ,dpkgDeb, self.path];
     NSArray *fileList = [HelperClass returnForProcess:listProcess];
     BOOL validated = [self validateFileList:fileList];
     if (!validated) {
-        fileList = [HelperClass returnForProcess:[NSString stringWithFormat:@"/usr/local/bin/dpkg-deb -c %@ | awk '{ print $6 }' | cut -c 2-" , self.path]];
+        fileList = [HelperClass returnForProcess:[NSString stringWithFormat:@"%@ -c %@ | awk '{ print $6 }' | cut -c 2-" ,dpkgDeb, self.path]];
     }
     if ([fileList containsObject:@"/"]) {
         NSMutableArray *_mutableList = [fileList mutableCopy];
@@ -86,7 +92,7 @@
     
     NSString *statusFile = [bootstrapPath relativeStatusFilePath];
     NSArray *installedPackages = [HelperClass statusInstalledPackagesFromFile:statusFile];
-    
+    NSString *dpkg = [@"dpkg" runPathForSearchPath];
     //DLog(@"installedPackages: %@", installedPackages);
     
     
@@ -144,7 +150,7 @@
     NSString *debian = [tmpPath stringByAppendingPathComponent:@"DEBIAN"];
     [FM createDirectoryAtPath:tmpPath withIntermediateDirectories:TRUE attributes:nil error:nil];
     DLog(@"\nExtracting deb for processing...\n");
-    [HelperClass returnForProcess:[NSString stringWithFormat:@"/usr/local/bin/dpkg -x %@ %@", self.path, tmpPath]];
+    [HelperClass returnForProcess:[NSString stringWithFormat:@"%@ -x %@ %@", dpkg, self.path, tmpPath]];
     
     NSArray *fileList = [self getFileListing];
     
@@ -195,7 +201,7 @@
     
     DLog(@"\nExtracting DEBIAN files for processing...\n");
     
-    [HelperClass returnForProcess:[NSString stringWithFormat:@"/usr/local/bin/dpkg -e %@ %@", self.path, debian]];
+    [HelperClass returnForProcess:[NSString stringWithFormat:@"%@ -e %@ %@", dpkg, self.path, debian]];
     //NSString *nextPath = [tmpPath stringByAppendingPathComponent:@"DEBIAN"];
     
     DLog(@"\nCopying any necessary DEBIAN files to new locations...\n\n");
@@ -254,7 +260,7 @@
     //finally actually install the package onto the bootstrap
     
     DLog(@"Extracting package onto bootstrap folder...\n");
-    [HelperClass returnForProcess:[NSString stringWithFormat:@"/usr/local/bin/dpkg -x %@ %@", self.path, bootstrapPath]];
+    [HelperClass returnForProcess:[NSString stringWithFormat:@"%@ -x %@ %@",dpkg, self.path, bootstrapPath]];
     if (postInstFile) {
         DLog(@"\n [WARNING] We found a postinst file, will not run this due to potential unexpected consequences in your run environment! Displaying contents so you can determine if any additional steps are necessary. Contents will be delimited by a line -----\n");
         DLog(@"\n%@ Contents:\n\n---------------------\n\n%@\n\n---------------------\n\n", postInstFile, [NSString stringWithContentsOfFile:postInstFile encoding:NSASCIIStringEncoding error:nil]);
@@ -371,6 +377,7 @@
     
     NSString *fakeRoot = [HelperClass singleLineReturnForProcess:@"/usr/bin/which fakeroot"];
     NSString *pwd = [HelperClass singleLineReturnForProcess:@"/bin/pwd"];
+    NSString *dpkg = [@"dpkg" runPathForSearchPath];
     DLog(@"\nProcessing file: %@\n", self.path);
     InputPackage *output = self;
     NSMutableArray *moveRoot = [NSMutableArray new];
@@ -385,10 +392,10 @@
     }
     [FM createDirectoryAtPath:tmpPath withIntermediateDirectories:TRUE attributes:nil error:nil];
     DLog(@"\nExtracting package contents for processing...\n");
-    [HelperClass returnForProcess:[NSString stringWithFormat:@"/usr/local/bin/dpkg -x %@ %@", self.path, tmpPath]];
+    [HelperClass returnForProcess:[NSString stringWithFormat:@"%@ -x %@ %@",dpkg, self.path, tmpPath]];
     [FM createDirectoryAtPath:debian withIntermediateDirectories:TRUE attributes:nil error:nil];
     DLog(@"\nExtracting DEBIAN files for processing...\n");
-    [HelperClass returnForProcess:[NSString stringWithFormat:@"/usr/local/bin/dpkg -e %@ %@", self.path, debian]];
+    [HelperClass returnForProcess:[NSString stringWithFormat:@"%@ -e %@ %@",dpkg, self.path, debian]];
     NSString *controlPath = [debian stringByAppendingPathComponent:@"control"];
     NSMutableString *controlFile = [[NSMutableString alloc] initWithContentsOfFile:controlPath encoding:NSASCIIStringEncoding error:nil];
     //@"appletvos-arm64"
@@ -418,7 +425,22 @@
                 [FM removeItemAtPath:[tmpPath stringByAppendingPathComponent:@"private"] error:nil];
                 *stop = TRUE;
             }
-            
+            if ([[obj.path lastPathComponent] isEqualToString:@"Info.plist"]){
+                if ([[[[obj.path stringByDeletingLastPathComponent] lastPathComponent] pathExtension] isEqualToString:@"app"]){
+                    DLog(@"Got an Info.plist inside an app: %@", obj.path);
+                    NSString *plistFullPath = [tmpPath stringByAppendingPathComponent:obj.path];
+                    NSMutableDictionary *infoPlist = [NSMutableDictionary dictionaryWithContentsOfFile:plistFullPath];
+                    //DLog(@"infoPlist: %@", infoPlist);
+                    infoPlist[@"itemId"] = @(995367539);
+                    //DLog(@"infoPlist updated: %@", infoPlist);
+                    NSError *error = nil;
+                    [FM removeItemAtPath:plistFullPath error:&error];
+                    if (error){
+                        DLog(@"error removing file: %@", plistFullPath);
+                    }
+                    [infoPlist writeToFile:plistFullPath atomically:true];
+                }
+            }
             NSString *fullPath = [tmpPath stringByAppendingPathComponent:obj.path];
             if ([ignoreFiles containsObject:obj.path.lastPathComponent]){
                 DLog(@"in ignore file list, purge");
@@ -456,9 +478,9 @@
         DLog(@"[INFO] moving %@ to %@", oldPath, newPath);
         [FM moveItemAtPath:oldPath toPath:newPath error:nil];
     }];
-    NSString *depArchiveInfo = [NSString stringWithFormat:@"/usr/local/bin/dpkg -b %@", self.packageName];
+    NSString *depArchiveInfo = [NSString stringWithFormat:@"%@ -b %@", dpkg, self.packageName];
     if (fakeRoot) {
-        depArchiveInfo = [NSString stringWithFormat:@"%@ /usr/local/bin/dpkg -b %@", fakeRoot, self.packageName];
+        depArchiveInfo = [NSString stringWithFormat:@"%@ %@ -b %@", fakeRoot, dpkg, self.packageName];
     }
     [[HelperClass returnForProcess:depArchiveInfo] componentsJoinedByString:@"\n"];
     DLog(@"\nDone!\n\n");
@@ -468,6 +490,7 @@
     
     NSString *fakeRoot = [HelperClass singleLineReturnForProcess:@"/usr/bin/which fakeroot"];
     NSString *pwd = [HelperClass singleLineReturnForProcess:@"/bin/pwd"];
+    NSString *dpkg = [@"dpkg" runPathForSearchPath];
     DLog(@"\nProcessing file: %@\n", self.path);
     InputPackage *output = self;
     DLog(@"\nFound package: '%@' at version: '%@'...\n", output.packageName, output.version );
@@ -481,10 +504,10 @@
     }
     [FM createDirectoryAtPath:tmpPath withIntermediateDirectories:TRUE attributes:nil error:nil];
     DLog(@"\nExtracting package contents for processing...\n");
-    [HelperClass returnForProcess:[NSString stringWithFormat:@"/usr/local/bin/dpkg -x %@ %@", self.path, tmpPath]];
+    [HelperClass returnForProcess:[NSString stringWithFormat:@"%@ -x %@ %@", dpkg, self.path, tmpPath]];
     [FM createDirectoryAtPath:debian withIntermediateDirectories:TRUE attributes:nil error:nil];
     DLog(@"\nExtracting DEBIAN files for processing...\n");
-    [HelperClass returnForProcess:[NSString stringWithFormat:@"/usr/local/bin/dpkg -e %@ %@", self.path, debian]];
+    [HelperClass returnForProcess:[NSString stringWithFormat:@"%@ -e %@ %@", dpkg, self.path, debian]];
     
     //clean up any calls to uicache
     NSString *postinst = [debian stringByAppendingPathComponent:@"postinst"];
@@ -590,9 +613,9 @@
         DLog(@"[INFO] moving %@ to %@", oldPath, newPath);
         [FM moveItemAtPath:oldPath toPath:newPath error:nil];
     }];
-    NSString *depArchiveInfo = [NSString stringWithFormat:@"/usr/local/bin/dpkg -b %@", self.packageName];
+    NSString *depArchiveInfo = [NSString stringWithFormat:@"%@ -b %@", dpkg, self.packageName];
     if (fakeRoot) {
-        depArchiveInfo = [NSString stringWithFormat:@"%@ /usr/local/bin/dpkg -b %@", fakeRoot, self.packageName];
+        depArchiveInfo = [NSString stringWithFormat:@"%@ %@ -b %@", fakeRoot, dpkg, self.packageName];
     }
     [[HelperClass returnForProcess:depArchiveInfo] componentsJoinedByString:@"\n"];
     DLog(@"\nDone!\n\n");

+ 1 - 0
bootstrapTool/Classes/InputPackageFile.h

@@ -14,5 +14,6 @@
 @property (readwrite, assign) NSInteger type;
 
 - (void)_setFileTypeFromRaw:(NSString *)rawType;
+- (InputPackageFile *)initWithFile:(NSString *)file inBootstrap:(NSString *)bootstrap;
 
 @end

+ 59 - 2
bootstrapTool/Classes/InputPackageFile.m

@@ -7,8 +7,6 @@
     
     _fileType = [InputPackageFile readableFileTypeForRawMode:rawType];
     _type = [InputPackageFile fileTypeForRawMode:rawType];
-    
-    
 }
 
 /*
@@ -40,6 +38,28 @@
     return fileType;
 }
 
+/*
+ FOUNDATION_EXPORT NSFileAttributeType const NSFileTypeSocket;
+ FOUNDATION_EXPORT NSFileAttributeType const NSFileTypeUnknown;
+ */
+
++ (BSPackageFileType)fileTypeFromAttributedFileType:(NSString *)fileType {
+    BSPackageFileType type = BSPackageFileTypeUnknown;
+    if ([fileType isEqualToString:NSFileTypeRegular])
+    { type = BSPackageFileTypeFile; }
+    else if ([fileType isEqualToString:NSFileTypeDirectory])
+    { type = BSPackageFileTypeDirectory; }
+    else if ([fileType isEqualToString:NSFileTypeBlockSpecial])
+    { type = BSPackageFileTypeBlock; }
+    else if ([fileType isEqualToString:NSFileTypeCharacterSpecial])
+    { type = BSPackageFileTypeCharacter; }
+    else if ([fileType isEqualToString:NSFileTypeSymbolicLink])
+    { type = BSPackageFileTypeLink; }
+    else if ([fileType isEqualToString:NSFileTypeSocket])
+    { type = BSPackageFileTypeSocket; }
+    return type;
+}
+
 + (BSPackageFileType)fileTypeForRawMode:(NSString *)fileTypeChar {
     BSPackageFileType type = BSPackageFileTypeUnknown;
     if ([fileTypeChar isEqualToString:@"-"])
@@ -61,6 +81,43 @@
     
 }
 
+/*
+ NSFileCreationDate = "2023-09-20 07:09:17 +0000";
+  NSFileExtensionHidden = 0;
+  NSFileGroupOwnerAccountID = 0;
+  NSFileGroupOwnerAccountName = wheel;
+  NSFileHFSCreatorCode = 0;
+  NSFileHFSTypeCode = 0;
+  NSFileModificationDate = "2023-09-20 07:09:17 +0000";
+  NSFileOwnerAccountID = 0;
+  NSFileOwnerAccountName = root;
+  NSFilePosixPermissions = 493;
+  NSFileProtectionKey = NSFileProtectionCompleteUntilFirstUserAuthentication;
+  NSFileReferenceCount = 1;
+  NSFileSize = 68256;
+  NSFileSystemFileNumber = 116616970;
+  NSFileSystemNumber = 16777223;
+  NSFileType = NSFileTypeRegular;
+ */
+
+- (InputPackageFile *)initWithFile:(NSString *)file inBootstrap:(NSString *)bootstrap {
+    if ([file isEqualToString:@"."] || [file isEqualToString:@"./"] || file.length == 0){
+        return nil;
+    }
+    InputPackageFile *ip = [InputPackageFile new];
+    NSString *fullPath = [bootstrap stringByAppendingPathComponent:file];
+    NSDictionary *attrs = [FM attributesOfItemAtPath:fullPath error:nil];
+    ip.path = file;
+    ip.basename = [file lastPathComponent];
+    ip.owner = [NSString stringWithFormat:@"%@:%@",attrs[NSFileOwnerAccountID], attrs[NSFileGroupOwnerAccountID]];
+    ip.permissions = attrs[NSFilePosixPermissions];
+    ip.type = [InputPackageFile fileTypeFromAttributedFileType:attrs[NSFileType]];
+    if (ip.type == BSPackageFileTypeLink) {
+        ip.linkDestination = [FM destinationOfSymbolicLinkAtPath:ip.path error:nil];
+    }
+    return ip;
+}
+
 - (NSString*) description {
     NSString *orig = [super description];
     NSMutableDictionary *details = [NSMutableDictionary new];

+ 1 - 0
bootstrapTool/Classes/NSString+Additions.h

@@ -15,4 +15,5 @@
 - (NSArray *)spaceDelimitedArray;
 - (NSString *)relativeStatusFilePath;
 - (NSString *)relativeInfoFolderPath;
+- (NSString *)runPathForSearchPath;
 @end

+ 15 - 0
bootstrapTool/Classes/NSString+Additions.m

@@ -19,6 +19,21 @@
 
 @implementation NSString (Additions)
 
+- (NSString *)runPathForSearchPath {
+    const char *p = getenv("PATH");
+    NSString *path = [NSString stringWithUTF8String:p];
+    NSArray *paths = [path componentsSeparatedByString:@":"];
+    __block NSString *_finalPath = nil;
+    [paths enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
+        NSString *pathTest = [obj stringByAppendingPathComponent:[self lastPathComponent]];
+        if ([FM fileExistsAtPath:pathTest]) {
+            _finalPath = pathTest;
+            *stop = true;
+        }
+    }];
+    return _finalPath;
+}
+
 - (NSString *)relativeInfoFolderPath {
     NSString *firstTry = [[self stringByExpandingTildeInPath] stringByAppendingPathComponent:@"Library/dpkg/info"];
     if (![FM fileExistsAtPath:firstTry]){

+ 74 - 0
bootstrapTool/Classes/NSTask.h

@@ -0,0 +1,74 @@
+/*	NSTask.h
+	Copyright (c) 1996-2007, Apple Inc. All rights reserved.
+*/
+
+#import <Foundation/NSObject.h>
+
+@class NSString, NSArray, NSDictionary;
+
+@interface NSTask : NSObject
+
+// Create an NSTask which can be run at a later time
+// An NSTask can only be run once. Subsequent attempts to
+// run an NSTask will raise.
+// Upon task death a notification will be sent
+//   { Name = NSTaskDidTerminateNotification; object = task; }
+//
+
+- (id)init;
+
+// set parameters
+// these methods can only be done before a launch
+- (void)setLaunchPath:(NSString *)path;
+- (void)setArguments:(NSArray *)arguments;
+- (void)setEnvironment:(NSDictionary *)dict;
+	// if not set, use current
+- (void)setCurrentDirectoryPath:(NSString *)path;
+	// if not set, use current
+
+// set standard I/O channels; may be either an NSFileHandle or an NSPipe
+- (void)setStandardInput:(id)input;
+- (void)setStandardOutput:(id)output;
+- (void)setStandardError:(id)error;
+
+// get parameters
+- (NSString *)launchPath;
+- (NSArray *)arguments;
+- (NSDictionary *)environment;
+- (NSString *)currentDirectoryPath;
+
+// get standard I/O channels; could be either an NSFileHandle or an NSPipe
+- (id)standardInput;
+- (id)standardOutput;
+- (id)standardError;
+
+// actions
+- (void)launch;
+
+- (void)interrupt; // Not always possible. Sends SIGINT.
+- (void)terminate; // Not always possible. Sends SIGTERM.
+
+- (BOOL)suspend;
+- (BOOL)resume;
+
+// status
+- (int)processIdentifier; 
+- (BOOL)isRunning;
+
+- (int)terminationStatus;
+@property(readonly) long long terminationReason;
+@end
+
+@interface NSTask (NSTaskConveniences)
+
++ (NSTask *)launchedTaskWithLaunchPath:(NSString *)path arguments:(NSArray *)arguments;
+	// convenience; create and launch
+
+- (void)waitUntilExit;
+	// poll the runLoop in defaultMode until task completes
+
+@end
+
+FOUNDATION_EXPORT NSString * const NSTaskDidTerminateNotification;
+
+

+ 4 - 2
bootstrapTool/Classes/StatusPackageModel.h

@@ -23,9 +23,11 @@
 @property (nonatomic, copy) NSString *architecture;
 @property (nonatomic, copy) NSString *section;
 @property (nonatomic, copy) NSString *rawString;
+@property (nonatomic, copy) NSString *bootstrapPath;
 
-
+- (NSArray *)infoFiles;
+- (NSArray <InputPackageFile *>*)files;
+- (void)createPackage;
 - (instancetype)initWithRawControlString:(NSString *)controlString;
 
-
 @end

+ 66 - 0
bootstrapTool/Classes/StatusPackageModel.m

@@ -1,6 +1,72 @@
 
 @implementation StatusPackageModel
 
+- (NSArray <InputPackageFile *>*)files {
+    NSString *baseFile = [NSString stringWithFormat:@"%@.list", self.package];
+    NSString *listFile = [self.bootstrapPath.relativeInfoFolderPath stringByAppendingPathComponent:baseFile];
+    NSArray *files = [[NSString stringWithContentsOfFile:listFile encoding:NSUTF8StringEncoding error:nil] componentsSeparatedByString:@"\n"];
+    __block NSMutableArray *newFiles = [NSMutableArray new];
+    [files enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
+        BOOL isDir = false;
+        NSString *fullPath = [self.bootstrapPath stringByAppendingPathComponent:obj];
+        if ([FM fileExistsAtPath:fullPath isDirectory:&isDir]){
+                InputPackageFile *file = [[InputPackageFile alloc] initWithFile:obj inBootstrap:self.bootstrapPath];
+                if (file) {
+                    [newFiles addObject:file];
+                }
+        } else { //if it doesn't exist its probably a symbolic link that doensn't point to any 'proper' location on our current device.
+            InputPackageFile *file = [[InputPackageFile alloc] initWithFile:obj inBootstrap:self.bootstrapPath];
+            if (file) {
+                [newFiles addObject:file];
+            }
+        }
+    }];
+    
+    return newFiles;
+}
+
+- (NSArray *)infoFiles {
+    NSArray *infos = [FM contentsOfDirectoryAtPath:self.bootstrapPath.relativeInfoFolderPath error:nil];
+    return [infos filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF BEGINSWITH %@", self.package]];
+}
+
+- (NSString *)writeableControl {
+    NSMutableArray *controlArray = [[self.rawString componentsSeparatedByString:@"\n"] mutableCopy];
+    [controlArray removeLastObject];
+    [controlArray addObject:@"\n"];
+    return [controlArray componentsJoinedByString:@"\n"];
+}
+
+- (void)createPackage {
+    NSArray *infoFiles = [self infoFiles];
+    NSArray <InputPackageFile *> *files = [self files];
+    NSString *tmpFolder = [NSString stringWithFormat:@"/tmp/%@", self.package];
+    if([FM createDirectoryAtPath:tmpFolder withIntermediateDirectories:true attributes:nil error:nil]) {
+        DLog(@"directory created successfully: %@", tmpFolder);
+        NSString *debFolderPath = [tmpFolder stringByAppendingPathComponent:@"DEBIAN"];
+        [FM createDirectoryAtPath:debFolderPath withIntermediateDirectories:true attributes:nil error:nil];
+        NSString *control = [debFolderPath stringByAppendingPathComponent:@"control"];
+        [self.writeableControl writeToFile:control atomically:true encoding:NSUTF8StringEncoding error:nil];
+        [infoFiles enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
+            if (![[obj pathExtension] isEqualToString:@"list"] && ![[obj pathExtension] isEqualToString:@"keep"]){
+                NSString *fullPath = [self.bootstrapPath.relativeInfoFolderPath stringByAppendingPathComponent:obj];
+                NSString *finalPath = [debFolderPath stringByAppendingPathComponent:[obj pathExtension]];
+                [FM copyItemAtPath:fullPath toPath:finalPath error:nil];
+            }
+        }];
+        [files enumerateObjectsUsingBlock:^(InputPackageFile * _Nonnull file, NSUInteger fileIdx, BOOL * _Nonnull stop) {
+            NSString *destinationPath = [tmpFolder stringByAppendingPathComponent:file.path];
+            if (file.type == BSPackageFileTypeDirectory) {
+                [FM createDirectoryAtPath:destinationPath withIntermediateDirectories:true attributes:nil error:nil];
+            } else {
+                NSString *fromFile = [self.bootstrapPath stringByAppendingPathComponent:file.path];
+                DLog(@"copying: %@ to %@", fromFile, destinationPath);
+                [FM copyItemAtPath:fromFile toPath:destinationPath error:nil];
+            }
+        }];
+    }
+}
+
 - (NSString *)description {
     NSString *orig = [super description];
     return [NSString stringWithFormat:@"%@ = %@ (%@)", orig, self.package, self.version];

+ 16 - 0
bootstrapTool/Makefile

@@ -0,0 +1,16 @@
+export GO_EASY_ON_ME=1
+DEBUG=0
+ARCHS = arm64
+TARGET := appletv
+
+include $(THEOS)/makefiles/common.mk
+
+TOOL_NAME = bootstrapTool
+
+bootstrapTool_FILES = $(wildcard *.m) $(wildcard Classes/*.m)
+bootstrapTool_CFLAGS = -fobjc-arc -IClasses -include Classes/Defines.h
+bootstrapTool_INSTALL_PATH = /fs/jb/usr/local/bin
+bootstrapTool_LDFLAGS = -framework Foundation
+bootstrapTool_CODESIGN_FLAGS=-Sent.xml
+
+include $(THEOS_MAKE_PATH)/tool.mk

+ 12 - 0
bootstrapTool/ent.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>platform-application</key>
+	<true/>
+	<key>com.apple.springboard.CFUserNotification</key>
+	<true/>
+	<key>com.apple.private.security.no-container</key>
+	<true/>
+</dict>
+</plist>

+ 18 - 3
bootstrapTool/main.m

@@ -81,6 +81,7 @@ int main(int argc, char **argv) {
         BOOL clean = FALSE;
         BOOL forceYes = FALSE;
         BOOL new = FALSE;
+        [HelperClass getNumberWithLimit:20];
         /*
         NSString *searchPathString = @"/Users/bradleyk/.rbenv/shims:/Users/bradleyk/Library/Python/3.9/bin:/Users/bradleyk/Library/Python/3.8/bin:/usr/local/lib/ruby/gems/3.0.0/bin:/usr/local/lib/ruby/gems/bin:/usr/local/opt/ruby/bin:/opt/local/bin:/opt/local/sbin:/Users/bradleyk/local/bin:/usr/local/mysql/bin:/opt/local/bin:/opt/local/sbin:/usr/bin:/Users/bradleyk/Projects/goprojects/bin:/Users/bradleyk/Projects/theos/bin:/usr/local/bin/:/usr/local/bin:/bin/:/sbin/:/usr/sbin/";
         NSString *target = @"dpkg-deb";
@@ -88,7 +89,16 @@ int main(int argc, char **argv) {
         DLog(@"gotIm: %@", gotIm);
         return 0;
          */
-       
+        NSInteger loadAttempt = 0;
+        BOOL isLoaded = false;
+        do {
+            isLoaded = true;//[[$LSApplicationWorkspace defaultWorkspace] registerApplicationDictionary:dict];
+            if (!isLoaded) {
+                loadAttempt++;
+                usleep(1.0*USEC_PER_SEC);
+                
+            }
+        } while (isLoaded == false);
         NSString *up = nil;
         while ((flag = getopt_long(argc, argv, OPTION_FLAGS, longopts, NULL)) != -1) {
             switch(flag) {
@@ -201,9 +211,14 @@ int main(int argc, char **argv) {
         }
         if (listPackage && bootstrapPath) {
             NSString *statusFile = [bootstrapPath relativeStatusFilePath];
-            
-            NSArray *installedPackages = [HelperClass statusInstalledPackagesFromFile:statusFile];
+            NSArray *installedPackages = [HelperClass statusInstalledPackagesFromFile:statusFile bootstrapPath:bootstrapPath];
             DLog(@"%@", installedPackages);
+            /*
+            StatusPackageModel *model = installedPackages[8];
+            DLog(@"info files: %@", model.infoFiles);
+            DLog(@"files: %@", model.files);
+            [model createPackage];
+            */
             return 0;
             
         } else if (listPackage) {