mirror of
https://github.com/CloverHackyColor/CloverBootloader.git
synced 2025-02-08 23:51:39 +01:00
Themes can be downloaded without git. The parser allows users to chose the Github user and the repository name, and so load themes from forks or any repo that has compatible directories structure (the repository must contains only themes, at first level).
568 lines
18 KiB
Swift
568 lines
18 KiB
Swift
//
|
|
// main.swift
|
|
// cloverDaemonSwift
|
|
//
|
|
// Created by vector sigma on 03/11/2019.
|
|
// Copyright © 2019 CloverHackyColor. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
let daemonVersion = "1.1.2"
|
|
|
|
let fm = FileManager.default
|
|
|
|
var mountRWCalled = fm.isWritableFile(atPath: "/")
|
|
|
|
func execAttr() -> [FileAttributeKey : Any] {
|
|
var attributes = [FileAttributeKey : Any]()
|
|
attributes[.posixPermissions] = NSNumber(value: 755) //NSNumber object. Use the int16Value method to retrieve the integer value for the permissions.
|
|
attributes[.groupOwnerAccountID] = NSNumber(value: 0) // NSNumber object containing an unsigned long.
|
|
attributes[.groupOwnerAccountName] = "wheel" as NSString
|
|
|
|
attributes[.ownerAccountID] = NSNumber(value: 0) //NSNumber object containing an unsigned long.
|
|
attributes[.ownerAccountName] = "root" as NSString
|
|
return attributes
|
|
}
|
|
|
|
func launchAttr() -> [FileAttributeKey : Any] {
|
|
var attributes = [FileAttributeKey : Any]()
|
|
attributes[.posixPermissions] = NSNumber(value: 420) //NSNumber object. Use the int16Value method to retrieve the integer value for the permissions.
|
|
attributes[.groupOwnerAccountID] = NSNumber(value: 0) // NSNumber object containing an unsigned long.
|
|
attributes[.groupOwnerAccountName] = "wheel" as NSString
|
|
|
|
attributes[.ownerAccountID] = NSNumber(value: 0) //NSNumber object containing an unsigned long.
|
|
attributes[.ownerAccountName] = "root" as NSString
|
|
return attributes
|
|
}
|
|
|
|
func run(cmd: String) {
|
|
let task = Process()
|
|
if #available(OSX 10.13, *) {
|
|
task.executableURL = URL(fileURLWithPath: "/bin/bash")
|
|
} else {
|
|
task.launchPath = "/bin/bash"
|
|
}
|
|
task.environment = ProcessInfo.init().environment
|
|
task.arguments = ["-c", cmd]
|
|
task.launch()
|
|
}
|
|
|
|
func checkSleepProxyClient(nvram: NSDictionary) {
|
|
let mDNSResponderPath = "/System/Library/LaunchDaemons/com.apple.mDNSResponder.plist"
|
|
let disableOption = "-DisableSleepProxyClient"
|
|
|
|
var value = "false"
|
|
|
|
if let nvdata = nvram.object(forKey: "Clover.DisableSleepProxyClient") as? Data {
|
|
value = String(decoding: nvdata, as: UTF8.self)
|
|
print("Clover.DisableSleepProxyClient=\(value).")
|
|
} else {
|
|
print("Clover.DisableSleepProxyClient is not set.")
|
|
}
|
|
|
|
if !fm.fileExists(atPath: mDNSResponderPath) {
|
|
print("Error: cannot found \(mDNSResponderPath).")
|
|
return
|
|
}
|
|
if !fm.isWritableFile(atPath: "/") {
|
|
print("Note: root filesystem is read-only.")
|
|
return
|
|
}
|
|
|
|
let toDisable : Bool = value == "true"
|
|
// check if enabled or disabled
|
|
if let mDNSResponder = NSDictionary(contentsOfFile: mDNSResponderPath) {
|
|
if let ProgramArguments = mDNSResponder.object(forKey: "ProgramArguments") as? NSArray {
|
|
var index : Int = -1
|
|
var serviceIsDisabled = false
|
|
for i in 0..<ProgramArguments.count {
|
|
if let arg = ProgramArguments.object(at: i) as? String {
|
|
if arg == disableOption {
|
|
index = i
|
|
serviceIsDisabled = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// no need to do anything if already as user wants.
|
|
if toDisable && serviceIsDisabled {
|
|
print("Sleep Proxy Client is already disabled.")
|
|
return
|
|
} else if !toDisable && !serviceIsDisabled {
|
|
print("Sleep Proxy Client is already enabled as default.")
|
|
return
|
|
}
|
|
|
|
|
|
if toDisable {
|
|
print("Trying to disable Sleep Proxy Client service.")
|
|
let cmd = "/usr/libexec/PlistBuddy -c \"Add ProgramArguments: string \(disableOption)\" \(mDNSResponderPath)"
|
|
run(cmd: cmd)
|
|
} else {
|
|
print("Trying to enable Sleep Proxy Client service as default.")
|
|
if index >= 0 {
|
|
let cmd = "/usr/libexec/PlistBuddy -c \"delete :ProgramArguments:\(index)\" \(mDNSResponderPath)"
|
|
run(cmd: cmd)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func getLogOutHook() -> String? {
|
|
return NSDictionary(contentsOfFile: loginWindowPath)?.object(forKey: "LogoutHook") as? String
|
|
}
|
|
|
|
func removeCloverRCScripts() {
|
|
let oldFiles : [String] = ["/etc/rc.boot.d/10.save_and_rotate_boot_log.local",
|
|
"/etc/rc.boot.d/20.mount_ESP.local",
|
|
"/etc/rc.boot.d/70.disable_sleep_proxy_client.local.disabled",
|
|
"/etc/rc.boot.d/70.disable_sleep_proxy_client.local",
|
|
"/etc/rc.clover.lib",
|
|
"/etc/rc.shutdown.d/80.save_nvram_plist.local",
|
|
oldLaunchDaemonPath,
|
|
oldLaunchPlistPath,
|
|
oldLogoutHookPath]
|
|
|
|
if fm.fileExists(atPath: oldLaunchPlistPath) {
|
|
print("unloading old CloverDaemon..")
|
|
run(cmd: "launchctl unload \(oldLaunchPlistPath)")
|
|
}
|
|
|
|
for f in oldFiles {
|
|
if fm.fileExists(atPath: f) {
|
|
if !fm.isWritableFile(atPath: "/") {
|
|
run(cmd: "mount -uw /")
|
|
sleep(3)
|
|
}
|
|
print("Removing \(f).")
|
|
do {
|
|
try fm.removeItem(atPath: f)
|
|
} catch {
|
|
print(error)
|
|
}
|
|
}
|
|
}
|
|
|
|
if getLogOutHook() == oldLogoutHookPath {
|
|
print("Removing old logoutHook (\(oldLogoutHookPath))..")
|
|
run(cmd: "defaults delete com.apple.loginwindow LogoutHook")
|
|
}
|
|
}
|
|
|
|
func installHook() {
|
|
print("Installing the LogoutHook..")
|
|
// if a hook exist and is not our one, then add a wrapper
|
|
if let loh = getLogOutHook() {
|
|
if loh != cloverLogOut {
|
|
if fm.fileExists(atPath: loh) {
|
|
let wrapper =
|
|
"""
|
|
#!/bin/sh
|
|
|
|
'\(cloverLogOut)'
|
|
|
|
# This file is automatically generated by CloverDaemonNew because
|
|
# it has detected you added a logout script somewhere else.
|
|
# if you edit this file, be aware that if you start to boot in UEFI
|
|
# CloverDaemonNew will take care to restore your custom logout script,
|
|
# but your script must be the last line to be detected again.
|
|
# The script path must be escaped within '' if contains spaces in the middle.
|
|
# NOTE: if your will is to add multiple scripts, please add them to
|
|
# the following script (and so not in this script):
|
|
|
|
'\(loh)'
|
|
"""
|
|
print("Detected user logout hook at \(loh), merging it with CloverLogOut in CloverWrapper.sh.")
|
|
do {
|
|
try wrapper.write(toFile: wrapperPath, atomically: false, encoding: .utf8)
|
|
try fm.setAttributes(execAttr(), ofItemAtPath: wrapperPath)
|
|
|
|
run(cmd: "defaults write com.apple.loginwindow LogoutHook '\(wrapperPath)'")
|
|
} catch {
|
|
print(error)
|
|
}
|
|
} else {
|
|
print("Detected user logout hook at \(loh), but the executable did not exist, installing our.")
|
|
run(cmd: "defaults write com.apple.loginwindow LogoutHook '\(cloverLogOut)'")
|
|
}
|
|
} else {
|
|
print("LogoutHook is already set.")
|
|
}
|
|
} else {
|
|
print("LogoutHook is not set, writing it.")
|
|
run(cmd: "defaults write com.apple.loginwindow LogoutHook '\(cloverLogOut)'")
|
|
}
|
|
}
|
|
|
|
func unInstallHook() {
|
|
// what to do? remove the logout hook if it's our
|
|
var uninstall = false
|
|
if let loh = getLogOutHook() {
|
|
if (loh == cloverLogOut || loh == oldLogoutHookPath){
|
|
// it is CloverLogOut
|
|
print("Removing CloverLogOut hook as EmuVariableUefiPresent doesn't exixts.")
|
|
uninstall = true
|
|
} else if loh == wrapperPath {
|
|
print("Removing CloverWrapper.sh logout hook as EmuVariableUefiPresent doesn't exixts.")
|
|
// it is CloverWrapper.sh. We have to mantain user script!
|
|
// get the user script path:
|
|
var scriptPath : String? = nil
|
|
if let lines = try? String(contentsOf: URL(fileURLWithPath: wrapperPath),
|
|
encoding: .utf8).components(separatedBy: .newlines) {
|
|
if lines.count > 0 {
|
|
scriptPath = lines.last
|
|
}
|
|
}
|
|
|
|
if scriptPath != nil {
|
|
if fm.fileExists(atPath: scriptPath!) {
|
|
print("..but taking user logout hook script at \(scriptPath!).")
|
|
run(cmd: "defaults write com.apple.loginwindow LogoutHook \(scriptPath!)")
|
|
} else {
|
|
uninstall = true
|
|
}
|
|
} else {
|
|
uninstall = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if uninstall {
|
|
run(cmd: "defaults delete com.apple.loginwindow LogoutHook")
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
let df = DateFormatter()
|
|
df.locale = Locale(identifier: "en_US")
|
|
df.dateFormat = "yyyy-MM-dd hh:mm:ss"
|
|
var now = df.string(from: Date())
|
|
|
|
print("\n\n--------------------------------------------")
|
|
print("- CloverDaemonNew v\(daemonVersion)")
|
|
print("- macOS \(ProcessInfo().operatingSystemVersionString)")
|
|
print("- System start at \(now)")
|
|
print("------")
|
|
run(cmd: "kextunload /System/Library/Extensions/msdosfs.kext 2>/dev/null")
|
|
if let volname = getMediaName(from: "/") {
|
|
print("root mount point is '/Volumes/\(volname)'")
|
|
}
|
|
// check Clover revision
|
|
if let revision = findCloverRevision() {
|
|
print("Started with Clover r\(revision).")
|
|
} else {
|
|
print("Started with an unknown firmware.")
|
|
}
|
|
|
|
// check if old daemon exist
|
|
removeCloverRCScripts()
|
|
|
|
/*
|
|
Clean some lines from clover.daemon.log.
|
|
This is not going to go into the System log and is not
|
|
controllable by the nvram.
|
|
*/
|
|
|
|
let logLinesMax : Int = 500
|
|
let logDir = "/Library/Logs/CloverEFI"
|
|
let logPath = "/Library/Logs/CloverEFI/clover.daemon.log"
|
|
|
|
if !fm.fileExists(atPath: logDir) {
|
|
try? fm.createDirectory(atPath: logDir,
|
|
withIntermediateDirectories: false,
|
|
attributes: nil)
|
|
}
|
|
|
|
if fm.fileExists(atPath: logPath) {
|
|
if let log : String? = try? String(contentsOfFile: logPath) {
|
|
if let lines = log?.components(separatedBy: CharacterSet.newlines) {
|
|
if lines.count > logLinesMax {
|
|
// take only latests
|
|
run(cmd: "tail -n \(logLinesMax) \(logPath) 2>/dev/null > \(logPath).tmp")
|
|
run(cmd: "mv -f \(logPath).tmp \(logPath)")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// check options in nvram
|
|
let fv = getFirmawareVendor() ?? "unknown"
|
|
var needsLogoutHook = isLegacyFirmware()
|
|
print("Firmware vendor is '\(fv)'.")
|
|
if let nvram = getNVRAM() {
|
|
if let nvdata = nvram.object(forKey: "Clover.RootRW") as? Data {
|
|
let value = String(decoding: nvdata, as: UTF8.self)
|
|
if value == "true" {
|
|
if !mountRWCalled {
|
|
mountRWCalled = true
|
|
print("making '/' writable as Clover.RootRW=true.")
|
|
run(cmd: "mount -uw /")
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if !needsLogoutHook {
|
|
if (nvram.object(forKey: "EmuVariableUefiPresent") != nil ||
|
|
nvram.object(forKey: "TestEmuVariableUefiPresent") != nil) {
|
|
print("Found EmuVariableUefiPresent in NVRAM.")
|
|
needsLogoutHook = true
|
|
} else {
|
|
print("EmuVariableUefiPresent doesn't exist.")
|
|
}
|
|
}
|
|
|
|
if let nvdata = nvram.object(forKey: "Clover.DisableSleepProxyClient") as? Data {
|
|
let value = String(decoding: nvdata, as: UTF8.self)
|
|
if value == "true" {
|
|
if !fm.isWritableFile(atPath: "/") {
|
|
if !mountRWCalled {
|
|
print("making '/' writable as Clover.DisableSleepProxyClient=true.")
|
|
}
|
|
run(cmd: "mount -uw /")
|
|
sleep(2)
|
|
}
|
|
}
|
|
}
|
|
|
|
checkSleepProxyClient(nvram: nvram)
|
|
/*
|
|
Clean old nvram.plist user may have in all volumes
|
|
Note: never delete in / as this will be done at shut down/restart
|
|
if the nvram is correctly saved somewhere else (e.g. in the ESP).
|
|
|
|
Also don't delete nvram.plist from external devices as this is not or
|
|
shouldn't be our business.
|
|
*/
|
|
|
|
for v in getVolumes() {
|
|
let nvramtPath = v.addPath("nvram.plist")
|
|
if v != "/" && isInternalDevice(diskOrMtp: v) {
|
|
if fm.fileExists(atPath: nvramtPath) {
|
|
if fm.isDeletableFile(atPath: nvramtPath) {
|
|
do {
|
|
try fm.removeItem(atPath: nvramtPath)
|
|
print("old '\(nvramtPath)' removed.")
|
|
} catch {
|
|
print("Error: can't remove '\(nvramtPath)'.")
|
|
}
|
|
} else {
|
|
print("Error: '\(nvramtPath)' is not deletable.")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
Mount ESP if required
|
|
Clover.MountEFI=Yes|diskX|GUID [default No]
|
|
*/
|
|
if let nvdata = nvram.object(forKey: "Clover.MountEFI") as? Data {
|
|
let value = String(decoding: nvdata, as: UTF8.self)
|
|
print("Clover.MountEFI=\(value)")
|
|
var disk : String? = nil
|
|
let allEsps = getAllESPs()
|
|
if value == "Yes" {
|
|
// mount the boot device
|
|
disk = findBootPartitionDevice()
|
|
} else if value.hasPrefix("disk") {
|
|
disk = value
|
|
} else if value.hasPrefix("/dev/") {
|
|
// mount desired ESP /dev/disk-rdisk
|
|
var tmpdisk = value
|
|
if tmpdisk.hasPrefix("/dev/disk") {
|
|
tmpdisk = value.replacingOccurrences(of: "/dev/", with: "")
|
|
} else if tmpdisk.hasPrefix("/dev/rdisk") {
|
|
tmpdisk = value.replacingOccurrences(of: "/dev/r", with: "")
|
|
}
|
|
if tmpdisk.hasPrefix("disk") {
|
|
disk = tmpdisk
|
|
}
|
|
} else if value.count == 36 && ((value .range(of: "-") != nil)) {
|
|
// mount desired ESP by Volume UUID
|
|
for d in allEsps {
|
|
if let uuid = getMediaUUID(from: d) {
|
|
if uuid == value {
|
|
disk = d
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (disk != nil) {
|
|
if (getBSDName(of: disk!) != nil) {
|
|
if allEsps.contains(disk!) {
|
|
print("try to mount \(disk!) as requested.")
|
|
mount(disk: disk!, at: nil)
|
|
} else {
|
|
print("\(disk!) is not an EFI System Partition.")
|
|
}
|
|
} else {
|
|
print("\(disk!) is not a valid disk object or is unavailable at the moment.")
|
|
}
|
|
} else {
|
|
print("Nothing to mount.")
|
|
}
|
|
}
|
|
|
|
} else {
|
|
print("Error: nvram not present in this System.")
|
|
}
|
|
|
|
if needsLogoutHook {
|
|
installHook()
|
|
} else {
|
|
unInstallHook()
|
|
}
|
|
|
|
print("Logout hook is: \(getLogOutHook() ?? "none")")
|
|
signal(SIGTERM, SIG_IGN) //preferred
|
|
let sigtermSource = DispatchSource.makeSignalSource(signal: SIGTERM)
|
|
sigtermSource.setEventHandler {
|
|
now = df.string(from: Date())
|
|
print("")
|
|
print("SIGTERM received at \(now)")
|
|
if let nvram = getNVRAM() {
|
|
checkSleepProxyClient(nvram: nvram)
|
|
} else {
|
|
print("nvram not present in this machine.")
|
|
}
|
|
exit(EXIT_SUCCESS)
|
|
}
|
|
sigtermSource.resume()
|
|
|
|
RunLoop.current.run()
|
|
//dispatchMain()
|
|
}
|
|
|
|
let myPath = CommandLine.arguments[0]
|
|
let myName = (myPath as NSString).lastPathComponent
|
|
|
|
if CommandLine.arguments.count > 1 {
|
|
if CommandLine.arguments[1] == "--install" {
|
|
print("Installing daemon...")
|
|
// build the launch daemon
|
|
let launch = NSMutableDictionary()
|
|
launch.setValue(true, forKey: "KeepAlive")
|
|
launch.setValue(true, forKey: "RunAtLoad")
|
|
launch.setValue("com.slice.CloverDaemonNew", forKey: "Label")
|
|
|
|
launch.setValue("/Library/Logs/CloverEFI/clover.daemon.log", forKey: "StandardErrorPath")
|
|
launch.setValue("/Library/Logs/CloverEFI/clover.daemon.log", forKey: "StandardOutPath")
|
|
|
|
let ProgramArguments = NSArray(object: cloverDaemonNewPath)
|
|
|
|
launch.setValue(ProgramArguments, forKey: "ProgramArguments")
|
|
|
|
removeCloverRCScripts()
|
|
do {
|
|
if !fm.fileExists(atPath: "/Library/Application Support/Clover") {
|
|
try fm.createDirectory(atPath: "/Library/Application Support/Clover",
|
|
withIntermediateDirectories: false,
|
|
attributes: nil)
|
|
}
|
|
|
|
if fm.fileExists(atPath: launchPlistPath) {
|
|
try fm.removeItem(atPath: launchPlistPath)
|
|
}
|
|
|
|
if fm.fileExists(atPath: cloverDaemonNewPath) {
|
|
try fm.removeItem(atPath: cloverDaemonNewPath)
|
|
}
|
|
|
|
launch.write(toFile: launchPlistPath, atomically: true)
|
|
|
|
try fm.copyItem(atPath: myPath, toPath: cloverDaemonNewPath)
|
|
|
|
try fm.setAttributes(execAttr(),
|
|
ofItemAtPath: cloverDaemonNewPath)
|
|
|
|
|
|
let logouthookSrc = myPath.deletingLastPath.addPath(cloverLogOut.lastPath)
|
|
if fm.fileExists(atPath: cloverLogOut) {
|
|
try fm.removeItem(atPath: cloverLogOut)
|
|
}
|
|
|
|
if fm.fileExists(atPath: logouthookSrc) {
|
|
try fm.copyItem(atPath: logouthookSrc,
|
|
toPath: cloverLogOut)
|
|
try fm.setAttributes(execAttr(),
|
|
ofItemAtPath: cloverLogOut)
|
|
}
|
|
|
|
var needsLogoutHook = isLegacyFirmware()
|
|
if !needsLogoutHook {
|
|
if let nvram = getNVRAM() {
|
|
if (nvram.object(forKey: "EmuVariableUefiPresent") != nil ||
|
|
nvram.object(forKey: "TestEmuVariableUefiPresent") != nil) {
|
|
needsLogoutHook = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if needsLogoutHook {
|
|
installHook()
|
|
} else {
|
|
unInstallHook()
|
|
}
|
|
|
|
try fm.setAttributes(launchAttr(), ofItemAtPath: launchPlistPath)
|
|
if fm.fileExists(atPath: launchPlistPath) {
|
|
run(cmd: "launchctl unload \(launchPlistPath)")
|
|
}
|
|
run(cmd: "launchctl load \(launchPlistPath)")
|
|
run(cmd: "launchctl start \(launchPlistPath)")
|
|
exit(EXIT_SUCCESS)
|
|
} catch {
|
|
print(error)
|
|
}
|
|
} else if CommandLine.arguments[1] == "--uninstall" {
|
|
print("uninstalling daemon...")
|
|
do {
|
|
if fm.fileExists(atPath: launchPlistPath) {
|
|
run(cmd: "launchctl unload \(launchPlistPath)")
|
|
try fm.removeItem(atPath: launchPlistPath)
|
|
}
|
|
|
|
if fm.fileExists(atPath: cloverDaemonNewPath) {
|
|
try fm.removeItem(atPath: cloverDaemonNewPath)
|
|
}
|
|
|
|
if fm.fileExists(atPath: cloverLogOut) {
|
|
try fm.removeItem(atPath: cloverLogOut)
|
|
}
|
|
|
|
if fm.fileExists(atPath: wrapperPath) {
|
|
try fm.removeItem(atPath: wrapperPath)
|
|
}
|
|
|
|
if fm.fileExists(atPath: frameworksPath) {
|
|
try fm.removeItem(atPath: frameworksPath)
|
|
}
|
|
|
|
unInstallHook()
|
|
exit(EXIT_SUCCESS)
|
|
} catch {
|
|
print(error)
|
|
}
|
|
} else if CommandLine.arguments[1] == "--CLOVER" {
|
|
let installer = Installer()
|
|
installer.realTimeOutPut = true
|
|
installer.install()
|
|
RunLoop.current.run()
|
|
} else {
|
|
print("Nothing to do bro'")
|
|
}
|
|
} else {
|
|
// be the Daemon
|
|
main()
|
|
}
|
|
|
|
exit(EXIT_FAILURE)
|
|
|