// Spike A — SMC-Key-Dump für Onyx // // Zweck: herausfinden, welche Lüfter- und Ladesteuerungs-Keys auf diesem Mac // (Mac17,9 / M5 Pro) tatsächlich existieren, welchen Typ und welche Größe sie // haben und welche Werte gerade anliegen. // // AUSSCHLIESSLICH LESEND. Dieser Spike enthält bewusst keinen Schreibpfad — // kSMCWriteKey wird nirgends verwendet. // // Bauen: xcrun swiftc -O Spikes/smc-dump.swift -o /tmp/smc-dump // Nutzen: /tmp/smc-dump → Zusammenfassung der interessanten Keys // /tmp/smc-dump --all → vollständiger Dump aller Keys import Foundation import IOKit // MARK: - Layout des AppleSMC-UserClients struct SMCVersion { var major: UInt8 = 0 var minor: UInt8 = 0 var build: UInt8 = 0 var reserved: UInt8 = 0 var release: UInt16 = 0 } struct SMCPLimitData { var version: UInt16 = 0 var length: UInt16 = 0 var cpuPLimit: UInt32 = 0 var gpuPLimit: UInt32 = 0 var memPLimit: UInt32 = 0 } struct SMCKeyInfoData { var dataSize: UInt32 = 0 var dataType: UInt32 = 0 var dataAttributes: UInt8 = 0 } struct SMCParamStruct { var key: UInt32 = 0 var vers = SMCVersion() var pLimitData = SMCPLimitData() var keyInfo = SMCKeyInfoData() 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 let kSMCHandleYPCEvent: UInt32 = 2 private let kSMCReadKey: UInt8 = 5 private let kSMCGetKeyFromIndex: UInt8 = 8 private let kSMCGetKeyInfo: UInt8 = 9 // MARK: - FourCC-Hilfen func fourCC(_ s: String) -> UInt32 { var result: UInt32 = 0 for ch in s.utf8.prefix(4) { result = result << 8 | UInt32(ch) } return result } func fourCCString(_ v: UInt32) -> String { let bytes = [UInt8((v >> 24) & 0xFF), UInt8((v >> 16) & 0xFF), UInt8((v >> 8) & 0xFF), UInt8(v & 0xFF)] return String(bytes: bytes, encoding: .ascii) ?? "????" } // MARK: - Verbindung struct SMCError: Error, CustomStringConvertible { let description: String } final class SMC { private var connection: io_connect_t = 0 init() throws { let service = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("AppleSMC")) guard service != 0 else { throw SMCError(description: "AppleSMC-Service nicht gefunden") } defer { IOObjectRelease(service) } let result = IOServiceOpen(service, mach_task_self_, 0, &connection) guard result == kIOReturnSuccess else { throw SMCError(description: "IOServiceOpen fehlgeschlagen: 0x\(String(result, radix: 16))") } } deinit { if connection != 0 { IOServiceClose(connection) } } private func call(_ input: SMCParamStruct) throws -> SMCParamStruct { var input = input var output = SMCParamStruct() var outputSize = MemoryLayout.stride let result = IOConnectCallStructMethod(connection, kSMCHandleYPCEvent, &input, MemoryLayout.stride, &output, &outputSize) guard result == kIOReturnSuccess else { throw SMCError(description: "IOConnectCallStructMethod: 0x\(String(result, radix: 16))") } // result == 132 heißt "Key existiert nicht" — das ist ein erwarteter Zustand. guard output.result == 0 else { throw SMCError(description: "SMC-Status \(output.result)") } return output } func keyCount() throws -> UInt32 { let value = try read(key: "#KEY") return value.uint32 } func key(atIndex index: UInt32) throws -> String { var input = SMCParamStruct() input.data8 = kSMCGetKeyFromIndex input.data32 = index return fourCCString(try call(input).key) } struct Value { let key: String let type: String let size: UInt32 let bytes: [UInt8] var uint32: UInt32 { var v: UInt32 = 0 for b in bytes.prefix(4) { v = v << 8 | UInt32(b) } return v } /// Best-effort-Interpretation anhand des SMC-Typs. var decoded: String { guard !bytes.isEmpty else { return "—" } switch type { case "ui8 ", "ui16", "ui32", "ui64": var v: UInt64 = 0 for b in bytes.prefix(Int(size)) { v = v << 8 | UInt64(b) } return String(v) case "si8 ": return String(Int8(bitPattern: bytes[0])) case "si16": let v = Int16(bitPattern: UInt16(bytes[0]) << 8 | UInt16(bytes[1])) return String(v) case "flag": return bytes[0] == 1 ? "true" : "false" case "flt ": guard bytes.count >= 4 else { return "—" } let bits = UInt32(bytes[3]) << 24 | UInt32(bytes[2]) << 16 | UInt32(bytes[1]) << 8 | UInt32(bytes[0]) return String(format: "%.2f", Float(bitPattern: bits)) case "fpe2": guard bytes.count >= 2 else { return "—" } return String((UInt16(bytes[0]) << 8 | UInt16(bytes[1])) >> 2) case "sp78": guard bytes.count >= 2 else { return "—" } let raw = Int16(bitPattern: UInt16(bytes[0]) << 8 | UInt16(bytes[1])) return String(format: "%.2f", Float(raw) / 256.0) case "ch8*": return String(bytes: bytes.prefix(Int(size)), encoding: .ascii)? .trimmingCharacters(in: CharacterSet(charactersIn: "\0")) ?? "—" case "{fds": // Fan-Descriptor-Struktur: die ersten 12 Byte sind der Fan-Name. return String(bytes: bytes.prefix(12), encoding: .ascii)? .trimmingCharacters(in: CharacterSet(charactersIn: "\0 ")) ?? "—" default: return "—" } } var hex: String { bytes.prefix(Int(size)).map { String(format: "%02X", $0) }.joined(separator: " ") } } func read(key: String) throws -> Value { var info = SMCParamStruct() info.key = fourCC(key) info.data8 = kSMCGetKeyInfo let keyInfo = try call(info).keyInfo var readCmd = SMCParamStruct() readCmd.key = fourCC(key) readCmd.keyInfo = keyInfo readCmd.data8 = kSMCReadKey let output = try call(readCmd) let all = withUnsafeBytes(of: output.bytes) { Array($0) } return Value(key: key, type: fourCCString(keyInfo.dataType), size: min(keyInfo.dataSize, 32), bytes: Array(all.prefix(Int(min(keyInfo.dataSize, 32))))) } } // MARK: - Ausgabe func printRow(_ v: SMC.Value, note: String = "") { let key = v.key.padding(toLength: 6, withPad: " ", startingAt: 0) let type = v.type.padding(toLength: 6, withPad: " ", startingAt: 0) let size = String(v.size).padding(toLength: 3, withPad: " ", startingAt: 0) let dec = v.decoded.padding(toLength: 14, withPad: " ", startingAt: 0) print(" \(key) \(type) \(size) \(dec) \(v.hex)\(note.isEmpty ? "" : " ← \(note)")") } func section(_ title: String) { print("\n\(title)") print(String(repeating: "─", count: 72)) print(" KEY TYPE SZ WERT ROH") } let smc: SMC do { smc = try SMC() } catch { print("FEHLER: \(error)") exit(1) } print("SMC-Dump — \(ProcessInfo.processInfo.hostName)") var model = [CChar](repeating: 0, count: 64) var modelSize = 64 sysctlbyname("hw.model", &model, &modelSize, nil, 0) print("Modell: \(String(cString: model)) macOS \(ProcessInfo.processInfo.operatingSystemVersionString)") print("Als root: \(getuid() == 0 ? "ja" : "nein")") // --- Lüfter --------------------------------------------------------------- section("LÜFTER") var fanCount = 0 if let count = try? smc.read(key: "FNum") { printRow(count, note: "Anzahl Lüfter") fanCount = Int(count.uint32) } else { print(" FNum nicht lesbar — keine Lüfterzählung verfügbar") } // Pro Lüfter: Ist, Min, Max, Ziel, Modus (0 = auto, 1 = manuell), Beschreibung let fanSuffixes: [(String, String)] = [ ("Ac", "Ist-Drehzahl"), ("Mn", "Minimum"), ("Mx", "Maximum"), ("Tg", "Ziel-Drehzahl"), ("Md", "Modus 0=auto 1=manuell"), ("ID", "Beschreibung"), ] for fan in 0.. 0 { printRow(v); foundTemp += 1 } } if foundTemp == 0 { print(" Keine klassischen T-Keys — auf Apple Silicon liegen die Temperaturen") print(" überwiegend hinter IOHIDEventSystemClient, nicht hinter SMC-Keys.") } // --- Leistung ------------------------------------------------------------- section("LEISTUNG") for (key, note) in [("PSTR", "Systemleistung W"), ("PDTR", "DC-Eingang W"), ("PMVR", "Netzteil W"), ("PPBR", "Akku-Leistung W")] { if let v = try? smc.read(key: key) { printRow(v, note: note) } } // --- Vollständiger Dump --------------------------------------------------- if CommandLine.arguments.contains("--all") { section("ALLE KEYS") guard let total = try? smc.keyCount() else { print(" Key-Anzahl nicht lesbar") exit(0) } print(" (\(total) Keys insgesamt)\n") for i in 0..