diff --git a/About This Hack.xcodeproj/project.pbxproj b/About This Hack.xcodeproj/project.pbxproj index edcbb72..61d3f61 100755 --- a/About This Hack.xcodeproj/project.pbxproj +++ b/About This Hack.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 58B988D926D7ACD80092EC5B /* ViewControllerStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B988D626D7ACD70092EC5B /* ViewControllerStorage.swift */; }; 58B988DA26D7ACD80092EC5B /* ViewControllerSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B988D726D7ACD80092EC5B /* ViewControllerSupport.swift */; }; 72E686C32862453700B8316D /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72E686C22862453700B8316D /* Reachability.swift */; }; + 9664A9132BECC81D00B5E306 /* SystemFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9664A9122BECC81D00B5E306 /* SystemFunctions.swift */; }; C1BAD6FF26D70091008460FB /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BAD6FE26D70091008460FB /* WindowController.swift */; }; C1D8307D2AAE57E500ABD4A4 /* Tooltips.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D8307C2AAE57E500ABD4A4 /* Tooltips.swift */; }; C1EDE5612AC9E70C00878889 /* InitGlobalVariables.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EDE5602AC9E70C00878889 /* InitGlobalVariables.swift */; }; @@ -52,6 +53,7 @@ 58B988D726D7ACD80092EC5B /* ViewControllerSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewControllerSupport.swift; sourceTree = ""; }; 72E686C22862453700B8316D /* Reachability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reachability.swift; sourceTree = ""; }; 965FD7C32AF3947B007E88BC /* fr */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = fr; path = fr.lproj/Main.storyboard; sourceTree = ""; }; + 9664A9122BECC81D00B5E306 /* SystemFunctions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemFunctions.swift; sourceTree = ""; }; C1BAD6FE26D70091008460FB /* WindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = ""; }; C1D8307C2AAE57E500ABD4A4 /* Tooltips.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tooltips.swift; sourceTree = ""; }; C1EDE5602AC9E70C00878889 /* InitGlobalVariables.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitGlobalVariables.swift; sourceTree = ""; }; @@ -122,6 +124,7 @@ 58134C0128668FF70008B00D /* Keycodes.swift */, C1F22EAE2AA4EDE000236BB6 /* UpdateController.swift */, C1D8307C2AAE57E500ABD4A4 /* Tooltips.swift */, + 9664A9122BECC81D00B5E306 /* SystemFunctions.swift */, ); path = "About This Hack"; sourceTree = ""; @@ -249,6 +252,7 @@ 115D922426D6F6AF00F5DF35 /* InsertExtension.swift in Sources */, DC68704A2A633DDD00CAD908 /* HCMacModel.swift in Sources */, 115D921226D6F65500F5DF35 /* AppDelegate.swift in Sources */, + 9664A9132BECC81D00B5E306 /* SystemFunctions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -415,7 +419,7 @@ "$(SDKROOT)/usr/lib/swift", ); MACOSX_DEPLOYMENT_TARGET = 10.12; - MARKETING_VERSION = 1.2.0; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = "app.netlify.0xCUBE.About-This-Hack"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -448,7 +452,7 @@ "$(SDKROOT)/usr/lib/swift", ); MACOSX_DEPLOYMENT_TARGET = 10.12; - MARKETING_VERSION = 1.2.0; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = "app.netlify.0xCUBE.About-This-Hack"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/About This Hack/HardwareCollector.swift b/About This Hack/HardwareCollector.swift index 44f4e10..7f3a098 100644 --- a/About This Hack/HardwareCollector.swift +++ b/About This Hack/HardwareCollector.swift @@ -156,31 +156,3 @@ class HardwareCollector { """, String(1 - percent)] } } - - -extension String { - - var length: Int { - return count - } - - subscript (i: Int) -> String { - return self[i ..< i + 1] - } - - func substring(fromIndex: Int) -> String { - return self[min(fromIndex, length) ..< length] - } - - func substring(toIndex: Int) -> String { - return self[0 ..< max(0, toIndex)] - } - - subscript (r: Range) -> String { - let range = Range(uncheckedBounds: (lower: max(0, min(length, r.lowerBound)), - upper: min(length, max(0, r.upperBound)))) - let start = index(startIndex, offsetBy: range.lowerBound) - let end = index(start, offsetBy: range.upperBound - range.lowerBound) - return String(self[start ..< end]) - } -} diff --git a/About This Hack/InitGlobalVariables.swift b/About This Hack/InitGlobalVariables.swift index 8a7afd2..b5360a8 100644 --- a/About This Hack/InitGlobalVariables.swift +++ b/About This Hack/InitGlobalVariables.swift @@ -1,28 +1,46 @@ +// +// InitGlobalVariables.swift +// + import Foundation +import AppKit class initGlobVar { - + + static var thisComponent: String { + return String(describing: self) + } + static var athfilesDirectory = "/.ath" static var tempDirectory = "/private/tmp" // "/tmp" is equiv. static var athDirectory = tempDirectory + athfilesDirectory -// static var athDirectory = NSHomeDirectory() + athfilesDirectory static var defaultfileManager = FileManager.default // Used by UpdateController - static var lastAthreleaseURL = "https://github.com/0xCUB3/About-This-Hack/releases/download/" + static var athrepositoryURL = "https://github.com/0xCUB3/About-This-Hack" + static var lastAthreleaseURL = athrepositoryURL + "/releases/download/" static var allAppliLocation = "/Applications" - static var thisAppliname = "/About This Hack.app" - static var thisAppliLocation = allAppliLocation + thisAppliname - static var newAthziprelease = "/About.This.Hack.zip" + static var thisAppliLocation = "\(allAppliLocation)/\(thisApplicationName).app" + static var newAthziprelease = "\(thisApplicationName.replacingOccurrences(of: " ", with: ".")).zip" static var newAthreleasezip = athDirectory + "/new_ath.zip" - static var athtargetversionfile = athDirectory + "/version.txt" - static var athsourceversionfile = "https://raw.githubusercontent.com/0xCUB3/Website/main/content/ath.txt" - + static var athlasttagpbxproj = "/blob/[LASTTAG]/About%20This%20Hack.xcodeproj/project.pbxproj" + // OCLP Dict File (if exists) where Patch Version Commit and DateTime will be extracted static var oclpXmlFilePath = "/System/Library/CoreServices/OpenCore-Legacy-Patcher.plist" static var bdmesgExecID = "/usr/local/bin/bdmesg" + // ioreg Dir perl script and pci ids and names files + static var whichLocation = "/usr/bin/which" + +// static var sysprofLocation = run(whichLocation + " system_profiler | /usr/bin/tr -d '\n'") +// static var diskutilLocation = run(whichLocation + " diskutil | /usr/bin/tr -d '\n'") +// static var sysctlLocation = run(whichLocation + " sysctl | /usr/bin/tr -d '\n'") +// static var catLocation = run(whichLocation + " cat | /usr/bin/tr -d '\n'") +// static var ioregLocation = run(whichLocation + " ioreg | /usr/bin/tr -d '\n'") + static var curlLocation = run(whichLocation + " curl | /usr/bin/tr -d '\n'") +// static var perlLocation = run(whichLocation + " perl | /usr/bin/tr -d '\n'") + // Files with Overview, Displays and Storage detailed Datas static var hwFilePath = athDirectory + "/hw.txt" static var scrFilePath = athDirectory + "/scr.txt" diff --git a/About This Hack/InsertExtension.swift b/About This Hack/InsertExtension.swift index 49dc207..922a5ee 100755 --- a/About This Hack/InsertExtension.swift +++ b/About This Hack/InsertExtension.swift @@ -1,12 +1,38 @@ // // InsertExtension.swift -// InsertExtension -// // +import Darwin import Foundation +var myself: String = "Extensions" + extension String { + + var length: Int { + return count + } + + subscript (i: Int) -> String { + return self[i ..< i + 1] + } + + func substring(fromIndex: Int) -> String { + return self[min(fromIndex, length) ..< length] + } + + func substring(toIndex: Int) -> String { + return self[0 ..< max(0, toIndex)] + } + + subscript (r: Range) -> String { + let range = Range(uncheckedBounds: (lower: max(0, min(length, r.lowerBound)), + upper: min(length, max(0, r.upperBound)))) + let start = index(startIndex, offsetBy: range.lowerBound) + let end = index(start, offsetBy: range.upperBound - range.lowerBound) + return String(self[start ..< end]) + } + func inserting(separator: String, every n: Int) -> String { var result: String = "" let characters = Array(self) @@ -18,4 +44,52 @@ extension String { } return result } + + func reverseString(separator: String, every n: Int, removeChars: String, hexencode: String) -> String { + + var outValue: String = "" + var workStr: String = self + let nbrSubStr = (workStr.count/n) + + if nbrSubStr > 1 { + for _ in 0.. String { + do { + let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive) + let range = NSRange(location: 0, length: self.count) + return regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith) + } catch { + return self + } + } + +} + +extension Data { + + func hexadecimalString() -> String { + self.reduce( "0x" ) { + $0.appending( String( format: "%02X", $1 ) ) + } + } } diff --git a/About This Hack/SystemFunctions.swift b/About This Hack/SystemFunctions.swift new file mode 100644 index 0000000..1a769ee --- /dev/null +++ b/About This Hack/SystemFunctions.swift @@ -0,0 +1,352 @@ +// +// SystemFunctions.swift +// + +import Cocoa +import AppKit + +var thisComponent = "SystemFunctions" + +var thisApplicationName = (Bundle.main.applicationName ?? "").replacingOccurrences(of: ".app", with: "") +var thisApplicationVersion = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String + +var thisAppNameContentsResources = "\(thisApplicationName)/Contents/Resources" + +var withDataFiles: Int = 0 // 0 = NO DataFiles (Data provided by system functions), 1 = Local DataFiles extraction, 2 = Linked DataFiles (Remote DataFiles sent) + +//var sp_SystemProfiler = "\(initGlobVar.sysprofLocation)" +var sp_DataMaskJson = "-json" +var sp_DataMaskXml = "-xml" +var sp_DataMaskEmpty = "" + +var sp_DisplaysDataType = "SPDisplaysDataType" +var sp_HardwareDataType = "SPHardwareDataType" +var sp_MemoryDataType = "SPMemoryDataType" +var sp_StorageDataType = "SPStorageDataType" +var sp_SPSoftwareDataType = "SPSoftwareDataType" + +var overViewCtrlInitStep: Int = 0 +var storViewCtrlInitStep: Int = 0 +var dispViewCtrlInitStep: Int = 0 +var prefViewCtrlInitStep: Int = 0 + +var viewctrl_Mac_Model: String = "Mac model unknown" +var macUCType: String = "Unknown" +var viewctrl_Mac_Name: String = "Mac (UNKNOWN)" +var viewctrl_OS_Name: String = "OS unknown" +var viewctrl_Board_Id: String = "Unknown" + +var viewctrl_SIP_Displayed: String = "SIP unknown" +var viewctrl_Cpu_Displayed: String = "CPU unknown" +var viewctrl_Ram_Displayed: String = "RAM unknown" +var viewctrl_Dsk_Displayed: String = "Disk unknown" +var viewctrl_Dsp_Displayed: String = "Display unknown" +var viewctrl_Gpu_Displayed: String = "GPU unknown" +var viewctrl_SrN_Displayed: String = "Serial Num. unknown" +var viewctrl_Bld_Displayed: String = "BootLoader unknown" +var viewctrl_Bag_Displayed: String = "BootArgs unknown" + +var spDisplay_gpu_List: [disp_light_s] = [] +var spDisplay_dsp_List: [String] = [] +var number_Of_Displays: Int = 0 +var qhasBuiltInDisplay: Bool = false + +var viewctrl_Dsk_Model: String = "Unknown" +var viewctrl_Dsk_Protocol: String = "Unknown" +var viewctrl_Dsk_Location: String = "Unknown" +var viewctrl_Dsk_VolPath: String = "Unknown" +var viewctrl_Dsk_BSDName: String = "Unknown" +var viewctrl_Dsk_MedType: String = "Unknown" +var viewctrl_Dsk_SizeInt: Int64 = 0 +var viewctrl_Dsk_FreeInt: Int64 = 0 +var viewctrl_Dsk_SizeStr: String = "Unknown" +var viewctrl_Dsk_FreeStr: String = "Unknown" +var viewctrl_Dsk_KindStr: String = "Unknown" + +var toolTips_OSNr_Info_Displayed: String = "Unknown" +var toolTips_Modl_Info_Displayed: String = "Unknown" +var toolTips_CPU_Info_Displayed: String = "Unknown" +var toolTips_Ram_Info_Displayed: String = "Unknown" +var toolTips_Disk_Info_Displayed: String = "Unknown" +var toolTips_Disk_List_Displayed: String = "Unknown" +var toolTips_Disp_Info_Displayed: String = "Unknown" +var toolTips_GPU_Info_Displayed: String = "Unknown" +var toolTips_SrN_Info_Displayed: String = "Unknown" + +//IOReg +var IOMainorMasterPortDefault:UInt32 = 0 +func initPortDefault() { + if #available(macOS 12.0, *) { + IOMainorMasterPortDefault = kIOMainPortDefault // New name as of macOS 12 + } else { + IOMainorMasterPortDefault = kIOMasterPortDefault // Old name up to macOS 11 + } +} + +struct disp_light_s { + var spdisp_model: String? = nil // sppci_model key + var spdisp_vendor: String? = nil // spdisplays_vendor key + var spdisp_vram: String? = nil // spdisplays_vram_shared or _spdisplays_vram key + var spdisp_metal: String? = nil // spdisplays_mtlgpufamilysupport key + var spdisp_cores: Int? = nil // sppci_cores key + var spdisp_ndrvs: [disp_light_det_s] = [] + var spdisp_metal_slot: String? = nil // _spdisplays_metal_slot key + var spdisp_regid: String? = nil // _spdisplays_regid key + var spdisp_device_id: String? = nil // spdisplays_device-id key + var spdisp_revision_id: String? = nil // spdisplays_revision-id key + var spdisp_bus: String? = nil // sppci_bus key + var spdisp_device_type: String? = nil // sppci_device_type key + var spdisp_slot_name: String? = nil // sppci_slot_name key +} +struct disp_light_det_s { + var spdisp_name: String? = nil // _name key + var spdisp_type: String? = nil // var spdisplays_display_type + var spdisp_resol: String? = nil // spdisplays_resolution key + var spdisp_pixelres: String? = nil // spdisplays_pixelresolution key + var spdisp_connect: String? = nil // spdisplays_connection_type key : "spdisplays_internal" + var spdisp_colordepth: String? = nil // spdisplays_depth key : "CGSThirtytwoBitColor" + var spdisp_main: String? = nil // spdisplays_main key : "spdisplays_yes" + var spdisp_mirror: String? = nil // spdisplays_mirror key : "spdisplays_off" + var spdisp_online: String? = nil // spdisplays_online key : "spdisplays_yes" +} + +func getNVRAM(variable nVRAMPath: String, variable nVRAMKey: String) -> String? { + var rootPath: io_registry_entry_t + var masterPort = IOMainorMasterPortDefault + + guard IOMasterPort(bootstrap_port, &masterPort) == KERN_SUCCESS else { + print("\(thisComponent) : NO successful Acces with mastreport \(masterPort)") + return nil + } + guard !(IORegistryEntryFromPath(masterPort, nVRAMPath) == 0) else { + print("\(thisComponent) : NO successful Access with mastreport \(masterPort) to path \(nVRAMPath)") + return nil + } + + rootPath = IORegistryEntryFromPath(masterPort, nVRAMPath) + + let vref = IORegistryEntryCreateCFProperty(rootPath, nVRAMKey as CFString, kCFAllocatorDefault, 0) + if (vref != nil) { + let data = vref?.takeRetainedValue() as! Data + var cleanedData = Data() + for i in 0.. String { + var rootPath: io_registry_entry_t + var masterPort = IOMainorMasterPortDefault + + guard IOMasterPort(bootstrap_port, &masterPort) == KERN_SUCCESS else { + print("\(thisComponent) : NO successful Acces with mastreport \(masterPort)") + return "" + } + guard !(IORegistryEntryFromPath(masterPort, ioRegPath) == 0) else { + print("\(thisComponent) : NO successful Access with mastreport \(masterPort) to path \(ioRegPath)") + return "" + } + + rootPath = IORegistryEntryFromPath(masterPort, ioRegPath) + + var ioRegValue : String = "" + let keyValueFound = IORegistryEntryCreateCFProperty(rootPath, ioRegKey as CFString, kCFAllocatorDefault, 0) + if (keyValueFound != nil) { + let notCleanedData = keyValueFound?.takeRetainedValue() as? Data + var cleanedData = Data() + for i in 0.. String { + var rootPath: io_registry_entry_t + var masterPort = IOMainorMasterPortDefault + + guard IOMasterPort(bootstrap_port, &masterPort) == KERN_SUCCESS else { + print("\(thisComponent) : NO successful Acces with mastreport \(masterPort)") + return "" + } + guard !(IORegistryEntryFromPath(masterPort, ioRegPath) == 0) else { + print("\(thisComponent) : NO successful Access with mastreport \(masterPort) to path \(ioRegPath)") + return "" + } + + rootPath = IORegistryEntryFromPath(masterPort, ioRegPath) + + var ioRegValue : String = "" + let keyValueFound = IORegistryEntryCreateCFProperty(rootPath, ioRegKey as CFString, kCFAllocatorDefault, 0) +// print("\(thisComponent) : keyValueFound : \(String(describing: keyValueFound))") + if (keyValueFound != nil) { + let notCleanedData = keyValueFound?.takeRetainedValue() as? Data + var cleanedData = Data() + for i in 0.. String { + print("\(thisComponent) : Parameters : \(ioRegPath) \(ioRegKey) \(hexVal) \(ioEncode) \(freeProcess)") + var rootPath: io_registry_entry_t + var masterPort = IOMainorMasterPortDefault + + guard IOMasterPort(bootstrap_port, &masterPort) == KERN_SUCCESS else { + print("\(thisComponent) : NO successful Acces with mastreport \(masterPort)") + return "" + } + guard !(IORegistryEntryFromPath(masterPort, ioRegPath) == 0) else { + print("\(thisComponent) : NO successful Access with mastreport \(masterPort) to path \(ioRegPath)") + return "" + } + + rootPath = IORegistryEntryFromPath(masterPort, ioRegPath) + + var ioRegValue : String = "" + let keyValueFound = IORegistryEntryCreateCFProperty(rootPath, ioRegKey as CFString, kCFAllocatorDefault, 0) + if (keyValueFound != nil) { + let notCleanedData = keyValueFound?.takeRetainedValue() as? Data + var cleanedData = Data() +// let hexVal = Data(bytes:newValue) + for i in 0.. String? { + var oNbrBytes: Int = 0 + sysctlbyname(sysctlKey, nil, &oNbrBytes, nil, 0) + var sysctlValue = [CChar](repeating: 0, count: Int(oNbrBytes)) + sysctlbyname(sysctlKey, &sysctlValue, &oNbrBytes, nil, 0) + print("\(thisComponent) : " + sysctlKey + " : \(String(validatingUTF8: sysctlValue) ?? "unknown")") + return String(validatingUTF8: sysctlValue) ?? "unknown" +} + + +func process(path: String, arguments: [String]) -> String? { + let task = Process() + task.launchPath = path + task.arguments = arguments + + let outputPipe = Pipe() + defer { + outputPipe.fileHandleForReading.closeFile() + } + task.standardOutput = outputPipe + + do { + if #available(macOS 10.13, *) { + try task.run() + } else { + let outputData = run("\(path) \(arguments)") + print("\(thisComponent) : macOS \(viewctrl_OS_Name) : \(outputData)") + } + } catch { + print("\(thisComponent) : Error(s) with \(path) \(arguments)") + return nil + } + + let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile() + let output = String(decoding: outputData, as: UTF8.self) + + if output.isEmpty { + return nil + } + return output +} + +extension Bundle { + /// Application name shown under the application icon. + var applicationName: String? { + object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? + object(forInfoDictionaryKey: "CFBundleName") as? String ?? + object(forInfoDictionaryKey: "CFBundleExecutable") as? String + } +} + +func doesDirectoryOrFileExist(absolutePath: String) -> Int { + var testIfDirectory: ObjCBool = false // default + var indResult : Int = 0 // 0 = Directory, 1 = File, -1 = absolutePath isn't a Directory nor a File (doesn't exist) + if initGlobVar.defaultfileManager.fileExists(atPath: absolutePath, isDirectory:&testIfDirectory) { + if testIfDirectory.boolValue { indResult = 0 + } else { indResult = 1 } + } else { indResult = -1 } + return indResult +} + +func doesURLFileExist(absolutePath: URL) -> Bool { + do { + if try absolutePath.checkResourceIsReachable() { + print(thisComponent + ": URL file \(absolutePath) reachable") + return true + } else { + print(thisComponent + ": URL file \(absolutePath) NOT reachable") + } + } catch { + print(thisComponent + ": URL file error: \(error)") + } + return false +} + +func getURLFileContentAsync(UrlFile: String) -> String { + print(thisComponent + " : getURLFileContentAsync for \(UrlFile)") + var urlFileContent = "" + DispatchQueue.global().async { + do { + urlFileContent = try String(contentsOf: URL(string: UrlFile)!) + } + catch { + urlFileContent = "\(UrlFile) : \(error)" //error as String + } + } + print(thisComponent + " : " + urlFileContent) + return urlFileContent +} diff --git a/About This Hack/UpdateController.swift b/About This Hack/UpdateController.swift index 635e576..a020abd 100644 --- a/About This Hack/UpdateController.swift +++ b/About This Hack/UpdateController.swift @@ -1,64 +1,320 @@ +// +// UpdateController.swift +// + import Foundation import AppKit +import Cocoa +import UserNotifications class UpdateController { + + static var thisComponent: String { + return String(describing: self) + } + + static var lastTagVersion :String = "" + static var marketVersion :String = "" + static var minSystemVersion :String = "Unknown" + static var exeToSearch :String = "" + static var kindToSearch :String = "app" + static var isUpdateAvailable :Bool = false + static var alertheader :String = "" + static var alertdetail :Any = "" + static var notificationID :Int = 0 + static var unzippedFile :Bool = false + static func checkForUpdates() -> Bool { - print("Checking for updates...") - let appVersion = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String - _ = run("curl -o" + " " + initGlobVar.athtargetversionfile + " " + initGlobVar.athsourceversionfile) - let latestVersion = run("tr -d '[:space:]' < " + initGlobVar.athtargetversionfile) - if appVersion < latestVersion { - print("Newer version (" + latestVersion + ") available") - let prompt = alert(message: "Update found!", text: "The latest version is " + latestVersion + ". You are currently running " + appVersion + ".") - print("Done") - return prompt + print("\(thisComponent) : Checking for updates...") + lastTagVersion = run("GIT_TERMINAL_PROMPT=0 git ls-remote --tags --refs \(initGlobVar.athrepositoryURL) | grep \"/tags/[0-9]\" | awk -F'/' '{print $NF}' | sort -u | tail -n1 | tr -d '\n'") + if lastTagVersion != "" && !lastTagVersion.starts(with: "fatal") { + let pbxProjLocat = initGlobVar.athlasttagpbxproj.replacingOccurrences(of: "[LASTTAG]", with: lastTagVersion) + marketVersion = run("\(initGlobVar.curlLocation) -s \(initGlobVar.athrepositoryURL)\(pbxProjLocat) | sed -e 's?,?\\n?g' -e 's?;?\\n?g' | grep \"MARKETING_VERSION = \" | awk '{print $NF}' | sort -u | tail -n1 | tr -d '\n' 2>/dev/null") +// Postulat : there is one and only one target in pbxproj + if marketVersion == "" { + marketVersion = thisApplicationVersion // fake marketVersion (ie. = localVersion) so no Update and no app. crash + } + print("\(thisComponent) : Local version (\(thisApplicationVersion)) and Remote version (\(marketVersion)) in .pbxproj from tag (\(lastTagVersion))") + if thisApplicationVersion < marketVersion { + //MARK: newer app found + print("\(thisComponent) : Newer version (\(marketVersion)) available") + let prompt = updateAlert(message: "Update found!", text: "The latest version is \(marketVersion).\nYou are currently running \(thisApplicationVersion).", buttonArray: ["Update", "Skip"]) + print("\(thisComponent) : Done") + return prompt + } + } else { + alertheader = "Can't get version from last remote repo tag" + alertdetail = "\(initGlobVar.athrepositoryURL)" + print("\(thisComponent) : \(alertheader) \(alertdetail)") + _ = updateAlert(message: "\(alertheader)", text: "\(alertdetail)", buttonArray: ["Return"]) } return false } + + static func updateATH() { + isUpdateAvailable = true + + //MARK: DownLoad new app.zip from repo + print("\(thisComponent) : Starting Download v\(marketVersion) Update...") + notify(title: "Starting Download... v\(marketVersion) Update...", informativeText: "") + guard run("\(initGlobVar.curlLocation) -L \(initGlobVar.lastAthreleaseURL)\(lastTagVersion)/\(initGlobVar.newAthziprelease) -o \(initGlobVar.newAthreleasezip)") == "" else { + alertheader = "Can't Download Update" + alertdetail = "\(initGlobVar.lastAthreleaseURL)\(lastTagVersion)/\(initGlobVar.newAthziprelease)" + print("\(thisComponent) : \(alertheader) \(alertdetail)") + _ = updateAlert(message: "\(alertheader)", text: "\(alertdetail)", buttonArray: ["Return"]) + isUpdateAvailable = false + return + } + + //MARK: unzip new app.zip + if isUpdateAvailable { + while (!initGlobVar.defaultfileManager.fileExists(atPath: initGlobVar.newAthreleasezip)) { + Thread.sleep(forTimeInterval: 0.2) + } + print("\(thisComponent) : Unzipping Archive...") + notify(title: "Unzipping Archive...", informativeText: "") + guard run("/usr/bin/unzip -q -o \(initGlobVar.newAthreleasezip) -d \(initGlobVar.athDirectory)") == "" else { + alertheader = "Can't unzip Archive" + alertdetail = "\(initGlobVar.newAthreleasezip) into \(initGlobVar.athDirectory)" + print("\(thisComponent) : \(alertheader) \(alertdetail)") + _ = updateAlert(message: "\(alertheader)", text: "\(alertdetail)", buttonArray: ["Return"]) + isUpdateAvailable = false + return + } + // what kind of extracted component a ".app" or a ".dmg" and man try 5 times + var notFoundLoop : Int = 0 + while (!unzippedFile) && (notFoundLoop < 5) { + Thread.sleep(forTimeInterval: 0.2) + exeToSearch = checkFileExtension(atPath: initGlobVar.athDirectory, withExtensions: [".app", ".dmg"]) + if exeToSearch != "" { + unzippedFile = true + notFoundLoop = 5 + kindToSearch = String(exeToSearch.suffix(3)) + } else { + notFoundLoop += 1 + } + } + if (!unzippedFile) && (notFoundLoop > 4) { + alertheader = "Can't find .app or .dmg extension on files name extracted from Archive" + alertdetail = "\(initGlobVar.newAthreleasezip) into \(initGlobVar.athDirectory)" + print("\(thisComponent) : \(alertheader) \(alertdetail)") + _ = updateAlert(message: "\(alertheader)", text: "\(alertdetail)", buttonArray: ["Return"]) + isUpdateAvailable = false + return + } + print("\(thisComponent) : From archive was extracted : \(exeToSearch)") + } + + //MARK: from new app.zip extracted .dmg or .app + if isUpdateAvailable && kindToSearch == "dmg" { + while (!initGlobVar.defaultfileManager.fileExists(atPath: exeToSearch)) { + Thread.sleep(forTimeInterval: 0.2) + } + //MARK: from new app.zip a .dmg extracted must be attached + print("\(thisComponent) : Try to mount dmg \(exeToSearch)") + notify(title: "Try to mount dmg...", informativeText: "") + guard run("/usr/bin/hdiutil attach \"\(exeToSearch)\" -nobrowse -quiet") == "" else { + alertheader = "Can't mount dmg" + alertdetail = "\(exeToSearch)" + print("\(thisComponent) : \(alertheader) \(alertdetail)") + _ = updateAlert(message: "\(alertheader)", text: "\(alertdetail)", buttonArray: ["Return"]) + isUpdateAvailable = false + return + } + if isUpdateAvailable { + while (!initGlobVar.defaultfileManager.fileExists(atPath: "/Volumes/\(thisApplicationName)")) { + Thread.sleep(forTimeInterval: 2) + } + //MARK: .dmg is mounted containing a .app so we copy it to temp Dir + print("\(thisComponent) : \(exeToSearch) mounted in /Volumes/\(thisApplicationName)!\n\(thisComponent) : Try to copy application /Volumes/\"\(thisApplicationName)/\(thisApplicationName).app\" to \(initGlobVar.athDirectory)") + guard run("/bin/cp -prf /Volumes/\"\(thisApplicationName)/\(thisApplicationName).app\" \(initGlobVar.athDirectory)/\"\(thisApplicationName).app\"") == "" else { + alertheader = "Can't copy application" + alertdetail = "/Volumes/\"\(thisApplicationName)/\(thisApplicationName).app\" to \(initGlobVar.athDirectory)" + print("\(thisComponent) : \(alertheader) \(alertdetail)") + _ = updateAlert(message: "\(alertheader)", text: "\(alertdetail)", buttonArray: ["Return"]) + isUpdateAvailable = false + return + } + } + if isUpdateAvailable { + while (!initGlobVar.defaultfileManager.fileExists(atPath: "\(initGlobVar.athDirectory)/\(thisApplicationName).app")) { + Thread.sleep(forTimeInterval: 0.2) + } + //MARK: .app from .dmg copied to temp Dir .dmg is detached + print("\(thisComponent) : /Volumes/\"\(thisApplicationName)/\(thisApplicationName).app\" copied to \(initGlobVar.athDirectory)!") + print("\(thisComponent) : Try to umount Volume \"/Volumes/\(thisApplicationName)\"") + notify(title: "Try to umount dmg...", informativeText: "") + guard run("/usr/bin/hdiutil detach /Volumes/\"\(thisApplicationName)\" -force -quiet") == "" else { + alertheader = "Can't umount \(thisApplicationName)dmg" + alertdetail = "/Volumes/\"\(thisApplicationName)\"" + print("\(thisComponent) : \(alertheader) \(alertdetail)") + _ = updateAlert(message: "\(alertheader)", text: "\(alertdetail)", buttonArray: ["Return"]) + isUpdateAvailable = false + return + } + print("\(thisComponent) : /Volumes/\"\(thisApplicationName)\" ejected!") + } + } + + //MARK: does this new .app alowed on this OS version + if isUpdateAvailable { + while (!initGlobVar.defaultfileManager.fileExists(atPath: "\(initGlobVar.athDirectory)/\(thisApplicationName).app")) { + Thread.sleep(forTimeInterval: 0.2) + } + print("\(thisComponent) : Is new app v\(marketVersion) allowed on current OS \(HCVersion.OSnum)?") + notify(title: "Is new app v\(marketVersion) allowed...", informativeText: "") + let plistNewVersion = "\(initGlobVar.athDirectory)/\(thisApplicationName).app/Contents/Info.plist" + if initGlobVar.defaultfileManager.fileExists(atPath: "\(plistNewVersion)") { + if let resourceFileDictionaryContent = NSDictionary(contentsOfFile: "\(plistNewVersion)") { + // Get "LSMinimumSystemVersion" value by key + minSystemVersion = resourceFileDictionaryContent.object(forKey: "LSMinimumSystemVersion")! as! String + } + } + if minSystemVersion == "Unknown" || minSystemVersion == "" { //plistNewVersion not found or LSMinimumSystemVersion key not found + alertheader = "Can't get Minimum OS Version" + alertdetail = "LSMinimumSystemVersion not found in\n\"\(plistNewVersion)\"" + print("\(thisComponent) : \(alertheader) \(alertdetail)") + _ = updateAlert(message: "\(alertheader)", text: "\(alertdetail)", buttonArray: ["Return"]) + isUpdateAvailable = false + return + } else { + let arrayMinOSVersion:[String] = minSystemVersion.components(separatedBy: ".") + let arrayCurOSVersion:[String] = HCVersion.OSnum.components(separatedBy: ".") + var minCountIndex:Int = 0 + var allowed:Bool = true + if arrayMinOSVersion.count > arrayCurOSVersion.count { + minCountIndex = arrayCurOSVersion.count + } else { + minCountIndex = arrayMinOSVersion.count + } + print("\(thisComponent) : Current OS version \(HCVersion.OSnum) and Minimum OS Version \(minSystemVersion)") + for index in 0...minCountIndex-1 { + if arrayCurOSVersion[index] < arrayMinOSVersion[index] { + allowed = false + break + } else { + if arrayCurOSVersion[index] > arrayMinOSVersion[index] { + break + } + } // if arrayCurOSVersion[index] = arrayMinOSVersion[index] go on to check with next index (next part of tows versions) + print("\(thisComponent) : Index (\(index)) : Current OS version \(arrayCurOSVersion[index]) and Minimum OS Version \(arrayMinOSVersion[index])") + } + if !allowed { + alertheader = "Update can't be achieved" + alertdetail = "Minimum OS Version \(minSystemVersion) is greater than current OS version \(HCVersion.OSnum)" + print("\(thisComponent) : \(alertheader) \(alertdetail)") + _ = updateAlert(message: "\(alertheader)", text: "\(alertdetail)", buttonArray: ["Return"]) + isUpdateAvailable = false + return + } else { + print("\(thisComponent) : Update (with \(minSystemVersion) as minimum OS version ) is allowed on current OS version \(HCVersion.OSnum)") + } + } + } + + //MARK: this new .app allowed current app is removed before installing new one + if isUpdateAvailable && initGlobVar.defaultfileManager.fileExists(atPath: initGlobVar.thisAppliLocation) { + while (!initGlobVar.defaultfileManager.fileExists(atPath: exeToSearch)) { + Thread.sleep(forTimeInterval: 0.2) + } + print("\(thisComponent) : Removing Old App...") + notify(title: "Removing Old App...", informativeText: "") + if initGlobVar.defaultfileManager.fileExists(atPath: initGlobVar.thisAppliLocation) { + do { + try initGlobVar.defaultfileManager.removeItem(atPath: "\(initGlobVar.thisAppliLocation)") + print("\(thisComponent) : Directory \(initGlobVar.thisAppliLocation) deleted successfully") + } catch { + print("\(thisComponent) : Error deleting Directory \(initGlobVar.thisAppliLocation) with error : (\(error))") + alertheader = "Failed to delete the old copy of" + alertdetail = "\(initGlobVar.thisAppliLocation)\nPlease make sure it is in \(initGlobVar.allAppliLocation) folder!!!" + print("\(thisComponent) : \(alertheader) \(alertdetail)") + _ = updateAlert(message: "\(alertheader)", text: "\(alertdetail)", buttonArray: ["Return"]) + isUpdateAvailable = false + return + } + } + } + + //MARK: current app removed new one replaces it + if isUpdateAvailable { + print("\(thisComponent) : Copying New Version \"/\(thisApplicationName).app\" Almost there!") + notify(title: "New Version Install...", informativeText: "") + guard run("/bin/mv -f \(initGlobVar.athDirectory)\"/\(thisApplicationName).app\" \(initGlobVar.allAppliLocation)") == "" else { + alertheader = "Can't replace application" + alertdetail = "\"\(thisApplicationName)\"" + print("\(thisComponent) : \(alertheader) \(alertdetail)") + _ = updateAlert(message: "\(alertheader)", text: "\(alertdetail)", buttonArray: ["Return"]) + isUpdateAvailable = false + return + } + } + + //MARK: update complete, that's all folks + if isUpdateAvailable { + while (!initGlobVar.defaultfileManager.fileExists(atPath: initGlobVar.thisAppliLocation)) { + Thread.sleep(forTimeInterval: 0.2) + } + if isUpdateAvailable { + print("\(thisComponent) : Update Complete!...Launching New \"\(thisApplicationName).app\" Version...") + notify(title: "Update Complete...Launching New Version...", informativeText: "") + _ = run("/usr/bin/open \"\(initGlobVar.thisAppliLocation)\"") + exit(0) + } + } + } - static func alert(message: String, text: String) -> Bool { + static func updateAlert(message: String, text: String, buttonArray: [String]) -> Bool { let alert = NSAlert() alert.messageText = message alert.informativeText = text alert.alertStyle = .critical - alert.addButton(withTitle: "Update") - alert.addButton(withTitle: "Skip") + buttonArray.forEach { buttonAlerte in + alert.addButton(withTitle: buttonAlerte) + } return alert.runModal() == NSApplication.ModalResponse.alertFirstButtonReturn } - - static func updateATH() { - print("Starting Update...") - print("Starting Download...") - notify(title: "Starting Download", informativeText: "This may take awhile...") - _ = run("curl -L " + initGlobVar.lastAthreleaseURL + run("tr -d '[:space:]' < " + initGlobVar.athtargetversionfile) + initGlobVar.newAthziprelease + " -o " + initGlobVar.newAthreleasezip) - print("Killing Old App...") - notify(title: "Replacing Apps", informativeText: "Deleting the old version and replacing it with the new version") - // Thanks for the code, Ben216k - let rm = run("rm -rf \"\(initGlobVar.thisAppliLocation)\"") - if rm.contains("No") { - notify(title: "Failed to delete the old copy of About This Hack.app", informativeText: "Please make sure it is in the Applications folder!!!") - return - } - _ = run("[[ ! -d \"\(initGlobVar.thisAppliLocation)\" ]]") - print("Unzipping Archive...") - notify(title: "Unzipping Archive", informativeText: "") - _ = run("unzip \(initGlobVar.newAthreleasezip) -d \(initGlobVar.athDirectory)") - - print("Copying New Version...") - notify(title: "Copying New Version", informativeText: "Almost there!") - _ = run("mv -f \(initGlobVar.athDirectory)" + "\"\(initGlobVar.thisAppliname)\"" + " \(initGlobVar.allAppliLocation)") - Thread.sleep(forTimeInterval: 0.5) - notify(title: "Update Complete!", informativeText: "Launching New Version...") - _ = run("open \"\(initGlobVar.thisAppliLocation)\"") - exit(0) - } - static func notify(title: String, informativeText: String) -> Void { - let notification = NSUserNotification() - notification.title = title - notification.informativeText = informativeText - notification.soundName = NSUserNotificationDefaultSoundName - NSUserNotificationCenter.default.deliver(notification) + notificationID += 1 + if #available(macOS 10.14, *) { + let notification = UNMutableNotificationContent() + notification.title = title + notification.body = informativeText + notification.sound = UNNotificationSound.default + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) + let request = UNNotificationRequest(identifier: "ATH \(notificationID)", content: notification, trigger: trigger) + UNUserNotificationCenter.current().add(request) { (error) in + if (error != nil) { + print("\(thisComponent) : \(String(describing: error))!") + } + } + } else { // macOS 10.13 and less + let notification = NSUserNotification() + notification.identifier = "ATH \(notificationID)" + notification.title = title + notification.informativeText = informativeText + notification.soundName = NSUserNotificationDefaultSoundName + NSUserNotificationCenter.default.deliver(notification) + } + } + + static func checkFileExtension(atPath path: String, withExtensions fileExtensionInArray:[String]) -> String { + let pathURL = NSURL(fileURLWithPath: path, isDirectory: true) + var fileNameToReturn: String = "" + if let enumerator = initGlobVar.defaultfileManager.enumerator(atPath: path) { + for file in enumerator { + let pathElement = NSURL(fileURLWithPath: file as! String, relativeTo: pathURL as URL).path + print("\(thisComponent) : Element from archive : \(String(describing: pathElement))") + if pathElement?.replacingOccurrences(of: "%20", with: " ").contains("\(initGlobVar.athDirectory)/\(thisApplicationName)") ?? false { + fileExtensionInArray.forEach { extention in + if pathElement?.hasSuffix(extention) ?? false { + fileNameToReturn = pathElement?.replacingOccurrences(of: "%20", with: " ") ?? "" + } + } + } + } + } + print("\(thisComponent) : Element returned \(fileNameToReturn)") + return fileNameToReturn } } diff --git a/README.md b/README.md index c8bc562..402f674 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,22 @@ Some values show more details when hovered over. See if you can find them all! ![CleanShot 2023-10-07 at 15 54 25](https://github.com/0xCUB3/About-This-Hack/assets/94565160/1b90c22b-a56b-4c58-9ef1-f27276db8850) +On High Sierra Update found + +![image](https://github.com/matxpa/About-This-Hack/assets/70573409/837c4265-77b1-46bf-ad84-4589fa307f08) + +Update found but not allowed (OS too old) + +![image](https://github.com/matxpa/About-This-Hack/assets/70573409/d171a1fb-44f9-4ec1-b8bc-4c1c7dd5b844) + +Update found on Monterey (and newer) + +![image](https://github.com/matxpa/About-This-Hack/assets/70573409/ca7481a0-c0ae-4952-9c2a-7dc717bad927) + +Update process with notifications (if you allow notifications on About This Hack) + +![image](https://github.com/matxpa/About-This-Hack/assets/70573409/27e1b9a5-dd01-4832-a058-798a5c249ba4) + ## Supported OS's - **10.12 Sierra and newer**