diff --git a/Quake3-iOS.xcodeproj/project.pbxproj b/Quake3-iOS.xcodeproj/project.pbxproj index ca9400af..4acba4fa 100644 --- a/Quake3-iOS.xcodeproj/project.pbxproj +++ b/Quake3-iOS.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -180,7 +180,6 @@ A1565FCE2109F30E00FA9BC9 /* jdhuff.c in Sources */ = {isa = PBXBuildFile; fileRef = A179C0612101470F00D3C611 /* jdhuff.c */; settings = {COMPILER_FLAGS = "-w"; }; }; A1565FD42109F30E00FA9BC9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A1902F68210939BC008823BB /* Assets.xcassets */; }; A1565FD52109F30E00FA9BC9 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A1902F65210939BA008823BB /* Main.storyboard */; }; - A1565FD62109F30E00FA9BC9 /* baseq3 in Resources */ = {isa = PBXBuildFile; fileRef = A1902E9A21075F1C008823BB /* baseq3 */; }; A1565FE2210A0D3E00FA9BC9 /* libcurl_tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A1565FE1210A0D3E00FA9BC9 /* libcurl_tvOS.a */; }; A1565FE4210A0DEF00FA9BC9 /* libcurl_iOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A1565FDC210A0CEA00FA9BC9 /* libcurl_iOS.a */; }; A1565FE8210A0E4A00FA9BC9 /* libssl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A1565FE6210A0E4A00FA9BC9 /* libssl.a */; }; @@ -384,7 +383,6 @@ A18F9699239B09D40053E3B1 /* UIImage-Targa.m in Sources */ = {isa = PBXBuildFile; fileRef = A18F9697239B09D40053E3B1 /* UIImage-Targa.m */; }; A18F969D23A04F070053E3B1 /* tga_reader.c in Sources */ = {isa = PBXBuildFile; fileRef = A18F969C23A04F070053E3B1 /* tga_reader.c */; }; A18F969E23A04F070053E3B1 /* tga_reader.c in Sources */ = {isa = PBXBuildFile; fileRef = A18F969C23A04F070053E3B1 /* tga_reader.c */; }; - A1902E9B21075F1C008823BB /* baseq3 in Resources */ = {isa = PBXBuildFile; fileRef = A1902E9A21075F1C008823BB /* baseq3 */; }; A196B6882115F5680031CEB8 /* ServerBrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A196B6872115F5680031CEB8 /* ServerBrowserViewController.swift */; }; A196B6A72115F7C50031CEB8 /* Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = A196B68B2115F7C50031CEB8 /* Server.swift */; }; A196B6AB2115F7C50031CEB8 /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = A196B68D2115F7C50031CEB8 /* Player.swift */; }; @@ -766,7 +764,6 @@ A18F969B23A04F070053E3B1 /* tga_reader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tga_reader.h; sourceTree = ""; }; A18F969C23A04F070053E3B1 /* tga_reader.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tga_reader.c; sourceTree = ""; }; A1902E982103EFC7008823BB /* bridging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bridging.h; sourceTree = ""; }; - A1902E9A21075F1C008823BB /* baseq3 */ = {isa = PBXFileReference; lastKnownFileType = folder; name = baseq3; path = "../../../Library/Application Support/quake3/baseq3"; sourceTree = ""; }; A1902F63210939BA008823BB /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; A1902F66210939BA008823BB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; A1902F68210939BC008823BB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -817,6 +814,10 @@ A1F4032225D22793009B1C92 /* qgles.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = qgles.h; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 6B3DA91C2DE5545500D653D7 /* baseq3 */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); name = baseq3; path = "/Users/dantecesa/Library/Application Support/Quake3/baseq3"; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ A1565FCF2109F30E00FA9BC9 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -861,12 +862,12 @@ A179BFDA210142F900D3C611 = { isa = PBXGroup; children = ( - A1902E9A21075F1C008823BB /* baseq3 */, A179BFFA210146BF00D3C611 /* Quake3 */, A179BFE5210142FA00D3C611 /* Quake3-iOS */, A1902F60210939BA008823BB /* Quake3-tvOS */, A179C4AD2101853600D3C611 /* Frameworks */, A179BFE4210142FA00D3C611 /* Products */, + 6B3DA91C2DE5545500D653D7 /* baseq3 */, ); sourceTree = ""; }; @@ -1422,6 +1423,9 @@ ); dependencies = ( ); + fileSystemSynchronizedGroups = ( + 6B3DA91C2DE5545500D653D7 /* baseq3 */, + ); name = "Quake3-tvOS"; packageProductDependencies = ( A1DBB61224A161600021BA6F /* CocoaAsyncSocket */, @@ -1445,6 +1449,9 @@ ); dependencies = ( ); + fileSystemSynchronizedGroups = ( + 6B3DA91C2DE5545500D653D7 /* baseq3 */, + ); name = "Quake3-iOS"; packageProductDependencies = ( A1DBB60A24A15FA90021BA6F /* CocoaAsyncSocket */, @@ -1509,7 +1516,6 @@ A16B765124ACC4C000D96F09 /* Font Awesome 5 Brands-Regular-400.otf in Resources */, A15AA922211A68E900DE867F /* ServerListViewCell.xib in Resources */, A15103A1210D50DC0087EA86 /* dpquake.ttf in Resources */, - A1565FD62109F30E00FA9BC9 /* baseq3 in Resources */, A16B764F24ACC4C000D96F09 /* Font Awesome 5 Free-Solid-900.otf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1522,7 +1528,6 @@ A179BFF1210142FB00D3C611 /* LaunchScreen.storyboard in Resources */, A16B764C24ACC4C000D96F09 /* Font Awesome 5 Free-Regular-400.otf in Resources */, A151039F210D50D50087EA86 /* dpquake.ttf in Resources */, - A1902E9B21075F1C008823BB /* baseq3 in Resources */, A179BFEE210142FB00D3C611 /* Assets.xcassets in Resources */, A179BFEC210142FA00D3C611 /* Main.storyboard in Resources */, A16B765024ACC4C000D96F09 /* Font Awesome 5 Brands-Regular-400.otf in Resources */, @@ -2192,8 +2197,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_WARN_UNGUARDED_AVAILABILITY = YES; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 9UY8SFDNQ8; - ENABLE_BITCODE = YES; + DEVELOPMENT_TEAM = TSKA2XAG4K; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", @@ -2218,7 +2222,7 @@ "$(inherited)", "$(PROJECT_DIR)/Quake3/libs/ios", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.tomkiddconsulting.Quake3-iOS"; + PRODUCT_BUNDLE_IDENTIFIER = "com.dantecesa.Quake3-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Quake3-iOS/bridging.h"; SWIFT_VERSION = 5.0; @@ -2233,8 +2237,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_WARN_UNGUARDED_AVAILABILITY = YES; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 9UY8SFDNQ8; - ENABLE_BITCODE = YES; + DEVELOPMENT_TEAM = TSKA2XAG4K; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", @@ -2260,7 +2263,7 @@ "$(inherited)", "$(PROJECT_DIR)/Quake3/libs/ios", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.tomkiddconsulting.Quake3-iOS"; + PRODUCT_BUNDLE_IDENTIFIER = "com.dantecesa.Quake3-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Quake3-iOS/bridging.h"; SWIFT_VERSION = 5.0; diff --git a/Quake3-iOS/BotMatchBotViewController.swift b/Quake3-iOS/BotMatchBotViewController.swift index 5d89bb9e..e6aef258 100644 --- a/Quake3-iOS/BotMatchBotViewController.swift +++ b/Quake3-iOS/BotMatchBotViewController.swift @@ -209,10 +209,16 @@ extension BotMatchBotViewController : UICollectionViewDelegate { let fileManager = FileManager() if fileManager.fileExists(atPath: destinationURL.path) { - let img: UIImage = UIImage.image(fromTGAFile: destinationURL.path) as! UIImage cell.botAvatar.contentMode = .scaleAspectFit cell.botAvatar.image = img + } else { + // Create a fallback image with bot name initials + let botName = bots[indexPath.row].name + let initials = String(botName.prefix(2)).uppercased() + let fallbackImage = createFallbackImage(with: initials) + cell.botAvatar.contentMode = .scaleAspectFit + cell.botAvatar.image = fallbackImage } cell.botName.text = bots[indexPath.row].name @@ -225,10 +231,40 @@ extension BotMatchBotViewController : UICollectionViewDelegate { cell.botAvatar.layer.borderWidth = 0 } - return cell } + private func createFallbackImage(with text: String) -> UIImage { + let size = CGSize(width: 64, height: 64) + UIGraphicsBeginImageContextWithOptions(size, false, 0) + + // Draw background + UIColor.darkGray.setFill() + UIRectFill(CGRect(origin: .zero, size: size)) + + // Draw text + let font = UIFont.boldSystemFont(ofSize: 20) + let attributes: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: UIColor.orange + ] + + let textSize = text.size(withAttributes: attributes) + let textRect = CGRect( + x: (size.width - textSize.width) / 2, + y: (size.height - textSize.height) / 2, + width: textSize.width, + height: textSize.height + ) + + text.draw(in: textRect, withAttributes: attributes) + + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return image ?? UIImage() + } + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { let cell = collectionView.cellForItem(at: indexPath) as! BotCollectionViewCell self.selectedBot = bots[indexPath.row].name diff --git a/Quake3-iOS/BotMatchMapViewController.swift b/Quake3-iOS/BotMatchMapViewController.swift index d331a77a..f5caff5a 100644 --- a/Quake3-iOS/BotMatchMapViewController.swift +++ b/Quake3-iOS/BotMatchMapViewController.swift @@ -55,9 +55,9 @@ class BotMatchMapViewController: UIViewController { mapList.register(UITableViewCell.self, forCellReuseIdentifier: "cell") #if os(tvOS) - let documentsDir = try! FileManager().url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true).path + currentWorkingPath = try! FileManager().url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true).path #else - let documentsDir = try! FileManager().url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).path + currentWorkingPath = try! FileManager().url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).path #endif } @@ -85,7 +85,52 @@ extension BotMatchMapViewController : UITableViewDelegate { self.delegate?.setMap(map: maps[indexPath.row].map, name: maps[indexPath.row].name) var destinationURL = URL(fileURLWithPath: currentWorkingPath) destinationURL.appendPathComponent("graphics/\(maps[indexPath.row].map).jpg") - mapShot.image = UIImage(contentsOfFile: destinationURL.path) + + if let image = UIImage(contentsOfFile: destinationURL.path) { + mapShot.image = image + } else { + // Create fallback image with map name + let fallbackImage = createFallbackMapImage(with: maps[indexPath.row].map) + mapShot.image = fallbackImage + } + } + + private func createFallbackMapImage(with mapName: String) -> UIImage { + let size = CGSize(width: 200, height: 150) + UIGraphicsBeginImageContextWithOptions(size, false, 0) + + // Draw background + UIColor.darkGray.setFill() + UIRectFill(CGRect(origin: .zero, size: size)) + + // Draw border + UIColor.orange.setStroke() + let borderRect = CGRect(origin: .zero, size: size).insetBy(dx: 2, dy: 2) + let borderPath = UIBezierPath(rect: borderRect) + borderPath.lineWidth = 2 + borderPath.stroke() + + // Draw text + let font = UIFont.boldSystemFont(ofSize: 16) + let attributes: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: UIColor.orange + ] + + let textSize = mapName.size(withAttributes: attributes) + let textRect = CGRect( + x: (size.width - textSize.width) / 2, + y: (size.height - textSize.height) / 2, + width: textSize.width, + height: textSize.height + ) + + mapName.draw(in: textRect, withAttributes: attributes) + + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return image ?? UIImage() } } @@ -103,7 +148,14 @@ extension BotMatchMapViewController : UITableViewDataSource { cell.setSelected(true, animated: false) var destinationURL = URL(fileURLWithPath: currentWorkingPath) destinationURL.appendPathComponent("graphics/\(maps[indexPath.row].map).jpg") - mapShot.image = UIImage(contentsOfFile: destinationURL.path) + + if let image = UIImage(contentsOfFile: destinationURL.path) { + mapShot.image = image + } else { + // Create fallback image with map name + let fallbackImage = createFallbackMapImage(with: maps[indexPath.row].map) + mapShot.image = fallbackImage + } } return cell diff --git a/Quake3-iOS/BotMatchViewController.swift b/Quake3-iOS/BotMatchViewController.swift index e4798c9f..1435832d 100644 --- a/Quake3-iOS/BotMatchViewController.swift +++ b/Quake3-iOS/BotMatchViewController.swift @@ -54,14 +54,12 @@ class BotMatchViewController: UIViewController { botList.register(UITableViewCell.self, forCellReuseIdentifier: "cell") #if os(tvOS) - let documentsDir = try! FileManager().url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true).path + documentsDir = try! FileManager().url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true).path #else - let documentsDir = try! FileManager().url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).path + documentsDir = try! FileManager().url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).path #endif - var destinationURL = URL(fileURLWithPath: documentsDir) - destinationURL.appendPathComponent("graphics/\(selectedMap).jpg") - mapShot.image = UIImage(contentsOfFile: destinationURL.path) + updateMapPreview() var skill1URL = URL(fileURLWithPath: documentsDir) skill1URL.appendPathComponent("graphics/menu/art/skill1.tga") @@ -89,6 +87,57 @@ class BotMatchViewController: UIViewController { skill5Button.setImage(UIImage.image(fromTGAFile: skill5URL.path) as? UIImage, for: .normal) skill5Button.layer.borderColor = UIColor.red.cgColor } + + private func updateMapPreview() { + var destinationURL = URL(fileURLWithPath: documentsDir) + destinationURL.appendPathComponent("graphics/\(selectedMap).jpg") + + if let image = UIImage(contentsOfFile: destinationURL.path) { + mapShot.image = image + } else { + // Create fallback image with map name + let fallbackImage = createFallbackMapImage(with: selectedMap) + mapShot.image = fallbackImage + } + } + + private func createFallbackMapImage(with mapName: String) -> UIImage { + let size = CGSize(width: 200, height: 150) + UIGraphicsBeginImageContextWithOptions(size, false, 0) + + // Draw background + UIColor.darkGray.setFill() + UIRectFill(CGRect(origin: .zero, size: size)) + + // Draw border + UIColor.orange.setStroke() + let borderRect = CGRect(origin: .zero, size: size).insetBy(dx: 2, dy: 2) + let borderPath = UIBezierPath(rect: borderRect) + borderPath.lineWidth = 2 + borderPath.stroke() + + // Draw text + let font = UIFont.boldSystemFont(ofSize: 16) + let attributes: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: UIColor.orange + ] + + let textSize = mapName.size(withAttributes: attributes) + let textRect = CGRect( + x: (size.width - textSize.width) / 2, + y: (size.height - textSize.height) / 2, + width: textSize.width, + height: textSize.height + ) + + mapName.draw(in: textRect, withAttributes: attributes) + + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return image ?? UIImage() + } // MARK: - Navigation @@ -171,9 +220,7 @@ extension BotMatchViewController: BotMatchProtocol { func setMap(map: String, name: String) { mapButton.setTitle(map, for: .normal) selectedMap = map - var destinationURL = URL(fileURLWithPath: documentsDir) - destinationURL.appendPathComponent("graphics/\(selectedMap).jpg") - mapShot.image = UIImage(contentsOfFile: destinationURL.path) + updateMapPreview() } func addBot(bot: String, difficulty: Float, icon:String) { diff --git a/Quake3-iOS/GameViewController.swift b/Quake3-iOS/GameViewController.swift index 9a2bce94..0dada7e1 100644 --- a/Quake3-iOS/GameViewController.swift +++ b/Quake3-iOS/GameViewController.swift @@ -39,11 +39,14 @@ class GameViewController: UIViewController { let documentsDir = try! FileManager().url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).path #endif + // Set up baseq3 directory structure in Documents and copies pk3 files from bundle + setupBaseq3Directory(documentsDir: documentsDir) + Sys_SetHomeDir(documentsDir) DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { - var argv: [String?] = [ Bundle.main.resourcePath! + "/quake3", "+set", "com_basegame", "baseq3", "+name", self.defaults.string(forKey: "playerName")] + var argv: [String?] = [ Bundle.main.resourcePath! + "/quake3", "+set", "fs_basepath", documentsDir, "+name", self.defaults.string(forKey: "playerName")] if !self.selectedMap.isEmpty { if self.botMatch { @@ -193,6 +196,31 @@ class GameViewController: UIViewController { } } + private func setupBaseq3Directory(documentsDir: String) { + let fileManager = FileManager.default + let bundlePath = Bundle.main.resourcePath! + let documentsBaseq3Path = documentsDir + "/baseq3" + + do { + // Create baseq3 directory in Documents + try fileManager.createDirectory(atPath: documentsBaseq3Path, withIntermediateDirectories: true, attributes: nil) + + // Copy pk3 files from bundle to Documents/baseq3 + let bundleContents = try fileManager.contentsOfDirectory(atPath: bundlePath) + for file in bundleContents where file.hasSuffix(".pk3") { + let sourcePath = bundlePath + "/" + file + let destPath = documentsBaseq3Path + "/" + file + + if !fileManager.fileExists(atPath: destPath) { + try fileManager.copyItem(atPath: sourcePath, toPath: destPath) + print("Copied \(file) to baseq3 directory") + } + } + } catch { + print("Error setting up baseq3 directory: \(error)") + } + } + override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } diff --git a/Quake3-iOS/Info.plist b/Quake3-iOS/Info.plist index c493afbb..588c616b 100644 --- a/Quake3-iOS/Info.plist +++ b/Quake3-iOS/Info.plist @@ -2,10 +2,6 @@ - UIUserInterfaceStyle - Light - UIViewControllerBasedStatusBarAppearance - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -26,6 +22,8 @@ 1 LSRequiresIPhoneOS + NSBluetoothAlwaysUsageDescription + Quake III: Arena would like to remain connected to nearby bluetooth Game Controllers and Game Pads even when you’re not using the app. UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -48,7 +46,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - NSBluetoothAlwaysUsageDescription - Quake III: Arena would like to remain connected to nearby bluetooth Game Controllers and Game Pads even when you’re not using the app. + UIUserInterfaceStyle + Light + UIViewControllerBasedStatusBarAppearance + diff --git a/Quake3-iOS/MainMenuViewController.swift b/Quake3-iOS/MainMenuViewController.swift index 9a250611..51d8c92d 100644 --- a/Quake3-iOS/MainMenuViewController.swift +++ b/Quake3-iOS/MainMenuViewController.swift @@ -161,7 +161,6 @@ class MainMenuViewController: UIViewController { func extractFile(pk3: String, source: String, destination: String) { let fileManager = FileManager() - let currentWorkingPath = fileManager.currentDirectoryPath #if os(tvOS) let documentsDir = try! FileManager().url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true).path #else @@ -172,19 +171,44 @@ class MainMenuViewController: UIViewController { destinationURL.appendPathComponent(destination) if fileManager.fileExists(atPath: destinationURL.path) { + print("File already exists at \(destinationURL.path)") return } - var archiveURL = URL(fileURLWithPath: currentWorkingPath) + // Look for PK3 files in Documents/baseq3 directory + var archiveURL = URL(fileURLWithPath: documentsDir) archiveURL.appendPathComponent(pk3) + + // If PK3 doesn't exist in Documents, try app bundle as fallback + if !fileManager.fileExists(atPath: archiveURL.path) { + print("PK3 not found at \(archiveURL.path), trying app bundle...") + if let bundlePath = Bundle.main.resourcePath { + archiveURL = URL(fileURLWithPath: bundlePath) + archiveURL.appendPathComponent(pk3) + } + } + + print("Attempting to open archive at: \(archiveURL.path)") guard let archive = Archive(url: archiveURL, accessMode: .read) else { + print("Could not open archive at path: \(archiveURL.path)") return } guard let entry = archive[source] else { + print("Could not find entry '\(source)' in archive \(archiveURL.path)") return } + + // Create directory structure if needed + let destinationDir = destinationURL.deletingLastPathComponent() + do { + try fileManager.createDirectory(at: destinationDir, withIntermediateDirectories: true, attributes: nil) + } catch { + print("Failed to create directory \(destinationDir.path): \(error)") + } + do { let _ = try archive.extract(entry, to: destinationURL) + print("Successfully extracted \(source) to \(destinationURL.path)") } catch { print("Extracting entry from archive failed with error:\(error)") }