import Foundation import IOKit /// SMC-Zugriff **mit** Schreibpfad — nur im privilegierten Helfer. /// /// Bewusst eine eigene Kopie und keine geteilte Bibliothek mit der App: der /// Schreibpfad soll gar nicht erst in einem Prozess vorhanden sein, der ohne /// Sonderrechte läuft und in dem jedes Widget Code ausführt. Was nicht da ist, /// kann auch nicht versehentlich aufgerufen werden. /// /// Byte-Reihenfolge und Struktur stammen aus `docs/spikes/A-smc.md`. final class SMCAccess { private struct Version { var major: UInt8 = 0; var minor: UInt8 = 0 var build: UInt8 = 0; var reserved: UInt8 = 0 var release: UInt16 = 0 } private struct PLimitData { var version: UInt16 = 0; var length: UInt16 = 0 var cpuPLimit: UInt32 = 0; var gpuPLimit: UInt32 = 0 var memPLimit: UInt32 = 0 } private struct KeyInfo { var dataSize: UInt32 = 0; var dataType: UInt32 = 0 var dataAttributes: UInt8 = 0 } private struct Param { var key: UInt32 = 0 var vers = Version() var pLimitData = PLimitData() var keyInfo = KeyInfo() var padding: UInt16 = 0 var result: UInt8 = 0 var status: UInt8 = 0 var data8: UInt8 = 0 var data32: UInt32 = 0 var bytes: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) } private static let handleYPCEvent: UInt32 = 2 private static let readKey: UInt8 = 5 private static let writeKey: UInt8 = 6 private static let getKeyFromIndex: UInt8 = 8 private static let getKeyInfo: UInt8 = 9 private var connection: io_connect_t = 0 private var keyInfoCache: [String: KeyInfo] = [:] init?() { let service = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("AppleSMC")) guard service != 0 else { return nil } defer { IOObjectRelease(service) } guard IOServiceOpen(service, mach_task_self_, 0, &connection) == kIOReturnSuccess else { return nil } } deinit { if connection != 0 { IOServiceClose(connection) } } // MARK: - Lesen func float(_ key: String) -> Double? { guard let value = read(key), value.bytes.count >= 4 else { return nil } let bits = UInt32(value.bytes[3]) << 24 | UInt32(value.bytes[2]) << 16 | UInt32(value.bytes[1]) << 8 | UInt32(value.bytes[0]) return Double(Float(bitPattern: bits)) } func uint8(_ key: String) -> UInt8? { read(key)?.bytes.first } func keys(withPrefix prefix: String) -> [String] { guard let value = read("#KEY"), value.bytes.count >= 4 else { return [] } // Big-endian, anders als die Messwerte — siehe Spike A. let total = UInt32(value.bytes[0]) << 24 | UInt32(value.bytes[1]) << 16 | UInt32(value.bytes[2]) << 8 | UInt32(value.bytes[3]) guard total > 0, total < 100_000 else { return [] } var found: [String] = [] for index in 0.. Bool { let bits = Float(value).bitPattern return write(key, bytes: [UInt8(bits & 0xFF), UInt8((bits >> 8) & 0xFF), UInt8((bits >> 16) & 0xFF), UInt8((bits >> 24) & 0xFF)]) } /// Schreibt ein einzelnes Byte (Betriebsart). @discardableResult func writeUInt8(_ key: String, _ value: UInt8) -> Bool { write(key, bytes: [value]) } private func write(_ key: String, bytes: [UInt8]) -> Bool { guard let info = keyInfo(for: key) else { return false } // Nur schreiben, wenn die Größe zur Firmware passt. Ein zu kurzer oder // zu langer Schreibvorgang landet sonst in benachbarten Feldern. guard Int(info.dataSize) == bytes.count else { return false } var command = Param() command.key = Self.fourCC(key) command.keyInfo = info command.data8 = Self.writeKey withUnsafeMutableBytes(of: &command.bytes) { raw in for (index, byte) in bytes.enumerated() { raw[index] = byte } } return call(command) != nil } // MARK: - Innereien private struct Value { let bytes: [UInt8] } private func keyInfo(for key: String) -> KeyInfo? { if let cached = keyInfoCache[key] { return cached } var command = Param() command.key = Self.fourCC(key) command.data8 = Self.getKeyInfo guard let output = call(command) else { return nil } keyInfoCache[key] = output.keyInfo return output.keyInfo } private func read(_ key: String) -> Value? { guard let info = keyInfo(for: key) else { return nil } var command = Param() command.key = Self.fourCC(key) command.keyInfo = info command.data8 = Self.readKey guard let output = call(command) else { return nil } let all = withUnsafeBytes(of: output.bytes) { Array($0) } return Value(bytes: Array(all.prefix(Int(min(info.dataSize, 32))))) } private func call(_ input: Param) -> Param? { var input = input var output = Param() var size = MemoryLayout.stride let result = IOConnectCallStructMethod(connection, Self.handleYPCEvent, &input, MemoryLayout.stride, &output, &size) guard result == kIOReturnSuccess, output.result == 0 else { return nil } return output } private static func fourCC(_ string: String) -> UInt32 { var value: UInt32 = 0 for character in string.utf8.prefix(4) { value = value << 8 | UInt32(character) } return value } private static func fourCCString(_ value: UInt32) -> String { let bytes = [UInt8((value >> 24) & 0xFF), UInt8((value >> 16) & 0xFF), UInt8((value >> 8) & 0xFF), UInt8(value & 0xFF)] return String(bytes: bytes, encoding: .ascii) ?? "????" } }