Repository: johnno1962/InjectionNext Branch: main Commit: 7bae3044bc64 Files: 55 Total size: 337.7 KB Directory structure: gitextract_vqy0p27r/ ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── .gitmodules ├── .swiftpm/ │ └── xcode/ │ └── package.xcworkspace/ │ └── contents.xcworkspacedata ├── App/ │ ├── InjectionBundle/ │ │ ├── Info.plist │ │ └── InjectionBundle-Bridging-Header.h │ ├── InjectionNext/ │ │ ├── App.icns │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets/ │ │ │ └── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ ├── Base.lproj/ │ │ │ └── MainMenu.xib │ │ ├── ControlServer.swift │ │ ├── Defaults.swift │ │ ├── Experimental.swift │ │ ├── FrontendServer.swift │ │ ├── Info.plist │ │ ├── InjectionBusy.tif │ │ ├── InjectionError.tif │ │ ├── InjectionHybrid.swift │ │ ├── InjectionIdle.tif │ │ ├── InjectionNext-Bridging-Header.h │ │ ├── InjectionNext.entitlements │ │ ├── InjectionOK.tif │ │ ├── InjectionReady.tif │ │ ├── InjectionServer.swift │ │ ├── MonitorXcode.swift │ │ ├── NextCompiler.swift │ │ ├── build_bundle.sh │ │ ├── build_bundles.sh │ │ ├── copy_bundle.sh │ │ ├── main.m │ │ └── swift-frontend.sh │ ├── InjectionNext.xcodeproj/ │ │ ├── project.pbxproj │ │ ├── project.xcworkspace/ │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata/ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata/ │ │ └── xcschemes/ │ │ └── InjectionNext.xcscheme │ ├── LICENSE │ ├── NIMBLE.md │ ├── QUICK.md │ └── feedcommands/ │ └── main.mm ├── BAZEL.md ├── INTRO.md ├── LICENSE ├── Package.swift ├── README.md ├── Sources/ │ ├── InjectionNext/ │ │ └── InjectionNext.swift │ └── InjectionNextC/ │ ├── ClientBoot.mm │ ├── SimpleSocket.mm │ └── include/ │ ├── InjectionClient.h │ └── SimpleSocket.h ├── Tests/ │ └── InjectionNextTests/ │ └── InjectionNextTests.swift └── mcp-server/ ├── .gitignore ├── README.md ├── index.js └── package.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: johnno1962 patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] ================================================ FILE: .gitignore ================================================ # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## User settings xcuserdata/ ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) *.xcscmblueprint *.xccheckout ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) build/ DerivedData/ *.moved-aside *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 ## Obj-C/Swift specific *.hmap ## App packaging *.ipa *.dSYM.zip *.dSYM ## Playgrounds timeline.xctimeline playground.xcworkspace # Swift Package Manager # # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ # Package.pins Package.resolved # *.xcodeproj # # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata # hence it is not needed unless you have added a package configuration file to your project # .swiftpm .build/ # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # # Pods/ # # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts Carthage/Build/ # Accio dependency management Dependencies/ .accio/ # fastlane # # It is recommended to not store the screenshots in the git repo. # Instead, use fastlane to re-generate the screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control fastlane/report.xml fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output # Code Injection # # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ ================================================ FILE: .gitmodules ================================================ [submodule "fishhook"] path = fishhook url = https://github.com/johnno1962/fishhook [submodule "DLKit"] path = DLKit url = https://github.com/johnno1962/DLKit [submodule "InjectionLite"] path = InjectionLite url = https://github.com/johnno1962/InjectionLite [submodule "SwiftRegex5"] path = SwiftRegex5 url = https://github.com/johnno1962/SwiftRegex5 [submodule "SwiftTrace"] path = SwiftTrace url = https://github.com/johnno1962/SwiftTrace ================================================ FILE: .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: App/InjectionBundle/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleVersion 1 NSHumanReadableCopyright Copyright © 2017 John Holdsworth. All rights reserved. NSPrincipalClass ================================================ FILE: App/InjectionBundle/InjectionBundle-Bridging-Header.h ================================================ // // Use this file to import your target's public headers that you would like to expose to Swift. // #import // Xcode 16.3?? #import "SimpleSocket.h" #import "InjectionImplC.h" #import "InjectionClient.h" // InjectionBundle only #import "fishhook.h" #import "DLKitC.h" #import "SwiftTrace.h" ================================================ FILE: App/InjectionNext/AppDelegate.swift ================================================ // // AppDelegate.swift // InjectionNext // // Created by John Holdsworth on 06/11/2017. // Copyright © 2017 John Holdsworth. All rights reserved. // // $Id: //depot/HotReloading/Sources/injectiond/AppDelegate.swift#76 $ // // Implementation Toolbar menu "UI". // import Cocoa import Popen import SwiftRegex enum InjectionState: String { case ok = "OK" // Orange case idle = "Idle" // Blue case busy = "Busy" // Green case ready = "Ready" // Purple case error = "Error" // Yellow } @objc(AppDelegate) class AppDelegate: NSObject, NSApplicationDelegate { static var ui: AppDelegate! // Status menu @IBOutlet weak var statusMenu: NSMenu! @IBOutlet var statusItem: NSStatusItem! // Codesigning identity @IBOutlet var identityField: NSTextField! // Enable injection on devices @IBOutlet var deviceTesting: NSButton! // Testing libraries to link with @IBOutlet var librariesField: NSTextField! // Place to display last error that occured @IBOutlet var lastErrorField: NSTextView! // Restart XCode if crashed. @IBOutlet weak var launchXcodeItem: NSMenuItem! @IBOutlet weak var selectXcodeItem: NSMenuItem! @IBOutlet weak var restartDeviceItem: NSMenuItem! @IBOutlet weak var patchCompilerItem: NSMenuItem! @IBOutlet weak var enableDevicesItem: NSMenuItem! @IBOutlet weak var watchDirectoryItem: NSMenuItem! // Interface to app's persistent state. @objc let defaults = Defaults.userDefaults @IBOutlet weak var codeSignBox: NSComboBox! /// Code signing ID as parsed from the code signing box. If the content of the box is not /// parsable as SHA1 code signing ID, an empty string. var codeSigningID: String { codeSignBox.stringValue.containedSHA1 ?? "" } let userIDComboBoxDataSaver = UserIDComboBoxDataSaver() @objc func applicationDidFinishLaunching(_ aNotification: Notification) { // Insert code here to initialize your application Self.ui = self let appName = "InjectionNext" if Bundle.main.infoDictionary?["LSUIElement"] as? Bool != true { NSApp.mainMenu?.item(withTitle: "File")?.submenu = statusMenu } else { let statusBar = NSStatusBar.system statusItem = statusBar.statusItem(withLength: statusBar.thickness) statusItem.highlightMode = true statusItem.menu = statusMenu statusItem.isEnabled = true statusItem.title = appName setMenuIcon(.idle) } signal(SIGPIPE, { which in print(APP_PREFIX+"⚠️ SIGPIPE #\(which)\n" + Thread.callStackSymbols.map { var frame = $0 frame[#"(?:\S+\s+){3}(\S+)"#, 1] = { (groups: [String], stop) in return groups[1].swiftDemangle ?? groups[1] } return frame }.joined(separator: "\n")) }) if let quit = statusMenu.item(at: statusMenu.items.count-1) { quit.title = "Quit "+appName if let build = Bundle.main .infoDictionary?[kCFBundleVersionKey as String] { quit.toolTip = "Quit (build #\(build))" } } librariesField.stringValue = Defaults.deviceLibraries let enableDevicesSticky = false if !enableDevicesSticky || Defaults.codesigningIdentity == nil { enableDevicesItem.state = .on } deviceEnable(nil) if let xcodePath = NSRunningApplication .runningApplications(withBundleIdentifier: "com.apple.dt.Xcode") .first?.bundleURL?.path { if Defaults.xcodeDefault == nil { Defaults.xcodeDefault = xcodePath } selectXcodeItem.toolTip = Defaults.xcodePath if updatePatchUnpatch() == .unpatched && getenv(INJECTION_HIDE_XCODE_ALERT) == nil { InjectionServer.alert(""" Please quit Xcode and use this app to launch it (unless you are using a file watcher). """) } } setupCodeSigningComboBox() restartDeviceItem.state = Defaults.xcodeRestart ? .on : .off selectXcodeItem.toolTip = Defaults.xcodePath // #if DEBUG // if NSHomeDirectory() == "/Users/johnholdsworth", // let path = Bundle.main.path(forResource: "macOSInjection", ofType: "bundle"), // let bundle = Bundle(path: path), // bundle.load() { // } // #endif if let project = Defaults.projectPath { _ = MonitorXcode(args: " '\(project)'") } if Defaults.mcpServer { LogBuffer.shared = LogBuffer() ControlServer.start() } } func setMenuIcon(_ state: InjectionState) { DispatchQueue.main.async { let tiffName = "Injection"+state.rawValue if let path = Bundle.main.path(forResource: tiffName, ofType: "tif"), let image = NSImage(contentsOfFile: path) { // image.template = TRUE; self.statusItem.image = image self.statusItem.alternateImage = image } } } @IBAction func runXcode(_ sender: Any) { if MonitorXcode.runningXcode == nil { _ = MonitorXcode() } } @IBAction func selectXcode(_ sender: NSMenuItem) { let open = NSOpenPanel() open.prompt = "Select Xcode" open.directoryURL = URL(fileURLWithPath: Defaults.xcodePath) open.canChooseDirectories = false open.canChooseFiles = true if open.runModal() == .OK, let path = open.url?.path { selectXcodeItem.toolTip = path Defaults.xcodeDefault = path updatePatchUnpatch() if Defaults.xcodeRestart { runXcode(sender) } } } lazy var startHostLocatingServerOnce: () = { InjectionServer.multicastServe(HOTRELOADING_MULTICAST, port: HOTRELOADING_PORT) }() @IBAction func deviceEnable(_ sender: NSMenuItem?) { var openPort = "" if enableDevicesItem.state.toggle() == .on { if sender != nil { NSApplication.shared.activate(ignoringOtherApps: true) codeSignBox.window?.makeKeyAndOrderFront(sender) } _ = startHostLocatingServerOnce openPort = "*" } if sender != nil { InjectionServer.stopLastServer() } InjectionServer.startServer(openPort+INJECTION_ADDRESS) } @IBAction func testingEnable(_ sender: NSButton) { if sender.state == .on, let script = Bundle.main .url(forResource: "copy_bundle", withExtension: "sh") { let buildPhase = """ export RESOURCES="\(script.deletingLastPathComponent().path)" if [ -f "$RESOURCES/\(script.lastPathComponent)" ]; then "$RESOURCES/\(script.lastPathComponent)" fi """ let pasteBoard = NSPasteboard.general pasteBoard.declareTypes([.string], owner:nil) pasteBoard.setString(buildPhase, forType:.string) InjectionServer.error("Run Script, Build Phase to " + "copy testing libraries added to clipboard.") } } @IBAction func updateLibraries(_ sender: NSTextField) { Defaults.deviceLibraries = librariesField.stringValue } @IBAction func updateXcodeRestart(_ sender: NSMenuItem) { Defaults.xcodeRestart = sender.state.toggle() == .on } @IBAction func unhideSymbols(_ sender: NSMenuItem) { Unhider.startUnhide() } @IBAction func resetUnhiding(_ sender: NSMenuItem) { Unhider.unhiddens.removeAll() } @IBAction func showlastError(_ sender: NSMenuItem) { lastErrorField.string = NextCompiler.lastError ?? "No error." lastErrorField.window?.makeKeyAndOrderFront(sender) NSApplication.shared.activate(ignoringOtherApps: true) } func setupCodeSigningComboBox() { codeSignBox.removeAllItems() codeSignBox.addItems(withObjectValues: userIDComboBoxDataSaver.validCodeSigningIDs) if let savedID = userIDComboBoxDataSaver.savedID { codeSignBox.stringValue = savedID } else if let firstIdentity = userIDComboBoxDataSaver.validCodeSigningIDs.first { codeSignBox.stringValue = firstIdentity } else { codeSignBox.stringValue = "No valid code signing IDs found" } codeSignBox.target = userIDComboBoxDataSaver codeSignBox.action = #selector(UserIDComboBoxDataSaver.comboBoxValueDidChange(_:)) } } private extension String { /// Returns the sha1 string contained in this string, or `nil` if no such string is contained. var containedSHA1: String? { self[#"([0-9A-F]{40})"#] } } class UserIDComboBoxDataSaver { /// List of valid IDs. let validCodeSigningIDs: [String] = { var identities: [String] = [] let security = Topen(exec: "/usr/bin/security", arguments: ["find-identity", "-v", "-p", "codesigning"]) while let line = security.readLine() { let components = line.split(separator: ")", maxSplits: 1) if components.count >= 2 { let identity = components[1] identities.append(String(identity)) } } return identities }() /// Last savedID, if valid. `nil` otherwise. var savedID: String? { guard let savedValue = Defaults.codesigningIdentity else { return nil } return validCodeSigningIDs.first(where: { $0.containedSHA1 == savedValue.containedSHA1} ) } @objc func comboBoxValueDidChange(_ sender: NSComboBox) { if let newValueSHA1 = sender.stringValue.containedSHA1, validCodeSigningIDs.contains(where: { $0.containedSHA1 == newValueSHA1 }) { Defaults.codesigningIdentity = newValueSHA1 } else { NSLog("Selected value does not contain a valid ID") return } } } private extension NSControl.StateValue { @discardableResult mutating func toggle() -> Self { switch self { case .on: self = .off case .off: self = .on case .mixed: self = .mixed default: break } return self } } ================================================ FILE: App/InjectionNext/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "mac", "size" : "16x16", "scale" : "1x" }, { "idiom" : "mac", "size" : "16x16", "scale" : "2x" }, { "idiom" : "mac", "size" : "32x32", "scale" : "1x" }, { "idiom" : "mac", "size" : "32x32", "scale" : "2x" }, { "idiom" : "mac", "size" : "128x128", "scale" : "1x" }, { "idiom" : "mac", "size" : "128x128", "scale" : "2x" }, { "idiom" : "mac", "size" : "256x256", "scale" : "1x" }, { "idiom" : "mac", "size" : "256x256", "scale" : "2x" }, { "idiom" : "mac", "size" : "512x512", "scale" : "1x" }, { "idiom" : "mac", "size" : "512x512", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: App/InjectionNext/Base.lproj/MainMenu.xib ================================================ Default Left to Right Right to Left Default Left to Right Right to Left Item 1 Item 2 Item 3 ================================================ FILE: App/InjectionNext/ControlServer.swift ================================================ // // ControlServer.swift // InjectionNext // // Local TCP control server for MCP integration. // Listens on localhost:8919 for JSON commands and // maps them to existing AppDelegate actions. // import Cocoa // MARK: - Log Buffer class LogBuffer { static var shared: LogBuffer? struct Entry { let timestamp: TimeInterval let message: String let level: String } private let lock = NSLock() private var entries = [Entry]() private let maxEntries = 2000 func append(_ message: String, level: String = "info") { lock.lock() defer { lock.unlock() } entries.append(Entry( timestamp: Date().timeIntervalSince1970, message: message, level: level )) if entries.count > maxEntries { entries.removeFirst(entries.count - maxEntries) } } func get(since: TimeInterval = 0, limit: Int = 200) -> [[String: Any]] { lock.lock() defer { lock.unlock() } let filtered = entries.filter { $0.timestamp > since } let sliced = filtered.suffix(limit) return sliced.map { ["timestamp": $0.timestamp, "message": $0.message, "level": $0.level] } } func clear() { lock.lock() defer { lock.unlock() } entries.removeAll() } var count: Int { lock.lock() defer { lock.unlock() } return entries.count } } // MARK: - Control Server class ControlServer { static let port: UInt16 = 8919 static var shared: ControlServer? private var serverSocket: Int32 = -1 private let queue = DispatchQueue(label: "ControlServer", attributes: .concurrent) static func start() { guard shared == nil else { return } shared = ControlServer() shared?.listen() } private func listen() { queue.async { [weak self] in guard let self = self else { return } self.serverSocket = socket(AF_INET, SOCK_STREAM, 0) guard self.serverSocket >= 0 else { NSLog("\(APP_PREFIX)ControlServer: socket() failed") return } var reuse: Int32 = 1 setsockopt(self.serverSocket, SOL_SOCKET, SO_REUSEADDR, &reuse, socklen_t(MemoryLayout.size)) var addr = sockaddr_in() addr.sin_len = UInt8(MemoryLayout.size) addr.sin_family = sa_family_t(AF_INET) addr.sin_port = Self.port.bigEndian addr.sin_addr.s_addr = inet_addr("127.0.0.1") let bindResult = withUnsafePointer(to: &addr) { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { bind(self.serverSocket, $0, socklen_t(MemoryLayout.size)) } } guard bindResult == 0 else { NSLog("\(APP_PREFIX)ControlServer: bind() failed on port \(Self.port): \(String(cString: strerror(errno)))") close(self.serverSocket) return } guard Darwin.listen(self.serverSocket, 5) == 0 else { NSLog("\(APP_PREFIX)ControlServer: listen() failed") close(self.serverSocket) return } NSLog("\(APP_PREFIX)ControlServer: listening on localhost:\(Self.port)") while true { var clientAddr = sockaddr_in() var clientLen = socklen_t(MemoryLayout.size) let clientSocket = withUnsafeMutablePointer(to: &clientAddr) { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { accept(self.serverSocket, $0, &clientLen) } } guard clientSocket >= 0 else { continue } self.queue.async { self.handleClient(clientSocket) } } } } private func handleClient(_ sock: Int32) { defer { close(sock) } let maxRequestSize = 64 * 1024 var data = Data() var buf = [UInt8](repeating: 0, count: 4096) while true { let n = recv(sock, &buf, buf.count, 0) guard n > 0 else { break } data.append(contentsOf: buf[0.. maxRequestSize { sendResponse(sock, success: false, error: "Request too large") return } } guard !data.isEmpty, let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], let action = json["action"] as? String else { sendResponse(sock, success: false, error: "Invalid JSON or missing 'action'") return } let result = executeAction(action, params: json) sendResponse(sock, success: result.success, data: result.data, error: result.error) } private func sendResponse(_ sock: Int32, success: Bool, data: [String: Any]? = nil, error: String? = nil) { var response: [String: Any] = ["success": success] if let error = error { response["error"] = error } if let data = data { response["data"] = data } guard let jsonData = try? JSONSerialization.data(withJSONObject: response), let jsonStr = String(data: jsonData, encoding: .utf8) else { return } let line = jsonStr + "\n" _ = line.withCString { ptr in send(sock, ptr, strlen(ptr), 0) } } struct ActionResult { let success: Bool let data: [String: Any]? let error: String? static func ok(_ data: [String: Any]? = nil) -> ActionResult { ActionResult(success: true, data: data, error: nil) } static func fail(_ error: String) -> ActionResult { ActionResult(success: false, data: nil, error: error) } } private func executeAction(_ action: String, params: [String: Any]) -> ActionResult { switch action { case "status": return getStatus() case "watch_project": guard let path = params["path"] as? String else { return .fail("Missing 'path' parameter") } return watchProject(path: path) case "stop_watching": return stopWatching() case "launch_xcode": return launchXcode() case "intercept_compiler": return interceptCompiler() case "enable_devices": let enable = params["enable"] as? Bool ?? true return enableDevices(enable: enable) case "unhide_symbols": return unhideSymbols() case "get_last_error": return getLastError() case "prepare_swiftui_source": return prepareSwiftUISource() case "prepare_swiftui_project": return prepareSwiftUIProject() case "set_xcode_path": guard let path = params["path"] as? String else { return .fail("Missing 'path' parameter") } return setXcodePath(path: path) case "get_logs": let since = params["since"] as? TimeInterval ?? 0 let limit = max(0, min(params["limit"] as? Int ?? 200, 500)) return getLogs(since: since, limit: limit) case "clear_logs": return clearLogs() default: return .fail("Unknown action: \(action)") } } // MARK: - Actions private func getStatus() -> ActionResult { var result = [String: Any]() DispatchQueue.main.sync { let delegate = AppDelegate.ui! result["xcode_running"] = MonitorXcode.runningXcode != nil result["xcode_path"] = Defaults.xcodePath result["compiler_intercepted"] = delegate.updatePatchUnpatch() == .patched result["devices_enabled"] = delegate.enableDevicesItem.state == .on result["watching_directories"] = Array(AppDelegate.watchers.keys) result["has_connected_client"] = InjectionServer.currentClient != nil result["auto_restart_xcode"] = Defaults.xcodeRestart result["last_error"] = NextCompiler.lastError } return .ok(result) } private func watchProject(path: String) -> ActionResult { guard FileManager.default.fileExists(atPath: path) else { return .fail("Path does not exist: \(path)") } DispatchQueue.main.sync { Reloader.xcodeDev = Defaults.xcodePath + "/Contents/Developer" AppDelegate.ui.watch(path: path) } return .ok(["watching": path]) } private func stopWatching() -> ActionResult { DispatchQueue.main.sync { AppDelegate.watchers.removeAll() AppDelegate.lastWatched = nil AppDelegate.ui.watchDirectoryItem.state = .off } return .ok() } private func launchXcode() -> ActionResult { DispatchQueue.main.sync { if MonitorXcode.runningXcode == nil { _ = MonitorXcode() } } return .ok(["xcode_path": Defaults.xcodePath]) } private func interceptCompiler() -> ActionResult { var state = "" DispatchQueue.main.sync { let delegate = AppDelegate.ui! let currentState = delegate.updatePatchUnpatch() state = currentState == .patched ? "patched" : "unpatched" } return .ok(["compiler_state": state, "note": "Use Xcode UI to toggle interception (requires user confirmation alert)"]) } private func enableDevices(enable: Bool) -> ActionResult { DispatchQueue.main.sync { let delegate = AppDelegate.ui! let currentlyEnabled = delegate.enableDevicesItem.state == .on if enable != currentlyEnabled { delegate.deviceEnable(delegate.enableDevicesItem) } } return .ok(["devices_enabled": enable]) } private func unhideSymbols() -> ActionResult { Unhider.startUnhide() return .ok() } private func getLastError() -> ActionResult { let error = NextCompiler.lastError ?? "No error." return .ok(["error": error]) } private func prepareSwiftUISource() -> ActionResult { guard let lastSource = NextCompiler.lastSource else { return .fail("No source file currently being edited") } DispatchQueue.main.sync { AppDelegate.ui.prepareSwiftUI(source: lastSource) } return .ok(["source": lastSource]) } private func prepareSwiftUIProject() -> ActionResult { DispatchQueue.main.sync { AppDelegate.ui.prepareProject(AppDelegate.ui.patchCompilerItem) } return .ok() } private func setXcodePath(path: String) -> ActionResult { guard FileManager.default.fileExists(atPath: path) else { return .fail("Xcode not found at: \(path)") } DispatchQueue.main.sync { Defaults.xcodeDefault = path AppDelegate.ui.selectXcodeItem.toolTip = path AppDelegate.ui.updatePatchUnpatch() } return .ok(["xcode_path": path]) } private func getLogs(since: TimeInterval, limit: Int) -> ActionResult { let logs = LogBuffer.shared?.get(since: since, limit: limit) return .ok(["logs": logs ?? [], "count": LogBuffer.shared?.count ?? 0]) } private func clearLogs() -> ActionResult { LogBuffer.shared?.clear() return .ok() } } ================================================ FILE: App/InjectionNext/Defaults.swift ================================================ // // Defaults.swift // InjectionNext // // Created by John Holdsworth on 24/07/2024. // Copyright © 2024 John Holdsworth. All rights reserved. // import Foundation struct Defaults { /// App deauflts for persistent state static let userDefaults = UserDefaults.standard static let xcodePathDefault = "XcodePath" static let librariesDefault = "libraries" static let codesigningDefault = "codesigningIdentity" private static let xcodeRestartDefault = "xcodeRestartDefault" static var xcodePath: String { xcodeDefault ?? "/Applications/Xcode.app" } static var xcodeDefault: String? { get { userDefaults.string(forKey: xcodePathDefault) } set { userDefaults.setValue(newValue, forKey: xcodePathDefault) } } static var deviceLibraries: String { get { userDefaults.string(forKey: librariesDefault) ?? "-framework XCTest -lXCTestSwiftSupport" } set { userDefaults.setValue(newValue, forKey: librariesDefault) } } static var codesigningIdentity: String? { get { userDefaults.string(forKey: codesigningDefault) } set { userDefaults.setValue(newValue, forKey: codesigningDefault) } } static var xcodeRestart: Bool { get { if userDefaults.value(forKey: xcodeRestartDefault) == nil { return true } return userDefaults.bool(forKey: xcodeRestartDefault) } set { userDefaults.setValue(newValue, forKey: xcodeRestartDefault) } } static let projectPathDefault = "projectPath" static var projectPath: String? { get { userDefaults.string(forKey: projectPathDefault) } } static var mcpServer = userDefaults.bool(forKey: "mcpServer") } ================================================ FILE: App/InjectionNext/Experimental.swift ================================================ // // Experimental.swift // InjectionIII // // Created by User on 20/10/2020. // Copyright © 2020 John Holdsworth. All rights reserved. // // $Id: //depot/HotReloading/Sources/injectiond/Experimental.swift#35 $ // // Some regular expressions to automatically prepare SwiftUI sources. // import Cocoa import SwiftRegex extension AppDelegate { /// Prepare the SwiftUI source file currently being edited for injection. @IBAction func prepareSource(_ sender: NSMenuItem) { if let lastSource = NextCompiler.lastSource { prepareSwiftUI(source: lastSource) } } /// Prepare all sources in the current target for injection. @IBAction func prepareProject(_ sender: NSMenuItem) { var changes = 0, edited = 0 for source in ((MonitorXcode.runningXcode != nil ? MonitorXcode.recompiler.lastCompilation : nil) ?? FrontendServer.frontendRecompiler().lastCompilation)? .swiftFiles.components(separatedBy: "\n").dropLast() ?? Array(Recompiler.workspaceCache.keys) { InjectionHybrid.lastInjected[source] = Date.timeIntervalSinceReferenceDate prepareSwiftUI(source: source, changes: &changes) edited += 1 } let s = changes == 1 ? "" : "s" InjectionServer.error("\(changes) automatic edit\(s) made to \(edited) files") } } ================================================ FILE: App/InjectionNext/FrontendServer.swift ================================================ // // FrontendServer.swift // InjectionNext // // Created by John Holdsworth on 23/02/2025. // Copyright © 2025 John Holdsworth. All rights reserved. // // Code related to "Intercepting" version where the binary // swift-frontend is replaced by a script which feeds all // compilation commands to the app where they can be reused // when a file is injected to recompile individual Swift files. // import Cocoa import Popen import Fortify #if INJECTION_III_APP struct Unhider { static var packageFrameworks: String? } #endif extension NextCompiler { func writeCache() { FrontendServer.writeCache(for: self.name, recompiler: self) } } class FrontendServer: SimpleSocket { enum State: String { case unpatched = "Intercept Compiler" case patched = "Unpatch Compiler" } /// Paths to unpatched/patched swift-frontend binary/script in toolchain. static let frontendQueue = DispatchQueue(label: "InjectionCapture") static var binURL: URL { URL(fileURLWithPath: Defaults.xcodePath + "/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin") } static var unpatchedURL: URL { binURL.appendingPathComponent("swift-frontend") } static var patched: String { unpatchedURL.path + ".save" } static var patchedURL: URL { URL(fileURLWithPath: patched) } /// Path to swift-frontend last logged. static var loggedFrontend: String? /// Start server for command logging. static var startOnce: Void = { FrontendServer.startServer(COMMANDS_PORT) }() static var clientPlatform: String { InjectionServer.currentClient?.platform ?? "iPhoneSimulator" } static func cacheURL(platform: String) -> URL { return URL(fileURLWithPath: Reloader.tmpbase+"_\(platform)_builds.json") } static private var recompilersLock = os_unfair_lock() static private var recompilers = [String: NextCompiler]() static func frontendRecompiler(for platform: String = clientPlatform) -> NextCompiler { os_unfair_lock_lock(&recompilersLock) defer { os_unfair_lock_unlock(&recompilersLock) } if let recompiler = recompilers[platform] { return recompiler } let recompiler = NextCompiler(name: platform) do { let compressed = cacheURL(platform: platform).path+".gz" if Fstat(path: compressed)?.st_size ?? 0 != 0, let stream = Popen(cmd: "gunzip <"+compressed), let cached = stream.readAll().data(using: .utf8) { let stored = try JSONDecoder().decode( [String: NextCompiler.Compilation].self, from: cached) for source in stored.keys.sorted() { guard let compile = stored[source] else { continue } recompiler.store(compilation: compile, for: source) } recompiler.modified = false print("Loaded \(recompiler.compilations.count) \(platform) commands.") } } catch { InjectionServer.error("Unable to read commands cache: \(error).") } recompilers[platform] = recompiler return recompiler } static func writeCache(for platform: String, recompiler: NextCompiler? = nil) { let recompiler = recompiler ?? frontendRecompiler(for: platform) do { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted let cache = cacheURL(platform: platform) let commands = recompiler.compilations try encoder.encode(commands).write(to: cache, options: .atomic) if let error = Popen.system("gzip -f "+cache.path, errors: true) { InjectionServer.error("Unable to zip commands cache: \(error)") } else { print("Cached \(commands.count) \(platform) commands") } recompiler.modified = false } catch { InjectionServer.error("Unable to write commands cache: \(error)") } } func validateConnection() -> Bool { return readInt() == COMMANDS_VERSION && readString() == NSHomeDirectory() } override func run() { Self.frontendQueue.async { do { try Fortify.protect { () -> () in guard self.validateConnection(), let vers = self.readString(), vers == "1.0" || vers == "2.0" else { return Self.frontendRecompiler() .error("Unpatch then repatch compiler to update script version") } try Self.processFrontendCommandFrom(feed: self) } } catch { Self.error("Feed error: \(error)") } } } class func processFrontendCommandFrom(feed: SimpleSocket) throws { guard var projectRoot = feed.readString(), let frontendPath = feed.readString(), frontendPath.hasSuffix(".save"), feed.readString() == "-frontend" && feed.readString() == "-c" else { return } // swift-frontend.sh 2.0+ capture environment var env: String? if let pwd: String = projectRoot["PWD=(.*)\n"] ?? projectRoot["HOME=(.*)\n"] { env = projectRoot projectRoot = pwd } var parser = CompilationArgParser() while let arg = feed.readString() { if arg.hasPrefix("llvmcas://") { return } parser.process(arg: arg, next: feed.readString) } let update = NextCompiler.Compilation(arguments: parser.args, swiftFiles: parser.swiftFiles, workingDir: projectRoot, env: env) DispatchQueue.main.async { if !projectRoot.hasSuffix(".xcodeproj") && projectRoot != "/" && // MonitorXcode.runningXcode == nil && AppDelegate.alreadyWatching(projectRoot) == nil { let open = NSOpenPanel() // open.titleVisibility = .visible // open.title = "InjectionNext: add directory" open.prompt = "InjectionNext - Watch Directory?" open.directoryURL = URL(fileURLWithPath: projectRoot) open.canChooseDirectories = true open.canChooseFiles = false if open.runModal() == .OK, let url = open.url { AppDelegate.ui.watch(path: url.path) } } } NextCompiler.compileQueue.async { let recompiler = Self.frontendRecompiler(for: parser.platform) loggedFrontend = frontendPath for source in parser.primaries { #if !INJECTION_III_APP // Don't update compilations while connected if InjectionServer.currentClient != nil && recompiler.canCompile(source: source, for: parser.platform) { continue } #endif print("Updating \(parser.args.count) args for \(parser.platform)/" + URL(fileURLWithPath: source).lastPathComponent) recompiler.store(compilation: update, for: source) } } } /// Argument parser shared by FrontendServer and MonitorXcode to accumulate /// compiler arguments into a NextCompiler.Compilation. struct CompilationArgParser { var swiftFiles = "" var args = [String]() var primaries = [String]() var platform = "iPhoneSimulator" var workingDir = "/tmp" var swiftFileCount = 0 lazy var productDirSuffix = "-"+clientPlatform.lowercased() /// Process one argument, calling `next()` to consume the following /// token whenever the argument takes a value. mutating func process(arg: String, next: () -> String?) { switch arg { case "-filelist": if let filelist = next(), let files = try? String(contentsOfFile: filelist, encoding: .utf8) { swiftFiles += files } case "-primary-file": if let source = next(), !source.isEmpty { primaries.append(source) if strstr(swiftFiles, source) == nil { swiftFiles += source+"\n" } } case "-o": if let object = next(), !object.isEmpty && Unhider.packageFrameworks == nil { var url = URL(fileURLWithPath: object) for _ in 1...4 { url.deleteLastPathComponent() } Unhider.packageFrameworks = url.path } default: if let sdkPlatform: String = arg[#"/([A-Za-z]+)[\d\.]+\.sdk$"#] { platform = sdkPlatform } else if args.last == "-F" { if arg.hasSuffix("/PackageFrameworks") { Unhider.packageFrameworks = arg } else if Unhider.packageFrameworks == nil, arg.hasSuffix(productDirSuffix) { Unhider.packageFrameworks = arg+"/PackageFrameworks" } } let pathArgFlags: Set = ["-F", "-I", "-iquote", "-isystem"] let isPathArgValue = pathArgFlags.contains(args.last ?? "") || arg.hasPrefix("-I") || arg.hasPrefix("-F") if arg.hasSuffix(".swift") && !isPathArgValue { swiftFiles += arg+"\n" swiftFileCount += 1 } else if arg[Reloader.optionsToRemove] { _ = next() } else if !arg[ "-validate-clang-modules-once|-frontend-parseable-output"] { args.append(arg) } } } } } extension AppDelegate { @IBAction func patchCompiler(_ sender: NSMenuItem) { let fm = FileManager.default do { let linksToMove = ["swift", "swiftc", "swift-symbolgraph-extract", "swift-api-digester", "swift-cache-tool"] if updatePatchUnpatch() == .unpatched { if !fm.fileExists(atPath: FrontendServer.patched), let feeder = Bundle.main .url(forResource: "swift-frontend", withExtension: "sh") { let alert: NSAlert = NSAlert() alert.alertStyle = .warning alert.messageText = APP_NAME alert.informativeText = """ The Swift compiler of your current toolchain \ \(FrontendServer.unpatchedURL.path) will be \ replaced by a script that calls the compiler \ and captures all compilation commands. Use menu \ item "Unpatch Compiler" to revert this change. """ alert.addButton(withTitle: "OK") alert.addButton(withTitle: "Cancel") if alert.runModal() != .alertFirstButtonReturn { return } try fm.moveItem(at: FrontendServer.unpatchedURL, to: FrontendServer.patchedURL) try fm.copyItem(at: feeder, to: FrontendServer.unpatchedURL) for binary in linksToMove { let link = FrontendServer.binURL .appendingPathComponent(binary) try fm.removeItem(at: link) symlink("swift-frontend.save", link.path) } } } else if fm.fileExists(atPath: FrontendServer.patched) { try? fm.removeItem(at: FrontendServer.unpatchedURL) try fm.moveItem(at: FrontendServer.patchedURL, to: FrontendServer.unpatchedURL) for binary in linksToMove { let link = FrontendServer.binURL .appendingPathComponent(binary) try fm.removeItem(at: link) symlink("swift-frontend", link.path) } FrontendServer.loggedFrontend = nil } } catch { let chmod = FrontendServer.unpatchedURL.deletingLastPathComponent() InjectionServer.error("Patching error: \(error). " + "Is the directory \(chmod.path) writable?") } updatePatchUnpatch() } @discardableResult func updatePatchUnpatch() -> FrontendServer.State { let state = FileManager.default .fileExists(atPath: FrontendServer.patched) ? FrontendServer.State.patched : .unpatched DispatchQueue.main.async { self.patchCompilerItem?.title = state.rawValue if state == .patched { _ = FrontendServer.startOnce } } return state } /// Shared regular expresssions to patch .enableInjection() and @ObserveInject into a source func prepareSwiftUI(source: String, changes: UnsafeMutablePointer? = nil) { let fileURL = URL(fileURLWithPath: source) guard let original = try? String(contentsOf: fileURL) else { return } var patched = original, before = changes?.pointee patched[#""" ^((\s+)(public )?(var body:|func body\([^)]*\) -\>) some View \{\n\# (\2(?! (if|switch|ForEach) )\s+(?!\.enableInjection)\S.*\n|(\s*|#.+)\n)+)(?() init() { cancellable = NotificationCenter.default.publisher(for: Notification.Name("INJECTION_BUNDLE_NOTIFICATION")) .sink { [weak self] change in self?.injectionNumber += 1 self?.publisher.send() } } } extension SwiftUI.View { public func eraseToAnyView() -> some SwiftUI.View { return AnyView(self) } public func enableInjection() -> some SwiftUI.View { return eraseToAnyView() } public func onInjection(bumpState: @escaping () -> ()) -> some SwiftUI.View { return self .onReceive(InjectionObserver.shared.publisher, perform: bumpState) .eraseToAnyView() } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @propertyWrapper public struct ObserveInjection: DynamicProperty { @ObservedObject private var iO = InjectionObserver.shared public init() {} public private(set) var wrappedValue: Int { get {0} set {} } } #else extension SwiftUI.View { @inline(__always) public func eraseToAnyView() -> some SwiftUI.View { return self } @inline(__always) public func enableInjection() -> some SwiftUI.View { return self } @inline(__always) public func onInjection(bumpState: @escaping () -> ()) -> some SwiftUI.View { return self } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @propertyWrapper public struct ObserveInjection { public init() {} public private(set) var wrappedValue: Int { get {0} set {} } } #endif #endif """ } if patched != original { do { try patched.write(to: fileURL, atomically: true, encoding: .utf8) } catch { InjectionServer.error("Could not save \(source): \(error)") } } } } ================================================ FILE: App/InjectionNext/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile App.icns CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion 14094 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) LSUIElement NSHumanReadableCopyright Copyright © 2017-20 John Holdsworth. All rights reserved. NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: App/InjectionNext/InjectionHybrid.swift ================================================ // // InjectionHybrid.swift // InjectionNext // // Created by John Holdsworth on 09/11/2024. // Copyright © 2024 John Holdsworth. All rights reserved. // // Provide file watcher/log parser fallback // for use outside Xcode (e.g. Cursor/VSCode) // Also uses FileWatcher for operation when // swift-frontend has been replaced by a // script to capture compiler invocations. // import Cocoa extension AppDelegate { static var watchers = [String: InjectionHybrid]() static var lastWatched: String? @IBAction func watchProject(_ sender: NSMenuItem) { let open = NSOpenPanel() open.prompt = "Select Project Directory" open.canChooseDirectories = true open.canChooseFiles = false // open.showsHiddenFiles = TRUE; if open.runModal() == .OK, let url = open.url { Reloader.xcodeDev = Defaults.xcodePath+"/Contents/Developer" watch(path: url.path) } else { Self.watchers.removeAll() Self.lastWatched = nil } } func watch(path: String) { guard Self.alreadyWatching(path) == nil else { return } GitIgnoreParser.monitor(directory: path) Self.watchers[path] = InjectionHybrid(watching: path) Self.lastWatched = path watchDirectoryItem.state = Self.watchers.isEmpty ? .off : .on } static func alreadyWatching(_ projectRoot: String) -> String? { return Self.watchers[projectRoot] != nil ? projectRoot : watchers.keys.first { projectRoot.hasPrefix($0+"/") } } static func restartLastWatcher() { DispatchQueue.main.async { lastWatched.flatMap { watchers[$0]?.watcher?.restart() } } } } class InjectionHybrid: InjectionBase { /// Last Injected for deduplication static var lastInjected = [String: TimeInterval]() /// Last queue of file changes static var pendingFilesChanged = [String]() /// Repository locked state - stops processing until app reconnects static var isRepositoryLocked = false /// Path to detected git lock file - used to check if git operation still active static var gitLockPath: String? /// InjectionNext compiler that uses InjectionLite log parser var logParsingCompiler: NextCompiler = HybridCompiler(name: "BuildLogs") /// Minimum seconds between injections let minInterval = 1.0 init(watching path: String) { // FileWatcher compatibility let watchPaths = (getenv(INJECTION_DIRECTORIES) == nil ? NSHomeDirectory()+"/Library/Developer," : "") + path setenv(INJECTION_DIRECTORIES, watchPaths, 1) Reloader.injectionQueue = .main super.init() // Extend FileWatcher pattern to detect git lock files FileWatcher.INJECTABLE_PATTERN = try! NSRegularExpression( pattern: #"[^~]\.(mm?|cpp|cc|swift|lock|o)$"#) } /// Called from file watcher when file is edited. override func inject(source: String) { guard MonitorXcode.runningXcode == nil else { return } // Detect git lock files - record path for later checking if source.hasSuffix(".lock") && source.contains("/.git/") { Self.gitLockPath = source return } // Skip processing if repository is already locked if Self.isRepositoryLocked { log(""" File processing stopped due to git lock. \ Please relaunch your app to resume injection. """) return } // Check if source file is changing while git lock still exists if let lockPath = Self.gitLockPath { if FileManager.default.fileExists(atPath: lockPath) { // Source files changing while git lock exists = branch switch/merge/rebase Self.isRepositoryLocked = true Self.pendingFilesChanged.removeAll() Self.gitLockPath = nil log(""" Git operation in progress (branch switch/merge/rebase detected). \ File processing stopped. Please relaunch your app to resume injection. """) return } else { // Lock file is gone - was probably just a commit Self.gitLockPath = nil } } let now = Date.timeIntervalSinceReferenceDate guard !AppDelegate.watchers.isEmpty, now - ( Self.lastInjected[source] ?? 0.0) > minInterval else { return } Self.lastInjected[source] = now Self.pendingFilesChanged.append(source) NextCompiler.compileQueue.async { self.injectNext() } } func injectNext() { guard let source = (DispatchQueue.main.sync { () -> String? in guard let source = Self.pendingFilesChanged.first else { return nil } Self.pendingFilesChanged.removeAll(where: { $0 == source }) if !Self.pendingFilesChanged.isEmpty { NextCompiler.compileQueue.async { self.injectNext() } } return source }) else { return } autoreleasepool { var recompiler = MonitorXcode.recompiler let platform = FrontendServer.clientPlatform if recompiler.canCompile(source: source, for: platform), recompiler.inject(source: source) { return } recompiler = logParsingCompiler if source.hasSuffix(".swift") && AppDelegate.ui.updatePatchUnpatch() == .patched { let proxyCompiler = FrontendServer.frontendRecompiler(for: platform) if proxyCompiler.canCompile(source: source) { recompiler = proxyCompiler } } if let why = GitIgnoreParser.shouldExclude(file: source) { log("Excluded \(source) as \(why)") } else if !recompiler.inject(source: source) { recompiler.pendingSource = source } } } } class HybridCompiler: NextCompiler { /// Legacy log parsing version of recomilation static var liteRecompiler = Recompiler() override func recompile(source: String, platform: String) -> String? { let oldCache = Reloader.cacheFile Reloader.sdk = platform // Select commands cache file. if oldCache != Reloader.cacheFile { Self.liteRecompiler = Recompiler() } return Self.liteRecompiler.recompile(source: source, platformFilter: "SDKs/"+platform, dylink: false) } override func link(object: String, dylib: String, arch: String) -> (String, Double)? { return super.link(object: object, dylib: dylib, arch: arch) ?? Self.liteRecompiler.linkingFailed() } } ================================================ FILE: App/InjectionNext/InjectionNext-Bridging-Header.h ================================================ // // Use this file to import your target's public headers that you would like to expose to Swift. // #import "SimpleSocket.h" #import "InjectionImplC.h" #import "InjectionClient.h" #import "/tmp/InjectionNextSalt.h" ================================================ FILE: App/InjectionNext/InjectionNext.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.files.user-selected.read-only com.apple.security.network.server com.apple.security.print com.apple.security.files.bookmarks.app-scope ================================================ FILE: App/InjectionNext/InjectionServer.swift ================================================ // // InjectionServer.swift // InjectionNext // // Created by John H on 30/05/2024. // Copyright © 2024 John Holdsworth. All rights reserved. // // Subclass of SimpleSocket to receive connection from // user apps using the InjectionNext Swift Package. An // incoming connection will enter runInBackground() on // a background thread. Validiates the connection and // forwards "commands" to the client app to load dynamic // libraries and inject them etc. Also receives feeds // of compilation commands from swift-frontend.sh. // import Cocoa import Fortify import Popen class InjectionServer: SimpleSocket { /// So commands from differnt threads don't get mixed up static let clientQueue = DispatchQueue(label: "InjectionCommand") static private var connected = [InjectionServer]() /// All access to `connected` serialised through clientQueue. static var currentClients: [InjectionServer?] { return clientQueue.sync { connected.isEmpty ? [nil] : connected } } /// Current connection to client app. There can be only one. static var currentClient: InjectionServer? { currentClients.last ?? nil } static var lastAlert: NSAlert? /// Sorted last symbols exported by source. var exports = [String: [String]]() /// Keeps dynamic library file names unique. var injectionNumber = 0 /// Some defaults var platform = "iPhoneSimulator" var arch = "arm64" var tmpPath = "/unset" class func alert(_ msg: String) { NSLog("\(APP_PREFIX)\(APP_NAME) \(msg)") LogBuffer.shared?.append("\(APP_NAME) \(msg)", level: "alert") lastAlert = NSAlert() lastAlert?.messageText = "\(self)" lastAlert?.informativeText = msg lastAlert?.alertStyle = .warning lastAlert?.addButton(withTitle: "OK") _ = lastAlert?.runModal() } /// Pops up an alert panel for networking @discardableResult override public class func error(_ message: String) -> Int32 { let msg = String(format:message, strerror(errno)) LogBuffer.shared?.append(msg, level: "error") DispatchQueue.main.async { alert(msg) } return -1 } // Send command to client app func sendCommand(_ command: InjectionCommand, with string: String?) { Self.clientQueue.async { _ = self.writeCommand(command.rawValue, with: string) } } // Write message into Xcode console of client app. open func log(_ msg: String) { NSLog("\(APP_PREFIX)\(APP_NAME) \(msg)") LogBuffer.shared?.append(msg, level: "info") sendCommand(.log, with: APP_PREFIX+msg) } open func error(_ msg: String) { log("⚠️ "+msg) } lazy var copyPlugIns: () = { let pattern = "/tmp/InjectionNext.PlugIns/*.xctest" if platform == "iPhoneOS" && isLocalClient { if let errors = Popen.system(""" rm -rf "\(tmpPath)"/*.xctest; \ rsync -a \(pattern) "\(tmpPath)" """, errors: true) { error("Copy *.xctest failed: "+errors) } return } guard let plugins = Glob(pattern: pattern) else { return } for plugin in plugins { writeCommand(InjectionCommand.log.rawValue, with: APP_PREFIX+"Sending "+plugin) let url = URL(fileURLWithPath: plugin) let dest = tmpPath+"/"+url.lastPathComponent writeCommand(InjectionCommand.sendFile.rawValue, with: dest+"/") writeCommand(InjectionCommand.sendFile.rawValue, with: dest+"/_CodeSignature/") for file in [url.deletingPathExtension().lastPathComponent, "Info.plist", "/_CodeSignature/CodeResources"] { writeCommand(InjectionCommand.sendFile.rawValue, with: dest+"/"+file) sendFile(url.appendingPathComponent(file).path) } } }() // Simple validation to weed out invalid connections func validateConnection() -> Bool { guard readInt() == INJECTION_VERSION, let injectionKey = readString() else { return false } guard injectionKey.hasPrefix(NSHomeDirectory()) else { error("Invalid INJECTION_KEY: "+injectionKey) return false } return true } // On a new connection starts executing here override func runInBackground() { do { try Fortify.protect { guard validateConnection() else { sendCommand(.invalid, with: nil) error("Connection did not validate.") return } DispatchQueue.main.async { InjectionHybrid.pendingFilesChanged.removeAll() // Reset repository locked state on app reconnect (relaunch) if InjectionHybrid.isRepositoryLocked { InjectionHybrid.isRepositoryLocked = false InjectionHybrid.gitLockPath = nil self.log("Repository lock cleared - injection resumed") } } AppDelegate.ui.setMenuIcon(.ok) processResponses() AppDelegate.ui.setMenuIcon(MonitorXcode .runningXcode != nil ? .ready : .idle) } } catch { self.error("\(self) error \(error)") } Self.clientQueue.sync { Self.connected.removeAll { $0 === self } } // flush messages and de-register } func processResponses() { sendCommand(.xcodePath, with: Defaults.xcodePath) AppDelegate.restartLastWatcher() while true { let responseInt = readInt() guard let response = InjectionResponse(rawValue: responseInt) else { error("Invalid responseInt: \(responseInt)") break } switch response { case .platform: if let platform = readString(), let arch = readString() { log("Platform connected: "+platform) self.platform = platform Reloader.arch = arch self.arch = arch } else { error("**** Bad platform ****") return } case .tmpPath: if let tmpPath = readString() { print("Tmp path: "+tmpPath) if tmpPath.contains("/Xcode/UserData/Previews/") { return } self.tmpPath = tmpPath self.tmpPath[#"/$"#] = "" // strip trailing slash Self.clientQueue.async { Self.connected.append(self) } } else { error("**** Bad tmp ****") } if MonitorXcode.runningXcode == nil && AppDelegate.watchers.isEmpty && AppDelegate.ui.updatePatchUnpatch() == .unpatched { error(""" Xcode not launched via app. Injection will not be possible \ unless you file-watch a project and Xcode logs are available. \ You can add an env var INJECTION_PROJECT_ROOT to your scheme \ with value $(SRCROOT) to auto file-watch this project. """) } if !AppDelegate.watchers.isEmpty { log("Watching directory: " + AppDelegate.watchers.keys.joined(separator: ", ")) } case .projectRoot: if let projectRoot = readString() { log("Auto-watching project: \(projectRoot)") DispatchQueue.main.sync { AppDelegate.ui.watch(path: projectRoot) Self.lastAlert?.buttons.last?.performClick(self) Self.lastAlert = nil } } else { error("**** Bad root ****") } case .executable: if let executable = readString() { Reloader.appName = URL(fileURLWithPath: executable).lastPathComponent } case .detail: if let detail = readString() { setenv(INJECTION_DETAIL, detail, 1) } case .bazelTarget: if let target = readString() { log("Received Bazel target: \(target)") BazelActionQueryHandler.cachedAppTarget = target // Set environment variable so Bazel parsers can use this target setenv(INJECTION_BAZEL_TARGET, target, 1) } else { error("**** Bad Bazel target ****") } case .injected: AppDelegate.ui.setMenuIcon(.ok) case .failed: AppDelegate.ui.setMenuIcon(.error) case .unhide: log("Injection could not load. If this was due to a default " + "argument. Select the app's menu item \"Unhide Symbols\".") case .exit: log("**** client disconnected ****") return @unknown default: Self.error("**** @unknown response case \(responseInt) ****") return } } } } ================================================ FILE: App/InjectionNext/MonitorXcode.swift ================================================ // // RunXcode.swift // InjectionNext // // Created by John H on 30/05/2024. // Copyright © 2024 John Holdsworth. All rights reserved. // // Launches Xcode and monitors console output for SourceKit // logging messages which reveal the compiler arguments to // use for the file currently being edited (Used for real // time syntax and error checking by the SourceKit daemon). // Captures this information and passes it onto a Recompiler // instance to process and inject when edited file is saved. // import Foundation import SwiftRegex import Fortify import Popen class MonitorXcode { // Currently running Xcode process static weak var runningXcode: MonitorXcode? // The service to recompile and inject a source file. static var recompiler = FrontendServer.frontendRecompiler(for: "Xcode") func debug(_ what: Any..., separator: String = " ") { #if DEBUG print(what, separator: separator) #endif } init(args: String = "") { var args = args #if DEBUG args += " | tee \(Reloader.tmpbase).log" #endif if !FileManager.default.fileExists(atPath: Defaults.xcodePath) { InjectionServer.error(""" No valid Xcode at path: \(Defaults.xcodePath) Use menu item "Select Xcode" to select a valid path. """) } else if let xcodeStdout = Popen(cmd: """ export SOURCEKIT_LOGGING=1 export RUNNING_VIA_INJECTION_NEXT=1 '\(Defaults.xcodePath)/Contents/MacOS/Xcode' 2>&1 \(args) """) { Self.runningXcode = self AppDelegate.ui.launchXcodeItem.state = .on DispatchQueue.global().async { while true { do { try Fortify.protect { AppDelegate.ui.setMenuIcon(.ready) self.processSourceKitOutput(from: xcodeStdout) AppDelegate.ui.setMenuIcon(.idle) } Self.runningXcode = nil AppDelegate.ui.launchXcodeItem.state = .off if !xcodeStdout.terminatedOK() && Defaults.xcodeRestart == true { AppDelegate.ui.runXcode(self) } Self.recompiler.writeCache() break // break on clean exit and EOF. } catch { // Continue processing on error Self.recompiler.error(error) } } } } } func processSourceKitOutput(from xcodeStdout: Popen) { var buffer = [CChar](repeating: 0, count: Popen.initialLineBufferSize) func readQuotedString() -> String? { var offset = 0 let doubleQuote = Int32(UInt8(ascii: "\"")), escaped = #"\""# while let line = fgets(&buffer[offset], CInt(buffer.count-offset), xcodeStdout.fileStream) { offset += strlen(line) if offset > 0 && buffer[offset-1] == UInt8(ascii: "\n") { if let start = strchr(buffer, doubleQuote), let end = strrchr(start+1, doubleQuote) { end[0] = 0 var out = String(cString: start+1) // Xcode used NSLog to log internal UTF8 strings // using %s which uses the macOS system encoding. // https://en.wikipedia.org/wiki/Mac_OS_Roman // For now we need to do the following dance // to revert scrambled non-ASCII file paths. if out.hasPrefix("/") && !FileManager.default.fileExists(atPath: out), let data = out.data(using: .macOSRoman), let recovered = String(data: data, encoding: .utf8), FileManager.default.fileExists(atPath: recovered) { out = recovered } if strstr(start+1, escaped) != nil { out = out.replacingOccurrences(of: escaped, with: "\"") } return out } return nil } var grown = [CChar](repeating: 0, count: buffer.count*2) strcpy(&grown, buffer) buffer = grown } return nil } let indexBuild = "/Index.noindex/Build/" while let line = xcodeStdout.readLine() { // debug(">>"+line+"<<") autoreleasepool { if line.hasPrefix(" key.request: source.request.") && (line == " key.request: source.request.editor.open," || line == " key.request: source.request.diagnostics," || line == " key.request: source.request.activeregions," || line == " key.request: source.request.relatedidents,") && xcodeStdout.readLine() == " key.compilerargs: [" || line == " key.compilerargs: [" { var parser = FrontendServer.CompilationArgParser() while var arg = readQuotedString() { /// Used if injecting the Swift compiler. let llvmIncs = "/llvm-macosx-arm64/lib" if arg.hasPrefix("-I"), arg.contains(llvmIncs) { arg = arg.replacingOccurrences(of: llvmIncs, with: "/../buildbot_osx"+llvmIncs) } /// Arguments received from SourceKit while syntax highlighting the editor /// have their own "Intermediates" directory. Map it back to the main one. let alt = arg[indexBuild, "/Build/"] if !arg.hasSuffix(".yaml"), alt != arg, !arg.contains("/Intermediates.noindex/"), let path: String = alt[#"[^/]*([^#]+)"#], FileManager.default.fileExists(atPath: path) { arg = alt } // SourceKit-specific args handled before the shared parser. if arg == "-fsyntax-only" || arg == "-o" { _ = xcodeStdout.readLine() } else if var work: String = arg[#"-working-directory(?:=(.*))?"#] { if work == RegexOptioned.unmatchedGroup, let swork = readQuotedString() { work = swork } parser.workingDir = work } else if parser.args.last == "-vfsoverlay", arg.contains(indexBuild) { // injecting tests without having run tests parser.args.removeLast() } else if arg == "-Xfrontend" || arg.hasPrefix("-driver-") { // drop silently } else { parser.process(arg: arg, next: readQuotedString) } } guard !parser.args.isEmpty, let source = readQuotedString() ?? readQuotedString(), !source.contains("\\n") else { return } print("Updating \(parser.args.count) args with \(parser.swiftFileCount) swift files "+source+" "+line) let update = NextCompiler.Compilation(arguments: parser.args, swiftFiles: parser.swiftFiles, workingDir: parser.workingDir) NextCompiler.compileQueue.async { Self.recompiler.store(compilation: update, for: source) } } else if line == " key.request: source.request.indexer.editor-did-save-file,", let _ = xcodeStdout.readLine(), let source = readQuotedString() { print("Injecting saved file "+source) NextCompiler.compileQueue.async { _ = Self.recompiler.inject(source: source) } } } } } } ================================================ FILE: App/InjectionNext/NextCompiler.swift ================================================ // // Recompiler.swift // InjectionNext // // Created by John Holdsworth on 21/06/2024. // Copyright © 2024 John Holdsworth. All rights reserved. // // Server side implementation of injection. // Recompile, link, codesign, send to client. // import Foundation import Fortify import Popen import DLKit /// bring in injectingXCTest() struct Reloader {} @discardableResult public func log(_ what: Any..., prefix: String = APP_PREFIX, separator: String = " ") -> Bool { var msg = what.map {"\($0)"}.joined(separator: separator) #if INJECTION_III_APP msg = "⏳ "+msg #else msg = prefix+msg LogBuffer.shared?.append(msg, level: "info") #endif print(msg) for client in InjectionServer.currentClients { client?.sendCommand(.log, with: msg) } return true } class NextCompiler { /// Information required to call the compiler for a file. struct Compilation: Codable, Hashable { /// Sundry arguments to the compiler let arguments: [String] /// Swift files in the target ready to be written as a -filelist let swiftFiles: String /// Directory to run compiler in (not important for Swift) let workingDir: String /// captured environment var env: String? } /// Queue for one compilation at a time. static let compileQueue = DispatchQueue(label: "InjectionCompile") /// Last build error. static var lastError: String?, lastSource: String? let name: String /// Base for temporary files let tmpbase = "/tmp/injectionNext" /// Injection pending if information was not available var pendingSource: String? /// Information for compiling a file per source file. var compilations = [String: Compilation]() /// Trying to avoid fragmenting memory var lastCompilation: Compilation? /// Previous dynamic libraries prepared by source file var prepared = [String: String]() /// Invalidate on three failures var strikes = [String: Int]() /// Default counter for Compilertron var compileNumber = 0 init(name: String) { self.name = name } func error(_ msg: String) { let msg = "⚠️ "+msg NSLog(msg) log(msg) } func error(_ err: Error) { error("Internal app error: \(err)") } var modified = false func store(compilation: Compilation, for source: String) { Self.lastSource = source if lastCompilation != compilation { lastCompilation = compilation } //else { print("reusing") } if compilations[source] != lastCompilation { compilations[source] = lastCompilation modified = true } if source == pendingSource { print("Delayed injection of "+source) if inject(source: source) { pendingSource = nil } } } func canCompile(source: String, for platform: String? = nil) -> Bool { if let compilation = compilations[source], platform == nil || ("SDKs/"+platform!).withCString({ sdk in compilation.arguments.first { strstr($0, sdk) != nil }}) != nil { return true } else { return false } } /// Main entry point called by MonitorXcode func inject(source: String) -> Bool { // Start tracking metrics currentMetrics = InjectionMetricsTracker(sourcePath: source) do { let result = try Fortify.protect { () -> Bool in for client in InjectionServer.currentClients.reversed() { guard let (dylib, dylibName, platform, useFilesystem) = try prepare(source: source, connected: client), let data = codesign(dylib: dylib, platform: platform) else { error("Injection failed. Was your app connected?") AppDelegate.ui.setMenuIcon(.error) return false } InjectionServer.clientQueue.sync { guard let client = client else { AppDelegate.ui.setMenuIcon(.ready) return } // if Reloader.injectingXCTest(in: dylib) { // _ = client.copyPlugIns // } if useFilesystem { client.writeCommand(InjectionCommand .load.rawValue, with: dylib) } else { client.writeCommand(InjectionCommand .inject.rawValue, with: dylibName) client.write(data) } unsupported(source: source, dylib: dylib, client: client) } } Self.lastSource = source if modified { writeCache() } return true } // Calculate total time and send metrics if let metrics = currentMetrics { metrics.success = result sendMetrics(metrics) } return result } catch { // Send failure metrics if let metrics = currentMetrics { metrics.success = false sendMetrics(metrics) } self.error(error) return false } } /// Seek to highlight potentially unsupported injections. func unsupported(source: String, dylib: String, client: InjectionServer) { #if !INJECTION_III_APP if let symbols = FileSymbols(path: dylib)?.trieSymbols()? .filter({ entry in lazy var symbol: String = String(cString: entry.name) return strncmp(entry.name, "_$s", 3) == 0 && strstr(entry.name, "fU") == nil && // closures !symbol.hasSuffix("MD") && !symbol.hasSuffix("Oh") && !symbol.hasSuffix("Wl") && !symbol.hasSuffix("WL") }) .map({ String(cString: $0.name) }).sorted() { // print(symbols) if let previous = client.exports[source], previous.count != symbols.count { log("ℹ️ Symbols altered, this may not be supported." + " \(symbols.count) c.f. \(previous.count)") if #available(macOS 15.0, *) { print(symbols.difference(from: previous)) } } client.exports[source] = symbols } #endif } func prepare(source: String, connected: InjectionServer?) throws -> (dylib: String, dylibName: String, platform: String, Bool)? { AppDelegate.ui.setMenuIcon(.busy) connected?.injectionNumber += 1 compileNumber += 1 Self.lastError = nil // Support for https://github.com/johnno1962/Compilertron let isCompilertron = connected == nil && source.hasSuffix(".cpp") let compilerTmp = "/tmp/compilertron_patches" let compilerPlatform = "MacOSX" let compilerArch = "arm64" let tmpPath = connected?.tmpPath ?? compilerTmp let platform = connected?.platform ?? compilerPlatform let sourceName = URL(fileURLWithPath: source) .deletingPathExtension().lastPathComponent if isCompilertron, let previous = prepared[sourceName] { unlink(previous) } let dylibName = DYLIB_PREFIX + sourceName + "_\(connected?.injectionNumber ?? compileNumber).dylib" let useFilesystem = connected?.isLocalClient != false #if INJECTION_III_APP let dylibPath = (true ? tmpPath : "/tmp") + dylibName #else let dylibPath = (useFilesystem ? tmpPath : "/tmp") + dylibName #endif guard let object = try recompile(source: source, platform: platform), { // Track compilation time if let startTime = currentMetrics?.startTime { currentMetrics?.compilationTimeMs = (Date.timeIntervalSinceReferenceDate - startTime) * 1000 } return true }(), tmpPath != compilerTmp || mkdir(compilerTmp, 0o777) != -999, let (dylib, linkingTimeMs) = link(object: object, dylib: dylibPath, arch: connected?.arch ?? compilerArch) else { let strike = (strikes[source] ?? 0)+1 strikes[source] = strike if strike >= 3 { compilations.removeValue(forKey: source) writeCache() } return nil } currentMetrics?.linkingTimeMs = linkingTimeMs strikes[source] = 0 prepared[sourceName] = dylib print("Prepared dylib: "+dylib) return (dylib, dylibName, platform, useFilesystem) } /// Compile a source file using inforation provided by MonitorXcode /// task and return the full path to the resulting object file. func recompile(source: String, platform: String) throws -> String? { guard let stored = compilations[source] else { error("Postponing: \(source) Have you viewed it in Xcode?") pendingSource = source return nil } let uniqueObject = InjectionServer.currentClient?.injectionNumber ?? 0 let object = tmpbase+"_\(uniqueObject).o" let isSwift = source.hasSuffix(".swift") let filesfile = tmpbase+".filelist" unlink(object) unlink(filesfile) try stored.swiftFiles.write(toFile: filesfile, atomically: false, encoding: .utf8) log("Recompiling: "+source) let toolchain = Defaults.xcodePath + "/Contents/Developer/Toolchains/XcodeDefault.xctoolchain" let compiler = (isSwift ? FrontendServer.loggedFrontend : nil) ?? toolchain + "/usr/bin/" + (isSwift ? "swift-frontend" : "clang") let platformUsr = Defaults.xcodePath + "/Contents/Developer/Platforms/" + platform.replacingOccurrences(of: "Simulator", with: "OS") + ".platform/Developer/usr/" let baseOptionsToAdd = ["-o", object, "-DDEBUG", "-DINJECTING"] let languageSpecific = (isSwift ? ["-c", "-filelist", filesfile, "-primary-file", source, Reloader.typeCheckLimit, "-external-plugin-path", platformUsr+"lib/swift/host/plugins#" + platformUsr+"bin/swift-plugin-server", "-external-plugin-path", platformUsr+"local/lib/swift/host/plugins#" + platformUsr+"bin/swift-plugin-server", "-plugin-path", toolchain+"/usr/lib/swift/host/plugins", "-plugin-path", toolchain+"/usr/local/lib/swift/host/plugins"] : ["-c", source, "-Xclang", "-fno-validate-pch"]) + baseOptionsToAdd let wmoFlags: Set = [ "-whole-module-optimization", "-internalize-at-link", "-no-serialize-debugging-options" ] var arguments = [String]() var skipNext = false for arg in stored.arguments where !wmoFlags.contains(arg) { if skipNext { skipNext = false; continue } if arg == "-o" { skipNext = true; continue } arguments.append(arg) } if let target = InjectionServer.currentClient?.arch, target != "arm64" { // Simulator running in Rosetta. for i in 0.. (String, Double)? { let linkingStartTime = Date.timeIntervalSinceReferenceDate var linkCommand = Reloader.linkCommand + " \(object) -o \"\(dylib)\" " if DispatchQueue.main.sync(execute: { AppDelegate.ui.deviceTesting?.state == .on }) { let otherOptions = DispatchQueue.main.sync { () -> String in AppDelegate.ui.librariesField.stringValue = Defaults.deviceLibraries return Defaults.deviceLibraries } let platformDev = "\(Reloader.xcodeDev)/Platforms/\(Reloader.platform).platform/Developer" linkCommand += """ -F /tmp/InjectionNext.Products \ -F "\(platformDev)/Library/Frameworks" \ -L "\(platformDev)/usr/lib" \(otherOptions) """.replacingOccurrences(of: "__PLATFORM__", with: Reloader.sysroot) } if let errors = Popen.system(linkCommand, errors: true) { error("Linking failed:\n\(linkCommand)\nerrors:\n"+errors) Self.lastError = errors return nil } let linkingTimeMs = (Date.timeIntervalSinceReferenceDate - linkingStartTime) * 1000 return (dylib, linkingTimeMs) } /// Codesign a dynamic library func codesign(dylib: String, platform: String) -> Data? { if platform != "iPhoneSimulator" { var identity = "-" if !platform.hasSuffix("Simulator") && platform != "MacOSX" { identity = DispatchQueue.main.sync { AppDelegate.ui.codeSigningID } log("Codesigning dylib with identity "+identity) } let codesign = """ (export CODESIGN_ALLOCATE="\(Defaults.xcodePath+"/Contents/Developer" )/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate"; \ if /usr/bin/file \"\(dylib)\" | /usr/bin/grep ' shared library ' >/dev/null; \ then /usr/bin/codesign --force -s "\(identity)" \"\(dylib)\";\ else exit 1; fi) """ if let errors = Popen.system(codesign, errors: true) { error("Codesign failed \(codesign) errors:\n"+errors) Self.lastError = errors } } return try? Data(contentsOf: URL(fileURLWithPath: dylib)) } /// Tracks timing metrics for injection process final class InjectionMetricsTracker: Codable { var compilationTimeMs: Double = 0 var linkingTimeMs: Double = 0 var totalTimeMs: Double = 0 var sourcePath: String var bazelTarget: String? var success: Bool = false var notificationName: String = INJECTION_METRICS_NOTIFICATION let startTime: Double init(sourcePath: String) { self.sourcePath = sourcePath self.startTime = Date.timeIntervalSinceReferenceDate } } /// Current metrics being tracked var currentMetrics: InjectionMetricsTracker? /// Enrich metrics with Bazel target and normalize source path #if !INJECTION_III_APP func enrichMetrics(_ metrics: inout InjectionMetricsTracker) { let sourcePath = metrics.sourcePath // Find workspace root to normalize the path if let workspaceRoot = BazelInterface.findWorkspaceRoot(containing: sourcePath) { let workspaceRootPath = (workspaceRoot as NSString).standardizingPath let fullPath = (sourcePath as NSString).standardizingPath // Normalize sourcePath to workspace-relative (in place) if fullPath.hasPrefix(workspaceRootPath + "/") { metrics.sourcePath = String(fullPath.dropFirst(workspaceRootPath.count + 1)) } else { metrics.sourcePath = URL(fileURLWithPath: sourcePath).lastPathComponent } // Try to discover the Bazel app target do { let queryHandler = try BazelAQueryParser(workspaceRoot: workspaceRoot) queryHandler.autoDiscoverAppTarget(for: sourcePath) metrics.bazelTarget = queryHandler.getAppTarget() } catch { // Bazel discovery failed, metrics will have nil bazelTarget print("⚠️ Could not discover Bazel target for \(sourcePath): \(error)") } } } #endif /// Send metrics to all connected clients func sendMetrics(_ metrics: InjectionMetricsTracker) { #if !INJECTION_III_APP if AppDelegate.watchers.isEmpty && BazelActionQueryHandler.cachedAppTarget == nil { return } metrics.totalTimeMs = (Date.timeIntervalSinceReferenceDate - metrics.startTime) * 1000 var enrichedMetrics = metrics enrichMetrics(&enrichedMetrics) let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase guard let jsonData = try? encoder.encode(enrichedMetrics), let jsonString = String(data: jsonData, encoding: .utf8) else { return } for client in InjectionServer.currentClients { client?.sendCommand(.metrics, with: jsonString) } #endif } } ================================================ FILE: App/InjectionNext/build_bundle.sh ================================================ #!/bin/sh -x # build_bundle.sh # InjectionNext # # Created by John Holdsworth on 22/07/2024. # Copyright © 2024 John Holdsworth. All rights reserved. FAMILY=$1 PLATFORM=$2 SDK="$(echo $PLATFORM | tr "[A-Z]" "[a-z]")" sleep $3 FIXED_XCODE_DEVELOPER_PATH=/Applications/Xcode.app/Contents/Developer export SWIFT_ACTIVE_COMPILATION_CONDITIONS="" SWIFT_DYLIBS_PATH="$FIXED_XCODE_DEVELOPER_PATH/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$SDK" CONCURRENCY_DYLIBS="$FIXED_XCODE_DEVELOPER_PATH/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/$SDK" XCODE_PLATFORM_PATH="$FIXED_XCODE_DEVELOPER_PATH/Platforms/$PLATFORM.platform" XCTEST_FRAMEWORK_PATH="$XCODE_PLATFORM_PATH/Developer/Library/Frameworks" XCTEST_SUPPORT_PATH="$XCODE_PLATFORM_PATH/Developer/usr/lib" BUNDLE_CONFIG=Debug if [ ! -d "$SWIFT_DYLIBS_PATH" -o ! -d "${XCTEST_FRAMEWORK_PATH}/XCTest.framework" ]; then echo "Missing RPATH $SWIFT_DYLIBS_PATH $XCTEST_FRAMEWORK_PATH" exit 1 fi ADD_INSTALL_NAME="" if [[ ${FAMILY} =~ Dev ]]; then # real devices require a copy_bundle.sh build phase ADD_INSTALL_NAME="LD_DYLIB_INSTALL_NAME=@rpath/lib${SDK}Injection.dylib" fi for i in 1 2 3; do if "$DEVELOPER_BIN_DIR"/xcodebuild SYMROOT=$SYMROOT ARCHS="$ARCHS" $APP_SANDBOXED PRODUCT_NAME="${FAMILY}Injection" LD_RUNPATH_SEARCH_PATHS="@loader_path/Frameworks @loader_path/${FAMILY}Injection.bundle/Frameworks $SWIFT_DYLIBS_PATH $CONCURRENCY_DYLIBS $XCTEST_FRAMEWORK_PATH $XCTEST_SUPPORT_PATH" $ADD_INSTALL_NAME PLATFORM_DIR="$DEVELOPER_DIR/Platforms/$PLATFORM.platform" -sdk $SDK -config $BUNDLE_CONFIG -target InjectionBundle; then #-archivePath "/tmp/Archive.$SDK" -derivedDataPath "/tmp/Derived.$SDK" break elif [ "$i" = "3" ]; then exit 1 fi echo "Retrying..."; done && rsync -au $SYMROOT/$BUNDLE_CONFIG-$SDK/*.bundle "$CODESIGNING_FOLDER_PATH/Contents/Resources" && ln -sf "${FAMILY}Injection.bundle/${FAMILY}Injection" "$CODESIGNING_FOLDER_PATH/Contents/Resources/lib${SDK}Injection.dylib" ================================================ FILE: App/InjectionNext/build_bundles.sh ================================================ #!/bin/sh # build_bundles.sh # InjectionNext # # Created by John Holdsworth on 22/07/2024. # Copyright © 2024 John Holdsworth. All rights reserved. FIXED_XCODE_DEVELOPER_PATH=/Applications/Xcode.app/Contents/Developer export SWIFT_ACTIVE_COMPILATION_CONDITIONS="" BUILD=`/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" $CODESIGNING_FOLDER_PATH/Contents/Info.plist` function build_bundle () { FAMILY=$1 PLATFORM=$2 SDK=$3 SWIFT_DYLIBS_PATH="$FIXED_XCODE_DEVELOPER_PATH/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$SDK" CONCURRENCY_DYLIBS="$FIXED_XCODE_DEVELOPER_PATH/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/$SDK" XCODE_PLATFORM_PATH="$FIXED_XCODE_DEVELOPER_PATH/Platforms/$PLATFORM.platform" XCCORE_FRAMEWORK_PATH="$XCODE_PLATFORM_PATH/Developer/Library/PrivateFrameworks" XCTEST_FRAMEWORK_PATH="$XCODE_PLATFORM_PATH/Developer/Library/Frameworks" XCTEST_SUPPORT_PATH="$XCODE_PLATFORM_PATH/Developer/usr/lib" BUNDLE_CONFIG=Debug if [ ! -d "$SWIFT_DYLIBS_PATH" -o ! -d "${XCTEST_FRAMEWORK_PATH}/XCTest.framework" ]; then echo "Missing RPATH $SWIFT_DYLIBS_PATH $XCTEST_FRAMEWORK_PATH" exit 1 fi ADD_INSTALL_NAME="" if [[ ${FAMILY} =~ Dev ]]; then # real devices require a copy_bundle.sh build phase ADD_INSTALL_NAME="LD_DYLIB_INSTALL_NAME=@rpath/lib${SDK}Injection.dylib" fi "$DEVELOPER_BIN_DIR"/xcodebuild SYMROOT=$SYMROOT ARCHS="$ARCHS" $APP_SANDBOXED PRODUCT_NAME="${FAMILY}Injection" LD_RUNPATH_SEARCH_PATHS="@executable_path/Frameworks @loader_path/Frameworks @loader_path/${FAMILY}Injection.bundle/Frameworks $SWIFT_DYLIBS_PATH $CONCURRENCY_DYLIBS $XCTEST_FRAMEWORK_PATH $XCTEST_SUPPORT_PATH $XCCORE_FRAMEWORK_PATH" $ADD_INSTALL_NAME PLATFORM_DIR="$DEVELOPER_DIR/Platforms/$PLATFORM.platform" -sdk $SDK -config $BUNDLE_CONFIG -target InjectionBundle && rsync -au $SYMROOT/$BUNDLE_CONFIG-$SDK/*.bundle "$CODESIGNING_FOLDER_PATH/Contents/Resources" && PLIST="$CODESIGNING_FOLDER_PATH/Contents/Resources/${FAMILY}Injection.bundle/Info.plist" && (/usr/libexec/PlistBuddy -c "Delete :CFBundleVersion" "$PLIST" || echo -n) && /usr/libexec/PlistBuddy -c "Add :CFBundleVersion string $BUILD" "$PLIST" && ln -sf "${FAMILY}Injection.bundle/${FAMILY}Injection" "$CODESIGNING_FOLDER_PATH/Contents/Resources/lib${SDK}Injection.dylib" } ln -sf "macOSInjection.bundle/Contents/MacOS/macOSInjection" "$CODESIGNING_FOLDER_PATH/Contents/Resources/libmacosxInjection.dylib" && build_bundle iOS iPhoneSimulator iphonesimulator && if [[ "$ACTION" = "install" ]]; then build_bundle tvOS AppleTVSimulator appletvsimulator && build_bundle xrOS XRSimulator xrsimulator && build_bundle iOSDev iPhoneOS iphoneos && build_bundle tvOSDev AppleTVOS appletvos && build_bundle xrOSDev XROS xros fi && exit 0 ================================================ FILE: App/InjectionNext/copy_bundle.sh ================================================ #!/bin/bash -x # # copy_bundle.sh # InjectionIII # # Copies injection bundle for on-device injection. # Thanks @oryonatan # # $Id: //depot/HotReloading/copy_bundle.sh#14 $ # if [[ "$CONFIGURATION" =~ Debug ]]; then if [ ! -w "$CODESIGNING_FOLDER_PATH" ]; then echo '*** copy_bundle.sh unable to write to file system. ***' \ 'Change build setting "User Script Sandboxing" to NO' exit 1; fi # determine which prebuilt bundle to copy RESOURCES=${RESOURCES:-"$(dirname "$0")"} # If there are frameworks used only by tests TESTING_FRAMEWORKS="$2" COPY="$CODESIGNING_FOLDER_PATH/iOSInjection.bundle" PLIST="$COPY/Info.plist" if [ "$PLATFORM_NAME" == "macosx" ]; then BUNDLE=${1:-macOSInjection} COPY="$CODESIGNING_FOLDER_PATH/Contents/Resources/macOSInjection.bundle" PLIST="$COPY/Contents/Info.plist" elif [ "$PLATFORM_NAME" == "appletvsimulator" ]; then BUNDLE=${1:-tvOSInjection} elif [ "$PLATFORM_NAME" == "appletvos" ]; then BUNDLE=${1:-tvOSDevInjection} elif [ "$PLATFORM_NAME" == "xrsimulator" ]; then BUNDLE=${1:-xrOSInjection} elif [ "$PLATFORM_NAME" == "xros" ]; then BUNDLE=${1:-xrOSDevInjection} elif [ "$PLATFORM_NAME" == "iphoneos" ]; then BUNDLE=${1:-iOSDevInjection} else BUNDLE=${1:-iOSInjection} fi mkdir -p "$CODESIGNING_FOLDER_PATH/Frameworks" && # copy frameworks used for testing into app's bundle/Frameworks ln -sf "../iOSInjection.bundle/$BUNDLE" "$CODESIGNING_FOLDER_PATH/Frameworks/lib${PLATFORM_NAME}Injection.dylib" && if [[ "$BUNDLE" =~ Dev ]]; then rsync -a "$PLATFORM_DEVELOPER_LIBRARY_DIR"/*Frameworks/{XC,StoreKit}* "$PLATFORM_DEVELOPER_USR_DIR/lib"/*.dylib "$CODESIGNING_FOLDER_PATH/Frameworks/" && codesign -f --sign "$EXPANDED_CODE_SIGN_IDENTITY" --timestamp\=none --preserve-metadata\=identifier,entitlements,flags --generate-entitlement-der "$CODESIGNING_FOLDER_PATH/Frameworks"/{XC*,StoreKit*,*.dylib} || echo "*** You should be able to ignore the above errors ***" else rsync -a "$PLATFORM_DEVELOPER_LIBRARY_DIR"/*Frameworks/XC* "$PLATFORM_DEVELOPER_USR_DIR/lib"/*.dylib "$CODESIGNING_FOLDER_PATH/Frameworks/" && codesign -f --sign "$EXPANDED_CODE_SIGN_IDENTITY" --timestamp\=none --preserve-metadata\=identifier,entitlements,flags --generate-entitlement-der "$CODESIGNING_FOLDER_PATH/Frameworks"/{XC*,*.dylib} fi && # Copy frameworks only used in test target PRODUCTS_DIR="$(dirname "$CODESIGNING_FOLDER_PATH")" rm -f /tmp/InjectionNext.Products ln -s "$PRODUCTS_DIR" /tmp/InjectionNext.Products (cd "$PRODUCTS_DIR" && for fwork in $TESTING_FRAMEWORKS; do if [ -f "$fwork/Info.plist" -a \ ! -d "$CODESIGNING_FOLDER_PATH/Frameworks/$fwork" ]; then rsync -a "$fwork" "$CODESIGNING_FOLDER_PATH/Frameworks" && codesign -f --sign "$EXPANDED_CODE_SIGN_IDENTITY" --timestamp\=none --preserve-metadata\=identifier,entitlements,flags --generate-entitlement-der "$CODESIGNING_FOLDER_PATH/Frameworks/$fwork" fi done) # Xcode 16's new SwiftTesting framework TESTING="$PLATFORM_DEVELOPER_LIBRARY_DIR/Frameworks/Testing.Framework" if [ -d "$TESTING" ]; then rsync -a "$TESTING"/* "$CODESIGNING_FOLDER_PATH/Frameworks/Testing.framework/" codesign -f --sign "$EXPANDED_CODE_SIGN_IDENTITY" --timestamp\=none --preserve-metadata\=identifier,entitlements,flags --generate-entitlement-der "$CODESIGNING_FOLDER_PATH/Frameworks/Testing.framework"; fi # Make copy of "PlugIns" directory when testing export PLUGINS="/tmp/PlugIns.$PRODUCT_NAME.$PLATFORM_NAME" export LAST_PLUGINS="/tmp/InjectionNext.PlugIns" rm -f $LAST_PLUGINS if [ -d "$CODESIGNING_FOLDER_PATH/PlugIns" ]; then (sleep 5; while rsync -va "$CODESIGNING_FOLDER_PATH/PlugIns"/* "$PLUGINS/" | grep -v /sec | grep /; do sleep 15; done) 1>/dev/null 2>&1 & else bash -x <<'CAN_FAIL' 2>/dev/null # Xcode 16 deletes PlugIns directory. copy or create link rsync -a "$PLUGINS"/* "$CODESIGNING_FOLDER_PATH/PlugIns/" && codesign -f --sign "$EXPANDED_CODE_SIGN_IDENTITY" --timestamp\=none --preserve-metadata\=identifier,entitlements,flags --generate-entitlement-der "$CODESIGNING_FOLDER_PATH/PlugIns/*.xctest" || ln -s $PLUGINS $LAST_PLUGINS CAN_FAIL fi # copy prebuilt bundle into app package and codesign rsync -a "$RESOURCES/$BUNDLE.bundle"/* "$COPY/" && # See +[SimpleSocket initialize] for pre-built bundles/dylibs /usr/libexec/PlistBuddy -c "Add :UserHome string $HOME" "$PLIST" && (/usr/libexec/PlistBuddy -c "Delete :InjectionUserHome" "$CODESIGNING_FOLDER_PATH/Info.plist" || echo -n) && /usr/libexec/PlistBuddy -c "Add :InjectionUserHome string $HOME" "$CODESIGNING_FOLDER_PATH/Info.plist" && codesign -f --sign "$EXPANDED_CODE_SIGN_IDENTITY" --timestamp\=none --preserve-metadata\=identifier,entitlements,flags --generate-entitlement-der "$COPY" && defaults write com.johnholdsworth.InjectionNext codesigningIdentity "$EXPANDED_CODE_SIGN_IDENTITY" fi ================================================ FILE: App/InjectionNext/main.m ================================================ // // main.m // InjectionIII // // Created by John Holdsworth on 06/11/2017. // Copyright © 2017 John Holdsworth. All rights reserved. // #import int main(int argc, const char * argv[]) { return NSApplicationMain(argc, argv); } ================================================ FILE: App/InjectionNext/swift-frontend.sh ================================================ #!/bin/bash # swift-frontend.sh # InjectionNext # # Created by John Holdsworth on 23/02/2025. # Copyright © 2025 John Holdsworth. All rights reserved. FRONTEND="$0" "$FRONTEND.save" "$@" && if [ "$2" = "-c" ]; then "/Applications/InjectionNext.app/Contents/Resources/feedcommands" \ "2.0" "$(/usr/bin/env)" "$FRONTEND.save" "$@" >>/tmp/feedcommands.log 2>&1 & fi ================================================ FILE: App/InjectionNext.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 70; objects = { /* Begin PBXBuildFile section */ 2217BE812C0A65C20032A832 /* Fortify in Frameworks */ = {isa = PBXBuildFile; productRef = 2217BE802C0A65C20032A832 /* Fortify */; }; 222BC9452C157C3200780A41 /* InjectionReady.tif in Resources */ = {isa = PBXBuildFile; fileRef = 222BC9442C157C3200780A41 /* InjectionReady.tif */; }; 224E57FB2C08978F00B71C79 /* SwiftRegex in Frameworks */ = {isa = PBXBuildFile; productRef = 224E57FA2C08978F00B71C79 /* SwiftRegex */; }; 224E57FE2C08BBE300B71C79 /* InjectionServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 224E57FD2C08BBE300B71C79 /* InjectionServer.swift */; }; 224E58002C08BC0E00B71C79 /* MonitorXcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 224E57FF2C08BC0E00B71C79 /* MonitorXcode.swift */; }; 229127F12C1EEF69005D7625 /* Experimental.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC17029253ED117002E823F /* Experimental.swift */; }; 22B645A02C18DD9D00F99B61 /* Unhider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22B6459F2C18DD9D00F99B61 /* Unhider.swift */; }; 22E3D98B2C19009900BB234E /* DLKit in Frameworks */ = {isa = PBXBuildFile; productRef = 22E3D98A2C19009900BB234E /* DLKit */; }; 22E3D98D2C19009900BB234E /* DLKitC in Frameworks */ = {isa = PBXBuildFile; productRef = 22E3D98C2C19009900BB234E /* DLKitC */; }; BB0CECB02C73C1610000A4E6 /* Quick in Frameworks */ = {isa = PBXBuildFile; productRef = BB0CECAF2C73C1610000A4E6 /* Quick */; }; BB0CECB32C73C1890000A4E6 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = BB0CECB22C73C1890000A4E6 /* Nimble */; }; BB16653A25E9A5F2001407AE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = BB16653925E9A5F0001407AE /* main.m */; }; BB2A66362E3E753B001EDD38 /* BAZEL.md in Resources */ = {isa = PBXBuildFile; fileRef = BB2A66352E3E753B001EDD38 /* BAZEL.md */; }; BB2D41AC2DAD14EA0073C203 /* Popen in Frameworks */ = {isa = PBXBuildFile; productRef = BB2D41AB2DAD14EA0073C203 /* Popen */; }; BB42F8E12EB626F100FDBBCC /* SwiftAspects.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8D62EB626F100FDBBCC /* SwiftAspects.swift */; }; BB42F8E22EB626F100FDBBCC /* SwiftInterpose.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8D72EB626F100FDBBCC /* SwiftInterpose.swift */; }; BB42F8E32EB626F100FDBBCC /* SwiftTrace.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8DF2EB626F100FDBBCC /* SwiftTrace.swift */; }; BB42F8E42EB626F100FDBBCC /* SwiftSwizzle.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8DD2EB626F100FDBBCC /* SwiftSwizzle.swift */; }; BB42F8E52EB626F100FDBBCC /* SwiftArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8D52EB626F100FDBBCC /* SwiftArgs.swift */; }; BB42F8E62EB626F100FDBBCC /* SwiftInvoke.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8D82EB626F100FDBBCC /* SwiftInvoke.swift */; }; BB42F8E72EB626F100FDBBCC /* EasyPointer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8D22EB626F100FDBBCC /* EasyPointer.swift */; }; BB42F8E82EB626F100FDBBCC /* SwiftStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8DB2EB626F100FDBBCC /* SwiftStack.swift */; }; BB42F8E92EB626F100FDBBCC /* SwiftStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8DC2EB626F100FDBBCC /* SwiftStats.swift */; }; BB42F8EA2EB626F100FDBBCC /* StringIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8D42EB626F100FDBBCC /* StringIndex.swift */; }; BB42F8EB2EB626F100FDBBCC /* SwiftLifetime.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8D92EB626F100FDBBCC /* SwiftLifetime.swift */; }; BB42F8EC2EB626F100FDBBCC /* SwiftMeta.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8DA2EB626F100FDBBCC /* SwiftMeta.swift */; }; BB42F9212EB639D100FDBBCC /* DLKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F91B2EB639D100FDBBCC /* DLKit.swift */; }; BB42F9222EB639D100FDBBCC /* Iterators.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F91F2EB639D100FDBBCC /* Iterators.swift */; }; BB42F9232EB639D100FDBBCC /* Interposing.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F91E2EB639D100FDBBCC /* Interposing.swift */; }; BB42F9242EB639D100FDBBCC /* ImageSymbols.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F91D2EB639D100FDBBCC /* ImageSymbols.swift */; }; BB42F9252EB639D100FDBBCC /* Demangling.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F91A2EB639D100FDBBCC /* Demangling.swift */; }; BB42F9262EB639D100FDBBCC /* FileSymbols.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F91C2EB639D100FDBBCC /* FileSymbols.swift */; }; BB42F92D2EB639E500FDBBCC /* DLKitC.c in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9292EB639E500FDBBCC /* DLKitC.c */; }; BB42F92E2EB639E500FDBBCC /* trie_dladdr.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB42F92A2EB639E500FDBBCC /* trie_dladdr.mm */; }; BB42F92F2EB639E500FDBBCC /* trie_dlops.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB42F92B2EB639E500FDBBCC /* trie_dlops.mm */; }; BB42F9382EB63C2900FDBBCC /* KeyPaths.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9332EB63C2900FDBBCC /* KeyPaths.swift */; }; BB42F9392EB63C2900FDBBCC /* Reloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9352EB63C2900FDBBCC /* Reloader.swift */; }; BB42F93A2EB63C2900FDBBCC /* Generics.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9322EB63C2900FDBBCC /* Generics.swift */; }; BB42F93B2EB63C2900FDBBCC /* Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9342EB63C2900FDBBCC /* Metadata.swift */; }; BB42F93C2EB63C2900FDBBCC /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9312EB63C2900FDBBCC /* Common.swift */; }; BB42F93D2EB63C2900FDBBCC /* Sweeper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9362EB63C2900FDBBCC /* Sweeper.swift */; }; BB42F93F2EB63CD800FDBBCC /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9312EB63C2900FDBBCC /* Common.swift */; }; BB42F9462EB63E3400FDBBCC /* InjectionBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9412EB63E3400FDBBCC /* InjectionBase.swift */; }; BB42F9472EB63E3400FDBBCC /* Recompiler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9442EB63E3400FDBBCC /* Recompiler.swift */; }; BB42F9482EB63E3400FDBBCC /* InjectionLite.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9422EB63E3400FDBBCC /* InjectionLite.swift */; }; BB42F9492EB63E3400FDBBCC /* LogParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9432EB63E3400FDBBCC /* LogParser.swift */; }; BB42F94A2EB63E3400FDBBCC /* FileWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9402EB63E3400FDBBCC /* FileWatcher.swift */; }; BB42F94B2EB63E3F00FDBBCC /* FileWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9402EB63E3400FDBBCC /* FileWatcher.swift */; }; BB42F94C2EB63E4F00FDBBCC /* LogParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9432EB63E3400FDBBCC /* LogParser.swift */; }; BB42F94D2EB63E5600FDBBCC /* Recompiler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9442EB63E3400FDBBCC /* Recompiler.swift */; }; BB42F94E2EB63E7700FDBBCC /* InjectionBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9412EB63E3400FDBBCC /* InjectionBase.swift */; }; BB42F9552EB63EFF00FDBBCC /* BazelInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9512EB63EFF00FDBBCC /* BazelInterface.swift */; }; BB42F9562EB63EFF00FDBBCC /* GitIgnoreParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9532EB63EFF00FDBBCC /* GitIgnoreParser.swift */; }; BB42F9572EB63EFF00FDBBCC /* BazelActionQueryHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F94F2EB63EFF00FDBBCC /* BazelActionQueryHandler.swift */; }; BB42F9582EB63EFF00FDBBCC /* BazelAQueryParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9502EB63EFF00FDBBCC /* BazelAQueryParser.swift */; }; BB42F9592EB63EFF00FDBBCC /* FilenameMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9522EB63EFF00FDBBCC /* FilenameMatcher.swift */; }; BB42F95A2EB63EFF00FDBBCC /* BazelInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9512EB63EFF00FDBBCC /* BazelInterface.swift */; }; BB42F95B2EB63EFF00FDBBCC /* GitIgnoreParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9532EB63EFF00FDBBCC /* GitIgnoreParser.swift */; }; BB42F95C2EB63EFF00FDBBCC /* BazelActionQueryHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F94F2EB63EFF00FDBBCC /* BazelActionQueryHandler.swift */; }; BB42F95D2EB63EFF00FDBBCC /* BazelAQueryParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9502EB63EFF00FDBBCC /* BazelAQueryParser.swift */; }; BB42F95E2EB63EFF00FDBBCC /* FilenameMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9522EB63EFF00FDBBCC /* FilenameMatcher.swift */; }; BB4654812C257F110080EC40 /* NextCompiler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB4654802C257F110080EC40 /* NextCompiler.swift */; }; BB4788802EB6B6E700464AB4 /* InjectionBoot.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB47887E2EB6B6E700464AB4 /* InjectionBoot.mm */; }; BB4788812EB6B71600464AB4 /* InjectionImplC.h in Resources */ = {isa = PBXBuildFile; fileRef = BB42F9302EB63AAF00FDBBCC /* InjectionImplC.h */; }; BB4788902EB6B81A00464AB4 /* fast_dladdr.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB4788852EB6B81A00464AB4 /* fast_dladdr.mm */; }; BB4788912EB6B81A00464AB4 /* xt_forwarding_trampoline_arm64.s in Sources */ = {isa = PBXBuildFile; fileRef = BB47888C2EB6B81A00464AB4 /* xt_forwarding_trampoline_arm64.s */; }; BB4788922EB6B81A00464AB4 /* xt_forwarding_trampoline_x64.s in Sources */ = {isa = PBXBuildFile; fileRef = BB47888D2EB6B81A00464AB4 /* xt_forwarding_trampoline_x64.s */; }; BB4788932EB6B81A00464AB4 /* ObjCBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB4788872EB6B81A00464AB4 /* ObjCBridge.mm */; }; BB4788942EB6B81A00464AB4 /* Trampolines.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB47888A2EB6B81A00464AB4 /* Trampolines.mm */; }; BB4788952EB6B81A00464AB4 /* xt_forwarding_trampoline_arm7.s in Sources */ = {isa = PBXBuildFile; fileRef = BB47888B2EB6B81A00464AB4 /* xt_forwarding_trampoline_arm7.s */; }; BB4788962EB6B81A00464AB4 /* SwiftTrace.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB4788882EB6B81A00464AB4 /* SwiftTrace.mm */; }; BB4788972EB6B81A00464AB4 /* xt_forwarding_trampoline_x86.s in Sources */ = {isa = PBXBuildFile; fileRef = BB47888E2EB6B81A00464AB4 /* xt_forwarding_trampoline_x86.s */; }; BB4788982EB6B81A00464AB4 /* fishhook.c in Sources */ = {isa = PBXBuildFile; fileRef = BB4788862EB6B81A00464AB4 /* fishhook.c */; }; BB5155DA2CDED44F00704C7A /* InjectionHybrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5155D92CDED44400704C7A /* InjectionHybrid.swift */; }; AA0000012F08000000000001 /* ControlServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0000002F08000000000001 /* ControlServer.swift */; }; BB52AD552F1501F500297CD9 /* Unhider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22B6459F2C18DD9D00F99B61 /* Unhider.swift */; }; BB6A56F02C50E73600C92112 /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6A56EF2C50E73600C92112 /* Defaults.swift */; }; BB6A56F12C50E79800C92112 /* copy_bundle.sh in Resources */ = {isa = PBXBuildFile; fileRef = BBDD84582C4FF0B9000F3124 /* copy_bundle.sh */; }; BB85A42B2D6B9C7B00FA29D0 /* feedcommands in Resources */ = {isa = PBXBuildFile; fileRef = BB85A4242D6B9C5300FA29D0 /* feedcommands */; }; BB85A42E2D6BA13B00FA29D0 /* SimpleSocket.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB67DBB31FB0CDA8000EAC8A /* SimpleSocket.mm */; }; BBA082362D6BC33500FFFB2F /* FrontendServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA082352D6BC32D00FFFB2F /* FrontendServer.swift */; }; BBA082382D6BD6CB00FFFB2F /* swift-frontend.sh in Resources */ = {isa = PBXBuildFile; fileRef = BBA082372D6BD6CB00FFFB2F /* swift-frontend.sh */; }; BBB040641FB17A6C007DDD0A /* ScriptingBridge.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BBB040631FB1798A007DDD0A /* ScriptingBridge.framework */; }; BBB64DC51FD450AF0020BE47 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = BB037DF31FAD808B004B267C /* README.md */; }; BBB64DC61FD450B40020BE47 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = BB037DF41FAD80D0004B267C /* LICENSE */; }; BBB64DE51FD571310020BE47 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = BBB64DE41FD571300020BE47 /* libz.tbd */; }; BBB64FEC1FD585D50020BE47 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BBB64FEB1FD585D50020BE47 /* WebKit.framework */; }; BBCA02021FB0F10300E45F0F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCA02011FB0F10300E45F0F /* AppDelegate.swift */; }; BBCA02041FB0F10300E45F0F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BBCA02031FB0F10300E45F0F /* Assets.xcassets */; }; BBCA02071FB0F10300E45F0F /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = BBCA02051FB0F10300E45F0F /* MainMenu.xib */; }; BBCA022A1FB0F64800E45F0F /* SimpleSocket.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB67DBB31FB0CDA8000EAC8A /* SimpleSocket.mm */; }; BBCA02591FB112F500E45F0F /* App.icns in Resources */ = {isa = PBXBuildFile; fileRef = BBCA02581FB112F500E45F0F /* App.icns */; }; BBCA02601FB122C300E45F0F /* InjectionIdle.tif in Resources */ = {isa = PBXBuildFile; fileRef = BBCA025E1FB122C300E45F0F /* InjectionIdle.tif */; }; BBCA02611FB122C300E45F0F /* InjectionOK.tif in Resources */ = {isa = PBXBuildFile; fileRef = BBCA025F1FB122C300E45F0F /* InjectionOK.tif */; }; BBCCC3B72C73B6FD00E71F81 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BBBEC42D1FC56522004188B3 /* XCTest.framework */; }; BBDD843F2C4FE8D5000F3124 /* macOSInjection.bundle in Resources */ = {isa = PBXBuildFile; fileRef = BBDD84132C4FE749000F3124 /* macOSInjection.bundle */; }; BBDD84422C4FEA4E000F3124 /* InjectionNext.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBDD84182C4FE7E6000F3124 /* InjectionNext.swift */; }; BBDD84542C4FEB16000F3124 /* TupleRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBDD84532C4FEB16000F3124 /* TupleRegex.swift */; }; BBDD84552C4FEC98000F3124 /* SimpleSocket.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB67DBB31FB0CDA8000EAC8A /* SimpleSocket.mm */; }; BBDD845A2C4FF646000F3124 /* ClientBoot.mm in Sources */ = {isa = PBXBuildFile; fileRef = BBDD84592C4FF645000F3124 /* ClientBoot.mm */; }; BBE490DB1FB2C643003D41BB /* InjectionBusy.tif in Resources */ = {isa = PBXBuildFile; fileRef = BBE490D91FB2C643003D41BB /* InjectionBusy.tif */; }; BBE490DC1FB2C643003D41BB /* InjectionError.tif in Resources */ = {isa = PBXBuildFile; fileRef = BBE490DA1FB2C643003D41BB /* InjectionError.tif */; }; BBEB87122C74FD930044578A /* Quick in Frameworks */ = {isa = PBXBuildFile; productRef = BBEB87112C74FD930044578A /* Quick */; }; BBF6572E2DAFF00200638170 /* NIMBLE.md in Resources */ = {isa = PBXBuildFile; fileRef = BBF6572C2DAFF00200638170 /* NIMBLE.md */; }; BBF6572F2DAFF00200638170 /* QUICK.md in Resources */ = {isa = PBXBuildFile; fileRef = BBF6572D2DAFF00200638170 /* QUICK.md */; }; BBFE1B422ED7C116004E14EE /* SwiftRefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBFE1B412ED7C116004E14EE /* SwiftRefs.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ BB85A42C2D6B9CA200FA29D0 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BB439B5D1FABA64300B4F50B /* Project object */; proxyType = 1; remoteGlobalIDString = BB85A4232D6B9C5300FA29D0; remoteInfo = feedcommands; }; BBDD843D2C4FE8C5000F3124 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BB439B5D1FABA64300B4F50B /* Project object */; proxyType = 1; remoteGlobalIDString = BBDD84122C4FE749000F3124; remoteInfo = InjectionBundle; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ BB6224721FC5BA6F00AD7A3A /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = Contents/Library/LaunchServices; dstSubfolderSpec = 1; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; BB6C87DB2520D0D3005AFCFC /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; BB85A4222D6B9C5300FA29D0 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/share/man/man1/; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 1; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 222BC9442C157C3200780A41 /* InjectionReady.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = InjectionReady.tif; sourceTree = ""; }; 224E57FC2C08BAE200B71C79 /* InjectionClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = InjectionClient.h; path = ../../Sources/InjectionNextC/include/InjectionClient.h; sourceTree = ""; }; 224E57FD2C08BBE300B71C79 /* InjectionServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectionServer.swift; sourceTree = ""; }; 224E57FF2C08BC0E00B71C79 /* MonitorXcode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonitorXcode.swift; sourceTree = ""; }; 22B6459F2C18DD9D00F99B61 /* Unhider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Unhider.swift; path = ../../InjectionLite/Sources/InjectionImpl/Unhider.swift; sourceTree = ""; }; BB037DF31FAD808B004B267C /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; BB037DF41FAD80D0004B267C /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; BB16653925E9A5F0001407AE /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; BB2A66352E3E753B001EDD38 /* BAZEL.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = BAZEL.md; path = ../BAZEL.md; sourceTree = SOURCE_ROOT; }; BB42F8D22EB626F100FDBBCC /* EasyPointer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EasyPointer.swift; sourceTree = ""; }; BB42F8D32EB626F100FDBBCC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; BB42F8D42EB626F100FDBBCC /* StringIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringIndex.swift; sourceTree = ""; }; BB42F8D52EB626F100FDBBCC /* SwiftArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftArgs.swift; sourceTree = ""; }; BB42F8D62EB626F100FDBBCC /* SwiftAspects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftAspects.swift; sourceTree = ""; }; BB42F8D72EB626F100FDBBCC /* SwiftInterpose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftInterpose.swift; sourceTree = ""; }; BB42F8D82EB626F100FDBBCC /* SwiftInvoke.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftInvoke.swift; sourceTree = ""; }; BB42F8D92EB626F100FDBBCC /* SwiftLifetime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftLifetime.swift; sourceTree = ""; }; BB42F8DA2EB626F100FDBBCC /* SwiftMeta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftMeta.swift; sourceTree = ""; }; BB42F8DB2EB626F100FDBBCC /* SwiftStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftStack.swift; sourceTree = ""; }; BB42F8DC2EB626F100FDBBCC /* SwiftStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftStats.swift; sourceTree = ""; }; BB42F8DD2EB626F100FDBBCC /* SwiftSwizzle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftSwizzle.swift; sourceTree = ""; }; BB42F8DE2EB626F100FDBBCC /* SwiftTrace.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftTrace.h; sourceTree = ""; }; BB42F8DF2EB626F100FDBBCC /* SwiftTrace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTrace.swift; sourceTree = ""; }; BB42F91A2EB639D100FDBBCC /* Demangling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Demangling.swift; sourceTree = ""; }; BB42F91B2EB639D100FDBBCC /* DLKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DLKit.swift; sourceTree = ""; }; BB42F91C2EB639D100FDBBCC /* FileSymbols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSymbols.swift; sourceTree = ""; }; BB42F91D2EB639D100FDBBCC /* ImageSymbols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageSymbols.swift; sourceTree = ""; }; BB42F91E2EB639D100FDBBCC /* Interposing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interposing.swift; sourceTree = ""; }; BB42F91F2EB639D100FDBBCC /* Iterators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Iterators.swift; sourceTree = ""; }; BB42F9272EB639E500FDBBCC /* DLKitC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DLKitC.h; sourceTree = ""; }; BB42F9292EB639E500FDBBCC /* DLKitC.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = DLKitC.c; sourceTree = ""; }; BB42F92A2EB639E500FDBBCC /* trie_dladdr.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = trie_dladdr.mm; sourceTree = ""; }; BB42F92B2EB639E500FDBBCC /* trie_dlops.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = trie_dlops.mm; sourceTree = ""; }; BB42F9302EB63AAF00FDBBCC /* InjectionImplC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = InjectionImplC.h; path = ../InjectionLite/Sources/InjectionImplC/include/InjectionImplC.h; sourceTree = SOURCE_ROOT; }; BB42F9312EB63C2900FDBBCC /* Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Common.swift; sourceTree = ""; }; BB42F9322EB63C2900FDBBCC /* Generics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Generics.swift; sourceTree = ""; }; BB42F9332EB63C2900FDBBCC /* KeyPaths.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPaths.swift; sourceTree = ""; }; BB42F9342EB63C2900FDBBCC /* Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Metadata.swift; sourceTree = ""; }; BB42F9352EB63C2900FDBBCC /* Reloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reloader.swift; sourceTree = ""; }; BB42F9362EB63C2900FDBBCC /* Sweeper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sweeper.swift; sourceTree = ""; }; BB42F9402EB63E3400FDBBCC /* FileWatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileWatcher.swift; sourceTree = ""; }; BB42F9412EB63E3400FDBBCC /* InjectionBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectionBase.swift; sourceTree = ""; }; BB42F9422EB63E3400FDBBCC /* InjectionLite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectionLite.swift; sourceTree = ""; }; BB42F9432EB63E3400FDBBCC /* LogParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogParser.swift; sourceTree = ""; }; BB42F9442EB63E3400FDBBCC /* Recompiler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Recompiler.swift; sourceTree = ""; }; BB42F94F2EB63EFF00FDBBCC /* BazelActionQueryHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BazelActionQueryHandler.swift; sourceTree = ""; }; BB42F9502EB63EFF00FDBBCC /* BazelAQueryParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BazelAQueryParser.swift; sourceTree = ""; }; BB42F9512EB63EFF00FDBBCC /* BazelInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BazelInterface.swift; sourceTree = ""; }; BB42F9522EB63EFF00FDBBCC /* FilenameMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilenameMatcher.swift; sourceTree = ""; }; BB42F9532EB63EFF00FDBBCC /* GitIgnoreParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitIgnoreParser.swift; sourceTree = ""; }; BB4654802C257F110080EC40 /* NextCompiler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextCompiler.swift; sourceTree = ""; }; BB47887C2EB6B6E700464AB4 /* InjectionImplC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InjectionImplC.h; sourceTree = ""; }; BB47887E2EB6B6E700464AB4 /* InjectionBoot.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = InjectionBoot.mm; sourceTree = ""; }; BB4788822EB6B81A00464AB4 /* fishhook.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = fishhook.h; sourceTree = ""; }; BB4788832EB6B81A00464AB4 /* SwiftTrace.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftTrace.h; sourceTree = ""; }; BB4788852EB6B81A00464AB4 /* fast_dladdr.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = fast_dladdr.mm; sourceTree = ""; }; BB4788862EB6B81A00464AB4 /* fishhook.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = fishhook.c; sourceTree = ""; }; BB4788872EB6B81A00464AB4 /* ObjCBridge.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ObjCBridge.mm; sourceTree = ""; }; BB4788882EB6B81A00464AB4 /* SwiftTrace.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SwiftTrace.mm; sourceTree = ""; }; BB4788892EB6B81A00464AB4 /* SwiftTrace-Swift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SwiftTrace-Swift.h"; sourceTree = ""; }; BB47888A2EB6B81A00464AB4 /* Trampolines.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Trampolines.mm; sourceTree = ""; }; BB47888B2EB6B81A00464AB4 /* xt_forwarding_trampoline_arm7.s */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; path = xt_forwarding_trampoline_arm7.s; sourceTree = ""; }; BB47888C2EB6B81A00464AB4 /* xt_forwarding_trampoline_arm64.s */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; path = xt_forwarding_trampoline_arm64.s; sourceTree = ""; }; BB47888D2EB6B81A00464AB4 /* xt_forwarding_trampoline_x64.s */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; path = xt_forwarding_trampoline_x64.s; sourceTree = ""; }; BB47888E2EB6B81A00464AB4 /* xt_forwarding_trampoline_x86.s */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; path = xt_forwarding_trampoline_x86.s; sourceTree = ""; }; BB5155D92CDED44400704C7A /* InjectionHybrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectionHybrid.swift; sourceTree = ""; }; AA0000002F08000000000001 /* ControlServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlServer.swift; sourceTree = ""; }; BB67DBB21FB0CDA8000EAC8A /* SimpleSocket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SimpleSocket.h; path = ../../Sources/InjectionNextC/include/SimpleSocket.h; sourceTree = ""; }; BB67DBB31FB0CDA8000EAC8A /* SimpleSocket.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = SimpleSocket.mm; path = ../../Sources/InjectionNextC/SimpleSocket.mm; sourceTree = ""; }; BB6A56EF2C50E73600C92112 /* Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Defaults.swift; sourceTree = ""; }; BB6A56F22C51154F00C92112 /* InjectionBundle-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "InjectionBundle-Bridging-Header.h"; sourceTree = ""; }; BB85A4242D6B9C5300FA29D0 /* feedcommands */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = feedcommands; sourceTree = BUILT_PRODUCTS_DIR; }; BBA082352D6BC32D00FFFB2F /* FrontendServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrontendServer.swift; sourceTree = ""; }; BBA082372D6BD6CB00FFFB2F /* swift-frontend.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "swift-frontend.sh"; sourceTree = ""; }; BBB040631FB1798A007DDD0A /* ScriptingBridge.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ScriptingBridge.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk/System/Library/Frameworks/ScriptingBridge.framework; sourceTree = DEVELOPER_DIR; }; BBB64DE41FD571300020BE47 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk/usr/lib/libz.tbd; sourceTree = DEVELOPER_DIR; }; BBB64FEB1FD585D50020BE47 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk/System/Library/Frameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; }; BBBEC42D1FC56522004188B3 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/MacOSX.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; BBCA01FE1FB0F10300E45F0F /* InjectionNext.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = InjectionNext.app; sourceTree = BUILT_PRODUCTS_DIR; }; BBCA02011FB0F10300E45F0F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; BBCA02031FB0F10300E45F0F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; BBCA02061FB0F10300E45F0F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; BBCA02081FB0F10400E45F0F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; BBCA020B1FB0F10400E45F0F /* InjectionNext.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = InjectionNext.entitlements; sourceTree = ""; }; BBCA02581FB112F500E45F0F /* App.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = App.icns; sourceTree = ""; }; BBCA025E1FB122C300E45F0F /* InjectionIdle.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = InjectionIdle.tif; sourceTree = ""; }; BBCA025F1FB122C300E45F0F /* InjectionOK.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = InjectionOK.tif; sourceTree = ""; }; BBCCC3B02C73A6AD00E71F81 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/WatchOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; BBDD84132C4FE749000F3124 /* macOSInjection.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = macOSInjection.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; BBDD84182C4FE7E6000F3124 /* InjectionNext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InjectionNext.swift; path = ../../Sources/InjectionNext/InjectionNext.swift; sourceTree = ""; }; BBDD843A2C4FE892000F3124 /* InjectionBoot.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = InjectionBoot.mm; path = ../../InjectionLite/Sources/InjectionImplC/InjectionBoot.mm; sourceTree = ""; }; BBDD84532C4FEB16000F3124 /* TupleRegex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TupleRegex.swift; path = ../../SwiftRegex5/SwiftRegex5.playground/Sources/TupleRegex.swift; sourceTree = ""; }; BBDD84562C4FED60000F3124 /* build_bundles.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = build_bundles.sh; sourceTree = ""; }; BBDD84582C4FF0B9000F3124 /* copy_bundle.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = copy_bundle.sh; sourceTree = ""; }; BBDD84592C4FF645000F3124 /* ClientBoot.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ClientBoot.mm; path = ../../Sources/InjectionNextC/ClientBoot.mm; sourceTree = ""; }; BBDF943C2349277A00334E08 /* InjectionNext-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "InjectionNext-Bridging-Header.h"; sourceTree = ""; }; BBE490D91FB2C643003D41BB /* InjectionBusy.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = InjectionBusy.tif; sourceTree = ""; }; BBE490DA1FB2C643003D41BB /* InjectionError.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = InjectionError.tif; sourceTree = ""; }; BBF6572C2DAFF00200638170 /* NIMBLE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = NIMBLE.md; sourceTree = ""; }; BBF6572D2DAFF00200638170 /* QUICK.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = QUICK.md; sourceTree = ""; }; BBFE1B412ED7C116004E14EE /* SwiftRefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftRefs.swift; sourceTree = ""; }; CEC17029253ED117002E823F /* Experimental.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Experimental.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ BB85A4252D6B9C5300FA29D0 /* feedcommands */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = feedcommands; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ BB85A4212D6B9C5300FA29D0 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; BBCA01FB1FB0F10300E45F0F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2217BE812C0A65C20032A832 /* Fortify in Frameworks */, 22E3D98D2C19009900BB234E /* DLKitC in Frameworks */, BBB64FEC1FD585D50020BE47 /* WebKit.framework in Frameworks */, 22E3D98B2C19009900BB234E /* DLKit in Frameworks */, BBB040641FB17A6C007DDD0A /* ScriptingBridge.framework in Frameworks */, 224E57FB2C08978F00B71C79 /* SwiftRegex in Frameworks */, BBB64DE51FD571310020BE47 /* libz.tbd in Frameworks */, BB2D41AC2DAD14EA0073C203 /* Popen in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; BBDD84102C4FE749000F3124 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( BB0CECB02C73C1610000A4E6 /* Quick in Frameworks */, BBEB87122C74FD930044578A /* Quick in Frameworks */, BB0CECB32C73C1890000A4E6 /* Nimble in Frameworks */, BBCCC3B72C73B6FD00E71F81 /* XCTest.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ BB42F8E02EB626F100FDBBCC /* SwiftTrace */ = { isa = PBXGroup; children = ( BB47888F2EB6B81A00464AB4 /* SwiftTraceGuts */, BB42F8D22EB626F100FDBBCC /* EasyPointer.swift */, BB42F8D32EB626F100FDBBCC /* Info.plist */, BB42F8D42EB626F100FDBBCC /* StringIndex.swift */, BB42F8D52EB626F100FDBBCC /* SwiftArgs.swift */, BB42F8D62EB626F100FDBBCC /* SwiftAspects.swift */, BB42F8D72EB626F100FDBBCC /* SwiftInterpose.swift */, BB42F8D82EB626F100FDBBCC /* SwiftInvoke.swift */, BB42F8D92EB626F100FDBBCC /* SwiftLifetime.swift */, BB42F8DA2EB626F100FDBBCC /* SwiftMeta.swift */, BBFE1B412ED7C116004E14EE /* SwiftRefs.swift */, BB42F8DB2EB626F100FDBBCC /* SwiftStack.swift */, BB42F8DC2EB626F100FDBBCC /* SwiftStats.swift */, BB42F8DD2EB626F100FDBBCC /* SwiftSwizzle.swift */, BB42F8DE2EB626F100FDBBCC /* SwiftTrace.h */, BB42F8DF2EB626F100FDBBCC /* SwiftTrace.swift */, ); name = SwiftTrace; path = ../SwiftTrace/SwiftTrace; sourceTree = SOURCE_ROOT; }; BB42F9202EB639D100FDBBCC /* DLKit */ = { isa = PBXGroup; children = ( BB42F92C2EB639E500FDBBCC /* DLKitC */, BB42F91A2EB639D100FDBBCC /* Demangling.swift */, BB42F91B2EB639D100FDBBCC /* DLKit.swift */, BB42F91C2EB639D100FDBBCC /* FileSymbols.swift */, BB42F91D2EB639D100FDBBCC /* ImageSymbols.swift */, BB42F91E2EB639D100FDBBCC /* Interposing.swift */, BB42F91F2EB639D100FDBBCC /* Iterators.swift */, ); name = DLKit; path = ../DLKit/Sources/DLKit; sourceTree = SOURCE_ROOT; }; BB42F9282EB639E500FDBBCC /* include */ = { isa = PBXGroup; children = ( BB42F9272EB639E500FDBBCC /* DLKitC.h */, ); path = include; sourceTree = ""; }; BB42F92C2EB639E500FDBBCC /* DLKitC */ = { isa = PBXGroup; children = ( BB42F9282EB639E500FDBBCC /* include */, BB42F9292EB639E500FDBBCC /* DLKitC.c */, BB42F92A2EB639E500FDBBCC /* trie_dladdr.mm */, BB42F92B2EB639E500FDBBCC /* trie_dlops.mm */, ); name = DLKitC; path = ../DLKit/Sources/DLKitC; sourceTree = SOURCE_ROOT; }; BB42F9372EB63C2900FDBBCC /* InjectionImpl */ = { isa = PBXGroup; children = ( BB47887F2EB6B6E700464AB4 /* InjectionImplC */, BB42F9312EB63C2900FDBBCC /* Common.swift */, BB42F9322EB63C2900FDBBCC /* Generics.swift */, BB42F9332EB63C2900FDBBCC /* KeyPaths.swift */, BB42F9342EB63C2900FDBBCC /* Metadata.swift */, BB42F9352EB63C2900FDBBCC /* Reloader.swift */, BB42F9362EB63C2900FDBBCC /* Sweeper.swift */, ); name = InjectionImpl; path = ../InjectionLite/Sources/InjectionImpl; sourceTree = SOURCE_ROOT; }; BB42F9452EB63E3400FDBBCC /* InjectionLite */ = { isa = PBXGroup; children = ( BB42F9402EB63E3400FDBBCC /* FileWatcher.swift */, BB42F9412EB63E3400FDBBCC /* InjectionBase.swift */, BB42F9422EB63E3400FDBBCC /* InjectionLite.swift */, BB42F9432EB63E3400FDBBCC /* LogParser.swift */, BB42F9442EB63E3400FDBBCC /* Recompiler.swift */, ); name = InjectionLite; path = ../InjectionLite/Sources/InjectionLite; sourceTree = SOURCE_ROOT; }; BB42F9542EB63EFF00FDBBCC /* InjectionBazel */ = { isa = PBXGroup; children = ( BB42F94F2EB63EFF00FDBBCC /* BazelActionQueryHandler.swift */, BB42F9502EB63EFF00FDBBCC /* BazelAQueryParser.swift */, BB42F9512EB63EFF00FDBBCC /* BazelInterface.swift */, BB42F9522EB63EFF00FDBBCC /* FilenameMatcher.swift */, BB42F9532EB63EFF00FDBBCC /* GitIgnoreParser.swift */, ); name = InjectionBazel; path = ../InjectionLite/Sources/InjectionBazel; sourceTree = SOURCE_ROOT; }; BB439B5C1FABA64300B4F50B = { isa = PBXGroup; children = ( BB037DF41FAD80D0004B267C /* LICENSE */, BB2A66352E3E753B001EDD38 /* BAZEL.md */, BBF6572D2DAFF00200638170 /* QUICK.md */, BBF6572C2DAFF00200638170 /* NIMBLE.md */, BB037DF31FAD808B004B267C /* README.md */, BBCA01FF1FB0F10300E45F0F /* InjectionNext */, BBDD84172C4FE79E000F3124 /* InjectionBundle */, BB85A4252D6B9C5300FA29D0 /* feedcommands */, BB439B661FABA64300B4F50B /* Products */, BBB040621FB1798A007DDD0A /* Frameworks */, ); indentWidth = 4; sourceTree = ""; tabWidth = 4; }; BB439B661FABA64300B4F50B /* Products */ = { isa = PBXGroup; children = ( BBCA01FE1FB0F10300E45F0F /* InjectionNext.app */, BBDD84132C4FE749000F3124 /* macOSInjection.bundle */, BB85A4242D6B9C5300FA29D0 /* feedcommands */, ); name = Products; sourceTree = ""; }; BB47887D2EB6B6E700464AB4 /* include */ = { isa = PBXGroup; children = ( BB47887C2EB6B6E700464AB4 /* InjectionImplC.h */, ); path = include; sourceTree = ""; }; BB47887F2EB6B6E700464AB4 /* InjectionImplC */ = { isa = PBXGroup; children = ( BB47887D2EB6B6E700464AB4 /* include */, BB47887E2EB6B6E700464AB4 /* InjectionBoot.mm */, ); name = InjectionImplC; path = ../InjectionLite/Sources/InjectionImplC; sourceTree = SOURCE_ROOT; }; BB4788842EB6B81A00464AB4 /* include */ = { isa = PBXGroup; children = ( BB4788822EB6B81A00464AB4 /* fishhook.h */, BB4788832EB6B81A00464AB4 /* SwiftTrace.h */, ); path = include; sourceTree = ""; }; BB47888F2EB6B81A00464AB4 /* SwiftTraceGuts */ = { isa = PBXGroup; children = ( BB4788842EB6B81A00464AB4 /* include */, BB4788852EB6B81A00464AB4 /* fast_dladdr.mm */, BB4788862EB6B81A00464AB4 /* fishhook.c */, BB4788872EB6B81A00464AB4 /* ObjCBridge.mm */, BB4788882EB6B81A00464AB4 /* SwiftTrace.mm */, BB4788892EB6B81A00464AB4 /* SwiftTrace-Swift.h */, BB47888A2EB6B81A00464AB4 /* Trampolines.mm */, BB47888B2EB6B81A00464AB4 /* xt_forwarding_trampoline_arm7.s */, BB47888C2EB6B81A00464AB4 /* xt_forwarding_trampoline_arm64.s */, BB47888D2EB6B81A00464AB4 /* xt_forwarding_trampoline_x64.s */, BB47888E2EB6B81A00464AB4 /* xt_forwarding_trampoline_x86.s */, ); name = SwiftTraceGuts; path = ../SwiftTrace/SwiftTraceGuts; sourceTree = SOURCE_ROOT; }; BBB040621FB1798A007DDD0A /* Frameworks */ = { isa = PBXGroup; children = ( BBCCC3B02C73A6AD00E71F81 /* XCTest.framework */, BBB64FEB1FD585D50020BE47 /* WebKit.framework */, BBB64DE41FD571300020BE47 /* libz.tbd */, BBBEC42D1FC56522004188B3 /* XCTest.framework */, BBB040631FB1798A007DDD0A /* ScriptingBridge.framework */, ); name = Frameworks; sourceTree = ""; }; BBCA01FF1FB0F10300E45F0F /* InjectionNext */ = { isa = PBXGroup; children = ( BB16653925E9A5F0001407AE /* main.m */, BBCA02011FB0F10300E45F0F /* AppDelegate.swift */, BB4654802C257F110080EC40 /* NextCompiler.swift */, 224E57FF2C08BC0E00B71C79 /* MonitorXcode.swift */, 224E57FD2C08BBE300B71C79 /* InjectionServer.swift */, BB5155D92CDED44400704C7A /* InjectionHybrid.swift */, BBA082352D6BC32D00FFFB2F /* FrontendServer.swift */, CEC17029253ED117002E823F /* Experimental.swift */, BB6A56EF2C50E73600C92112 /* Defaults.swift */, 22B6459F2C18DD9D00F99B61 /* Unhider.swift */, AA0000002F08000000000001 /* ControlServer.swift */, 224E57FC2C08BAE200B71C79 /* InjectionClient.h */, BB67DBB21FB0CDA8000EAC8A /* SimpleSocket.h */, BB67DBB31FB0CDA8000EAC8A /* SimpleSocket.mm */, BBCA02031FB0F10300E45F0F /* Assets.xcassets */, BBCA025E1FB122C300E45F0F /* InjectionIdle.tif */, 222BC9442C157C3200780A41 /* InjectionReady.tif */, BBCA025F1FB122C300E45F0F /* InjectionOK.tif */, BBE490D91FB2C643003D41BB /* InjectionBusy.tif */, BBE490DA1FB2C643003D41BB /* InjectionError.tif */, BBCA02051FB0F10300E45F0F /* MainMenu.xib */, BBCA02081FB0F10400E45F0F /* Info.plist */, BBCA02581FB112F500E45F0F /* App.icns */, BBCA020B1FB0F10400E45F0F /* InjectionNext.entitlements */, BBDF943C2349277A00334E08 /* InjectionNext-Bridging-Header.h */, BB42F9542EB63EFF00FDBBCC /* InjectionBazel */, BBDD84562C4FED60000F3124 /* build_bundles.sh */, BBDD84582C4FF0B9000F3124 /* copy_bundle.sh */, BBA082372D6BD6CB00FFFB2F /* swift-frontend.sh */, ); path = InjectionNext; sourceTree = ""; }; BBDD84172C4FE79E000F3124 /* InjectionBundle */ = { isa = PBXGroup; children = ( BB42F9302EB63AAF00FDBBCC /* InjectionImplC.h */, BBDD84592C4FF645000F3124 /* ClientBoot.mm */, BBDD843A2C4FE892000F3124 /* InjectionBoot.mm */, BBDD84182C4FE7E6000F3124 /* InjectionNext.swift */, BBDD84532C4FEB16000F3124 /* TupleRegex.swift */, BB42F9452EB63E3400FDBBCC /* InjectionLite */, BB42F9372EB63C2900FDBBCC /* InjectionImpl */, BB42F9202EB639D100FDBBCC /* DLKit */, BB42F8E02EB626F100FDBBCC /* SwiftTrace */, BB6A56F22C51154F00C92112 /* InjectionBundle-Bridging-Header.h */, ); path = InjectionBundle; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ BB85A4232D6B9C5300FA29D0 /* feedcommands */ = { isa = PBXNativeTarget; buildConfigurationList = BB85A42A2D6B9C5300FA29D0 /* Build configuration list for PBXNativeTarget "feedcommands" */; buildPhases = ( BBDD999A2F4AF61000E88DFB /* ShellScript */, BB85A4202D6B9C5300FA29D0 /* Sources */, BB85A4212D6B9C5300FA29D0 /* Frameworks */, BB85A4222D6B9C5300FA29D0 /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); fileSystemSynchronizedGroups = ( BB85A4252D6B9C5300FA29D0 /* feedcommands */, ); name = feedcommands; packageProductDependencies = ( ); productName = feedcommands; productReference = BB85A4242D6B9C5300FA29D0 /* feedcommands */; productType = "com.apple.product-type.tool"; }; BBCA01FD1FB0F10300E45F0F /* InjectionNext */ = { isa = PBXNativeTarget; buildConfigurationList = BBCA020C1FB0F10400E45F0F /* Build configuration list for PBXNativeTarget "InjectionNext" */; buildPhases = ( 22240ECB2C0C7178004DCBAD /* Run Script */, BBCA01FA1FB0F10300E45F0F /* Sources */, BBCA01FB1FB0F10300E45F0F /* Frameworks */, BBCA01FC1FB0F10300E45F0F /* Resources */, BB6224721FC5BA6F00AD7A3A /* CopyFiles */, BB6C87DB2520D0D3005AFCFC /* Embed Frameworks */, BBDD84572C4FED87000F3124 /* ShellScript */, ); buildRules = ( ); dependencies = ( BB85A42D2D6B9CA200FA29D0 /* PBXTargetDependency */, BBDD843E2C4FE8C5000F3124 /* PBXTargetDependency */, ); name = InjectionNext; packageProductDependencies = ( 224E57FA2C08978F00B71C79 /* SwiftRegex */, 2217BE802C0A65C20032A832 /* Fortify */, 22E3D98A2C19009900BB234E /* DLKit */, 22E3D98C2C19009900BB234E /* DLKitC */, BB2D41AB2DAD14EA0073C203 /* Popen */, ); productName = InjectionIII; productReference = BBCA01FE1FB0F10300E45F0F /* InjectionNext.app */; productType = "com.apple.product-type.application"; }; BBDD84122C4FE749000F3124 /* InjectionBundle */ = { isa = PBXNativeTarget; buildConfigurationList = BBDD84162C4FE749000F3124 /* Build configuration list for PBXNativeTarget "InjectionBundle" */; buildPhases = ( BBDD840F2C4FE749000F3124 /* Sources */, BBDD84102C4FE749000F3124 /* Frameworks */, BBDD84112C4FE749000F3124 /* Resources */, ); buildRules = ( ); dependencies = ( BB2D41AE2DAD15220073C203 /* PBXTargetDependency */, ); name = InjectionBundle; productName = InjectionBundle; productReference = BBDD84132C4FE749000F3124 /* macOSInjection.bundle */; productType = "com.apple.product-type.bundle"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ BB439B5D1FABA64300B4F50B /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1150; LastUpgradeCheck = 1640; ORGANIZATIONNAME = "John Holdsworth"; TargetAttributes = { BB85A4232D6B9C5300FA29D0 = { CreatedOnToolsVersion = 16.3; }; BBCA01FD1FB0F10300E45F0F = { CreatedOnToolsVersion = 9.0.1; LastSwiftMigration = 1110; ProvisioningStyle = Manual; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; }; }; }; BBDD84122C4FE749000F3124 = { CreatedOnToolsVersion = 15.2; }; }; }; buildConfigurationList = BB439B601FABA64300B4F50B /* Build configuration list for PBXProject "InjectionNext" */; compatibilityVersion = "Xcode 8.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = BB439B5C1FABA64300B4F50B; packageReferences = ( 224E57F92C08978F00B71C79 /* XCRemoteSwiftPackageReference "SwiftRegex5" */, 2217BE7F2C0A65C20032A832 /* XCRemoteSwiftPackageReference "Fortify" */, 22E3D9892C19009900BB234E /* XCRemoteSwiftPackageReference "DLKit" */, BB0CECB12C73C1890000A4E6 /* XCRemoteSwiftPackageReference "Nimble" */, BBEB87102C74FD930044578A /* XCRemoteSwiftPackageReference "Quick" */, BB2D41A42DAD10110073C203 /* XCRemoteSwiftPackageReference "Popen" */, ); productRefGroup = BB439B661FABA64300B4F50B /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( BBCA01FD1FB0F10300E45F0F /* InjectionNext */, BBDD84122C4FE749000F3124 /* InjectionBundle */, BB85A4232D6B9C5300FA29D0 /* feedcommands */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ BBCA01FC1FB0F10300E45F0F /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( BB4788812EB6B71600464AB4 /* InjectionImplC.h in Resources */, BB85A42B2D6B9C7B00FA29D0 /* feedcommands in Resources */, BBDD843F2C4FE8D5000F3124 /* macOSInjection.bundle in Resources */, BBA082382D6BD6CB00FFFB2F /* swift-frontend.sh in Resources */, BB6A56F12C50E79800C92112 /* copy_bundle.sh in Resources */, BBB64DC61FD450B40020BE47 /* LICENSE in Resources */, BB2A66362E3E753B001EDD38 /* BAZEL.md in Resources */, BBB64DC51FD450AF0020BE47 /* README.md in Resources */, BBE490DC1FB2C643003D41BB /* InjectionError.tif in Resources */, BBCA02591FB112F500E45F0F /* App.icns in Resources */, BBCA02601FB122C300E45F0F /* InjectionIdle.tif in Resources */, BBF6572E2DAFF00200638170 /* NIMBLE.md in Resources */, BBF6572F2DAFF00200638170 /* QUICK.md in Resources */, BBCA02611FB122C300E45F0F /* InjectionOK.tif in Resources */, BBCA02041FB0F10300E45F0F /* Assets.xcassets in Resources */, 222BC9452C157C3200780A41 /* InjectionReady.tif in Resources */, BBCA02071FB0F10300E45F0F /* MainMenu.xib in Resources */, BBE490DB1FB2C643003D41BB /* InjectionBusy.tif in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; BBDD84112C4FE749000F3124 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 22240ECB2C0C7178004DCBAD /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); name = "Run Script"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nif [ \"$ACTION\" == \"install\" ]; then exit 0; fi\nbuildNumber=$(/usr/libexec/PlistBuddy -c \"Print CFBundleVersion\" \"${PROJECT_DIR}/${INFOPLIST_FILE}\")\nbuildNumber=$(($buildNumber + 1))\nchmod +w \"${PROJECT_DIR}/${INFOPLIST_FILE}\"\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"${PROJECT_DIR}/${INFOPLIST_FILE}\"\n"; }; BBDD84572C4FED87000F3124 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 12; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nInjectionNext/build_bundles.sh\n"; }; BBDD999A2F4AF61000E88DFB /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "perl -e 'print \"#define COMMANDS_VERSION @{[1_000_000 + int(rand() * ((1 << 31)) - 1_000_000)]}\\n\"' >/tmp/InjectionNextSalt.h\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ BB85A4202D6B9C5300FA29D0 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( BB85A42E2D6BA13B00FA29D0 /* SimpleSocket.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; BBCA01FA1FB0F10300E45F0F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( BB6A56F02C50E73600C92112 /* Defaults.swift in Sources */, BB42F93F2EB63CD800FDBBCC /* Common.swift in Sources */, BB42F94C2EB63E4F00FDBBCC /* LogParser.swift in Sources */, 224E58002C08BC0E00B71C79 /* MonitorXcode.swift in Sources */, BB42F94D2EB63E5600FDBBCC /* Recompiler.swift in Sources */, 229127F12C1EEF69005D7625 /* Experimental.swift in Sources */, BBA082362D6BC33500FFFB2F /* FrontendServer.swift in Sources */, BBCA022A1FB0F64800E45F0F /* SimpleSocket.mm in Sources */, BB42F94E2EB63E7700FDBBCC /* InjectionBase.swift in Sources */, BB16653A25E9A5F2001407AE /* main.m in Sources */, BBCA02021FB0F10300E45F0F /* AppDelegate.swift in Sources */, BB5155DA2CDED44F00704C7A /* InjectionHybrid.swift in Sources */, BB42F94B2EB63E3F00FDBBCC /* FileWatcher.swift in Sources */, BB4654812C257F110080EC40 /* NextCompiler.swift in Sources */, BB42F95A2EB63EFF00FDBBCC /* BazelInterface.swift in Sources */, BB42F95B2EB63EFF00FDBBCC /* GitIgnoreParser.swift in Sources */, BB42F95C2EB63EFF00FDBBCC /* BazelActionQueryHandler.swift in Sources */, BB42F95D2EB63EFF00FDBBCC /* BazelAQueryParser.swift in Sources */, BB42F95E2EB63EFF00FDBBCC /* FilenameMatcher.swift in Sources */, 224E57FE2C08BBE300B71C79 /* InjectionServer.swift in Sources */, 22B645A02C18DD9D00F99B61 /* Unhider.swift in Sources */, AA0000012F08000000000001 /* ControlServer.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; BBDD840F2C4FE749000F3124 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( BBDD84422C4FEA4E000F3124 /* InjectionNext.swift in Sources */, BB42F8E12EB626F100FDBBCC /* SwiftAspects.swift in Sources */, BB42F8E22EB626F100FDBBCC /* SwiftInterpose.swift in Sources */, BB42F8E32EB626F100FDBBCC /* SwiftTrace.swift in Sources */, BB42F8E42EB626F100FDBBCC /* SwiftSwizzle.swift in Sources */, BB42F8E52EB626F100FDBBCC /* SwiftArgs.swift in Sources */, BB42F8E62EB626F100FDBBCC /* SwiftInvoke.swift in Sources */, BB42F8E72EB626F100FDBBCC /* EasyPointer.swift in Sources */, BB42F8E82EB626F100FDBBCC /* SwiftStack.swift in Sources */, BB42F9552EB63EFF00FDBBCC /* BazelInterface.swift in Sources */, BB4788902EB6B81A00464AB4 /* fast_dladdr.mm in Sources */, BB4788912EB6B81A00464AB4 /* xt_forwarding_trampoline_arm64.s in Sources */, BB4788922EB6B81A00464AB4 /* xt_forwarding_trampoline_x64.s in Sources */, BB4788932EB6B81A00464AB4 /* ObjCBridge.mm in Sources */, BB4788942EB6B81A00464AB4 /* Trampolines.mm in Sources */, BB4788952EB6B81A00464AB4 /* xt_forwarding_trampoline_arm7.s in Sources */, BB4788962EB6B81A00464AB4 /* SwiftTrace.mm in Sources */, BB4788972EB6B81A00464AB4 /* xt_forwarding_trampoline_x86.s in Sources */, BB4788982EB6B81A00464AB4 /* fishhook.c in Sources */, BB42F9562EB63EFF00FDBBCC /* GitIgnoreParser.swift in Sources */, BB42F9572EB63EFF00FDBBCC /* BazelActionQueryHandler.swift in Sources */, BB42F9582EB63EFF00FDBBCC /* BazelAQueryParser.swift in Sources */, BB42F9592EB63EFF00FDBBCC /* FilenameMatcher.swift in Sources */, BB42F8E92EB626F100FDBBCC /* SwiftStats.swift in Sources */, BB42F9212EB639D100FDBBCC /* DLKit.swift in Sources */, BB42F9222EB639D100FDBBCC /* Iterators.swift in Sources */, BB42F9232EB639D100FDBBCC /* Interposing.swift in Sources */, BBFE1B422ED7C116004E14EE /* SwiftRefs.swift in Sources */, BB42F9242EB639D100FDBBCC /* ImageSymbols.swift in Sources */, BB42F9252EB639D100FDBBCC /* Demangling.swift in Sources */, BB42F9262EB639D100FDBBCC /* FileSymbols.swift in Sources */, BB42F8EA2EB626F100FDBBCC /* StringIndex.swift in Sources */, BB42F8EB2EB626F100FDBBCC /* SwiftLifetime.swift in Sources */, BB42F9462EB63E3400FDBBCC /* InjectionBase.swift in Sources */, BB42F9472EB63E3400FDBBCC /* Recompiler.swift in Sources */, BB42F9482EB63E3400FDBBCC /* InjectionLite.swift in Sources */, BB42F9492EB63E3400FDBBCC /* LogParser.swift in Sources */, BB42F94A2EB63E3400FDBBCC /* FileWatcher.swift in Sources */, BB42F8EC2EB626F100FDBBCC /* SwiftMeta.swift in Sources */, BB4788802EB6B6E700464AB4 /* InjectionBoot.mm in Sources */, BBDD845A2C4FF646000F3124 /* ClientBoot.mm in Sources */, BB42F92D2EB639E500FDBBCC /* DLKitC.c in Sources */, BB42F92E2EB639E500FDBBCC /* trie_dladdr.mm in Sources */, BB42F92F2EB639E500FDBBCC /* trie_dlops.mm in Sources */, BBDD84542C4FEB16000F3124 /* TupleRegex.swift in Sources */, BBDD84552C4FEC98000F3124 /* SimpleSocket.mm in Sources */, BB42F9382EB63C2900FDBBCC /* KeyPaths.swift in Sources */, BB42F9392EB63C2900FDBBCC /* Reloader.swift in Sources */, BB52AD552F1501F500297CD9 /* Unhider.swift in Sources */, BB42F93A2EB63C2900FDBBCC /* Generics.swift in Sources */, BB42F93B2EB63C2900FDBBCC /* Metadata.swift in Sources */, BB42F93C2EB63C2900FDBBCC /* Common.swift in Sources */, BB42F93D2EB63C2900FDBBCC /* Sweeper.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ BB2D41AE2DAD15220073C203 /* PBXTargetDependency */ = { isa = PBXTargetDependency; productRef = BB2D41AD2DAD15220073C203 /* Popen */; }; BB85A42D2D6B9CA200FA29D0 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = BB85A4232D6B9C5300FA29D0 /* feedcommands */; targetProxy = BB85A42C2D6B9CA200FA29D0 /* PBXContainerItemProxy */; }; BBDD843E2C4FE8C5000F3124 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = BBDD84122C4FE749000F3124 /* InjectionBundle */; targetProxy = BBDD843D2C4FE8C5000F3124 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ BBCA02051FB0F10300E45F0F /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( BBCA02061FB0F10300E45F0F /* Base */, ); name = MainMenu.xib; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ BB439B821FABA64300B4F50B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 9V5A8WE85E; EMIT_FRONTEND_COMMAND_LINES = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = $PLATFORM_DIR/Developer/Library/Frameworks; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = $SRCROOT/HotReloading/Sources/HotReloadingGuts/include; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(TOOLCHAIN_DIR)/usr/lib/swift/$(SWIFT_PLATFORM_TARGET_PREFIX)", $PLATFORM_DIR/Developer/Library/Frameworks, ); MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-expression-type-checking=150"; PRODUCT_NAME = macOSInjection; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; BB439B831FABA64300B4F50B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 9V5A8WE85E; EMIT_FRONTEND_COMMAND_LINES = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = $PLATFORM_DIR/Developer/Library/Frameworks; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = $SRCROOT/HotReloading/Sources/HotReloadingGuts/include; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(TOOLCHAIN_DIR)/usr/lib/swift/$(SWIFT_PLATFORM_TARGET_PREFIX)", $PLATFORM_DIR/Developer/Library/Frameworks, ); MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = macOSInjection; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; VALIDATE_PRODUCT = YES; }; name = Release; }; BB85A4282D6B9C5300FA29D0 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CODE_SIGN_STYLE = Automatic; DEAD_CODE_STRIPPING = YES; ENABLE_HARDENED_RUNTIME = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MACOSX_DEPLOYMENT_TARGET = 15.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SKIP_INSTALL = YES; }; name = Debug; }; BB85A4292D6B9C5300FA29D0 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CODE_SIGN_STYLE = Automatic; DEAD_CODE_STRIPPING = YES; ENABLE_HARDENED_RUNTIME = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MACOSX_DEPLOYMENT_TARGET = 15.1; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SKIP_INSTALL = YES; }; name = Release; }; BBCA020D1FB0F10400E45F0F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = InjectionNext/InjectionNext.entitlements; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; ENABLE_DEBUG_DYLIB = NO; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = InjectionNext/Info.plist; LD_RUNPATH_SEARCH_PATHS = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.13; MARKETING_VERSION = 1.6.0; PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.InjectionNext; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) INJECTION_NEXT_APP"; SWIFT_OBJC_BRIDGING_HEADER = "InjectionNext/InjectionNext-Bridging-Header.h"; }; name = Debug; }; BBCA020E1FB0F10400E45F0F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = InjectionNext/InjectionNext.entitlements; CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; ENABLE_DEBUG_DYLIB = NO; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = InjectionNext/Info.plist; LD_RUNPATH_SEARCH_PATHS = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.13; MARKETING_VERSION = 1.6.0; PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.InjectionNext; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) INJECTION_NEXT_APP"; SWIFT_OBJC_BRIDGING_HEADER = "InjectionNext/InjectionNext-Bridging-Header.h"; }; name = Release; }; BBDD84142C4FE749000F3124 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = c17; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 John Holdsworth. All rights reserved."; INFOPLIST_KEY_NSPrincipalClass = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; LD_DYLIB_INSTALL_NAME = "/Applications/InjectionNext.app/Contents/Resources/lib$(PLATFORM_NAME)Injection.dylib"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/lib, /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks, /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/PrivateFrameworks, ); LIBRARY_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/usr/lib"; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MACH_O_TYPE = mh_dylib; MACOSX_DEPLOYMENT_TARGET = 10.15; MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( "-lXCTestSwiftSupport", "$(BUILT_PRODUCTS_DIR)/Popen.o", ); PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.InjectionBundle; PRODUCT_MODULE_NAME = InjectionBundle; PRODUCT_NAME = macOSInjection; SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = "InjectionBundle/InjectionBundle-Bridging-Header.h"; WRAPPER_EXTENSION = bundle; }; name = Debug; }; BBDD84152C4FE749000F3124 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = c17; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 John Holdsworth. All rights reserved."; INFOPLIST_KEY_NSPrincipalClass = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; LD_DYLIB_INSTALL_NAME = "/Applications/InjectionNext.app/Contents/Resources/lib$(PLATFORM_NAME)Injection.dylib"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/lib, /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks, /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/PrivateFrameworks, ); LIBRARY_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/usr/lib"; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MACH_O_TYPE = mh_dylib; MACOSX_DEPLOYMENT_TARGET = 10.15; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( "-lXCTestSwiftSupport", "$(BUILT_PRODUCTS_DIR)/Popen.o", ); PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.InjectionBundle; PRODUCT_MODULE_NAME = InjectionBundle; PRODUCT_NAME = macOSInjection; SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = "InjectionBundle/InjectionBundle-Bridging-Header.h"; WRAPPER_EXTENSION = bundle; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ BB439B601FABA64300B4F50B /* Build configuration list for PBXProject "InjectionNext" */ = { isa = XCConfigurationList; buildConfigurations = ( BB439B821FABA64300B4F50B /* Debug */, BB439B831FABA64300B4F50B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; BB85A42A2D6B9C5300FA29D0 /* Build configuration list for PBXNativeTarget "feedcommands" */ = { isa = XCConfigurationList; buildConfigurations = ( BB85A4282D6B9C5300FA29D0 /* Debug */, BB85A4292D6B9C5300FA29D0 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; BBCA020C1FB0F10400E45F0F /* Build configuration list for PBXNativeTarget "InjectionNext" */ = { isa = XCConfigurationList; buildConfigurations = ( BBCA020D1FB0F10400E45F0F /* Debug */, BBCA020E1FB0F10400E45F0F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; BBDD84162C4FE749000F3124 /* Build configuration list for PBXNativeTarget "InjectionBundle" */ = { isa = XCConfigurationList; buildConfigurations = ( BBDD84142C4FE749000F3124 /* Debug */, BBDD84152C4FE749000F3124 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 2217BE7F2C0A65C20032A832 /* XCRemoteSwiftPackageReference "Fortify" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/johnno1962/Fortify"; requirement = { kind = upToNextMajorVersion; minimumVersion = 2.1.12; }; }; 224E57F92C08978F00B71C79 /* XCRemoteSwiftPackageReference "SwiftRegex5" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/johnno1962/SwiftRegex5"; requirement = { kind = upToNextMajorVersion; minimumVersion = 6.3.0; }; }; 22E3D9892C19009900BB234E /* XCRemoteSwiftPackageReference "DLKit" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/johnno1962/DLKit"; requirement = { kind = upToNextMajorVersion; minimumVersion = 3.5.7; }; }; BB0CECAE2C73C1610000A4E6 /* XCRemoteSwiftPackageReference "Quick" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Quick/Quick"; requirement = { branch = main; kind = branch; }; }; BB0CECB12C73C1890000A4E6 /* XCRemoteSwiftPackageReference "Nimble" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Quick/Nimble.git"; requirement = { kind = upToNextMinorVersion; minimumVersion = 13.2.0; }; }; BB2D41A42DAD10110073C203 /* XCRemoteSwiftPackageReference "Popen" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/johnno1962/Popen"; requirement = { kind = upToNextMajorVersion; minimumVersion = 2.2.1; }; }; BBEB87102C74FD930044578A /* XCRemoteSwiftPackageReference "Quick" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/johnno1962/Quick"; requirement = { branch = uncached; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 2217BE802C0A65C20032A832 /* Fortify */ = { isa = XCSwiftPackageProductDependency; package = 2217BE7F2C0A65C20032A832 /* XCRemoteSwiftPackageReference "Fortify" */; productName = Fortify; }; 224E57FA2C08978F00B71C79 /* SwiftRegex */ = { isa = XCSwiftPackageProductDependency; package = 224E57F92C08978F00B71C79 /* XCRemoteSwiftPackageReference "SwiftRegex5" */; productName = SwiftRegex; }; 22E3D98A2C19009900BB234E /* DLKit */ = { isa = XCSwiftPackageProductDependency; package = 22E3D9892C19009900BB234E /* XCRemoteSwiftPackageReference "DLKit" */; productName = DLKit; }; 22E3D98C2C19009900BB234E /* DLKitC */ = { isa = XCSwiftPackageProductDependency; package = 22E3D9892C19009900BB234E /* XCRemoteSwiftPackageReference "DLKit" */; productName = DLKitC; }; BB0CECAF2C73C1610000A4E6 /* Quick */ = { isa = XCSwiftPackageProductDependency; package = BB0CECAE2C73C1610000A4E6 /* XCRemoteSwiftPackageReference "Quick" */; productName = Quick; }; BB0CECB22C73C1890000A4E6 /* Nimble */ = { isa = XCSwiftPackageProductDependency; package = BB0CECB12C73C1890000A4E6 /* XCRemoteSwiftPackageReference "Nimble" */; productName = Nimble; }; BB2D41AB2DAD14EA0073C203 /* Popen */ = { isa = XCSwiftPackageProductDependency; package = BB2D41A42DAD10110073C203 /* XCRemoteSwiftPackageReference "Popen" */; productName = Popen; }; BB2D41AD2DAD15220073C203 /* Popen */ = { isa = XCSwiftPackageProductDependency; package = BB2D41A42DAD10110073C203 /* XCRemoteSwiftPackageReference "Popen" */; productName = Popen; }; BBEB87112C74FD930044578A /* Quick */ = { isa = XCSwiftPackageProductDependency; package = BBEB87102C74FD930044578A /* XCRemoteSwiftPackageReference "Quick" */; productName = Quick; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = BB439B5D1FABA64300B4F50B /* Project object */; } ================================================ FILE: App/InjectionNext.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: App/InjectionNext.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: App/InjectionNext.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ ================================================ FILE: App/InjectionNext.xcodeproj/xcshareddata/xcschemes/InjectionNext.xcscheme ================================================ ================================================ FILE: App/LICENSE ================================================ MIT License Copyright (c) 2024 John Holdsworth Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: App/NIMBLE.md ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2016 Quick Team Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: App/QUICK.md ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2014, Quick Team Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: App/feedcommands/main.mm ================================================ // // main.mm // feedcommands // // Created by John Holdsworth on 23/02/2025. // Copyright © 2025 John Holdsworth. All rights reserved. // #import #import "/tmp/InjectionNextSalt.h" #import "../../../InjectionNext/Sources/InjectionNextC/include/SimpleSocket.h" #import "../../../InjectionNext/Sources/InjectionNextC/include/InjectionClient.h" @interface FeedSocket: SimpleSocket @end @implementation FeedSocket { FILE *out; } + (int)error:(NSString *)message { if (isatty(STDIN_FILENO)) printf([NSString stringWithFormat:@"%@/%@\n", self, message].UTF8String, strerror(errno)); return 1; } - (BOOL)writeBytes:(const void *)buffer length:(size_t)length cmd:(SEL)cmd { if (!out) out = fdopen(clientSocket, "w"); return fwrite(buffer, 1, length, out) == length; } - (void)dealloc { if (out) fclose(out); } @end int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... if (SimpleSocket *app = [FeedSocket connectTo:@COMMANDS_PORT]) { [app writeInt:COMMANDS_VERSION]; [app writeString:NSHomeDirectory()]; for (int i=1; i/rules_xcodeproj.noindex/build_output_base/` rather than the main Bazel output base. InjectionNext automatically detects this and: - Resolves `bazel-out/` paths to the rules_xcodeproj output base - Maps aquery configuration hashes (e.g. `ios_sim_arm64-fastbuild-*`) to the corresponding rules_xcodeproj configs (e.g. `ios_sim_arm64-dbg-*`) - Uses the rules_xcodeproj exec root as the working directory for recompilation This is transparent — hot reloading works the same whether you build via `bazel run` or from Xcode with a rules_xcodeproj-generated project. ================================================ FILE: INTRO.md ================================================ ## A lightning introduction to Injection As it says in the README.md, using a feature of Apple's linker Code Injection allows you to update the implementation (i.e. body) of functions in your app without having to relauch it. There are three parts to implementing this functionality. Recompiling your source file and preparing a dynamic library, loading the dynamic library and stitching the new implementations into your running program and arranging for the new implementations to be called to you can see on the screen that your code changes have taken effect. The first part is implemented by the InjectionNext.app that runs on the menu bar, the second and third by code in the InjectionNext Swift package which you add to the project you want to be able to inject. On startup the Swift package creates a socket connection to the InjectionNext.app so it can be controlled by it and instructed to load dynamic libaries either shared with it on the filesystem or received through the socket connection for real devices. ### Preparing the dynamic library InjectionNext comes with a new solution to this problem. It integrates with Xcode which if you launch it using the app (with SourceKit logging enabled) provides all the information you need to recompile a file with the user's changes which is the first step. The object file created by the recompilation is then linked in a reasonably generic way to create a dyanmic library which can be loaded into your program at run time using dlopen(). ### Loading and binding the dynamic library When a new dynamic library is available, the InjectionNext.app messages the code in the InjectionNext swift package to load it and "bind" it. Binding is the means by which a call site in your program is dispatched to the implementing function and when you use the "Other Linker Flags", "-Xlinker -interposable" this disptach is indirect through a writable section of memory. Change the function pointer in this memory and your new version of code is called instead of the old. This is done by a small piece of code from facebook named "fishhook" and all that remains is to know the new functions just loaded in the dynamic library. To do this you scan the "symbol table" data structures included in the dynamic library to find the "mangled" function names. The mangled names of Swift functions all start with "$s" and generally end in "F" or something very similar and most of injection is simply extracting these names and the implementation they point to and calling fishhook to rebind them. Classes are a ittle more complicated in that they have "vtables" in the meta-data describing the class but we don't need to explain that here. For Objective-C methods there way always a public runtime API to rebind them. ### Forcing redisplay This is the more difficult part of injection to realise. You can have the new fuction implementtions loaded and rebound but you won't see any change in the appearance on the screen until that new code is called. With SwiftUI the esiest way to do this is to have an observed instance variable on your View struct that changes when an injection has occured. There is code to do this in the HotSwiftUI and Inject packages if you add an @ObserveInjection property wrapper to the View struct. It's a little more complicated for legacy UIViewController subclasses and what needs to happen generally is viewDidLoad() needs to be called again each time you inject but how to arrange this for all currently displayed view controllers? The solution is InjectionNext performs a sweep all known instances of classes in the app and if they have been injected in the last loaded dynamic library and have an "@objc func injected()" method, call it. This method can call viewDidLoad() or "configureView()" or perform any other work required to update the display. Another approach you can use is is to "host" the view controller using the Inject package which reinstantiates the view controller on injection. ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2024 John Holdsworth Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Package.swift ================================================ // swift-tools-version: 5.4 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "InjectionNext", products: [ // Products define the executables and libraries a package produces, making them visible to other packages. .library( name: "InjectionNext", targets: ["InjectionBundle"]), // To avoid duplicate symbols if other // packages use e.g. DLKit or fishhook // .library( // name: "InjectionNextDynamic", // type: .dynamic, // targets: ["InjectionNext"]), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .target( name: "InjectionBundle", dependencies: ["InjectionNextC"], path: "Sources/InjectionNext", swiftSettings: [.define("DEBUG_ONLY")]), .target( name: "InjectionNextC", cSettings: [.define("DEBUG_ONLY"), .define("FISHHOOK_EXPORT")]), .testTarget( name: "InjectionNextTests", dependencies: ["InjectionBundle"]), ] ) ================================================ FILE: README.md ================================================ # InjectionNext ### The fourth evolution of Code Injection for Xcode Using a feature of Apple's linker this implementation of Code Injection allows you to update the implementation (i.e. body) of functions in your app without having to relaunch it. This can save a developer a significant amount of time tweaking code or iterating over a design. This repo is a refresh of the [InjectionIII](https://github.com/johnno1962/InjectionIII) app that uses different techniques to determine how to rebuild source files that should be faster and more reliable for very large projects. The basic M.O. is to download one of the binary releases in this repo (or build the app in the `App` directory), move it to /Applications, quit Xcode and run the `InjectionNext.app` and use that to re-launch Xcode using the menu item `Launch Xcode` from the status bar. You then add this repo as a Swift package dependency of your project and that should be all that is required for injection in the simulator, injection on devices and injection of a MacOS app. No more code changes required to load binary code bundles etc and you can leave the InjectionNext package configured into your project permanently as its code is only included for a DEBUG build. Your code changes take effect when you save a source for an app that has this package as a dependency and has connected to the InjectIonNext app which has launched Xcode. As ever, it is important to add the options `-Xlinker` and `-interposable` (without double quotes and on separate lines) to the "Other Linker Flags" of the targets of your project (for the `Debug` configuration only) to enable function "interposing". Otherwise, you will only be able to inject non-final class methods. ![Icon](App/interposable.png) If your app contains local Swift packages, add the following to their targets in Package.swift to be able to inject functions inside the package. linkerSettings: [ .unsafeFlags(["-Xlinker", "-interposable"], .when(configuration: .debug)) ] **Please note:** Due to an Xcode bug (as of version 16.2), `.when(configuration: .debug)` is ignored for local package linker settings. Until it's fixed, you might find this workaround helpful: ```swift var linkerSettings: [LinkerSetting] { let isInjectionRunning = ProcessInfo.processInfo.environment["RUNNING_VIA_INJECTION_NEXT"] != nil return isInjectionRunning ? [.unsafeFlags(["-Xlinker", "-interposable"])] : [] } ``` **Please note:** you can only inject changes to code inside a function body and you cannot add/remove or rename properties with storage or add or reorder methods in a non final class or change function signatures. To inject SwiftUI sucessfully a couple of minor code changes to each View are required. Consult the README of repo https://github.com/johnno1962/HotSwiftUI or you can make the required changes automatically using the menu item "Prepare SwiftUI/...". For SwiftUI you would generally also integrate either the [Inject](https://github.com/krzysztofzablocki/Inject) or [HotSwiftUI](https://github.com/johnno1962/HotSwiftUI) package into your project. If you'd rather not be adding a SPM dependency to your project, the app's resources contains pre-built bundles which you can copy into your app during the build by using a "Run Script/Build Phase" (while disabling the "user script sandboxing" build setting) such as the following: ``` export RESOURCES="/Applications/InjectionNext.app/Contents/Resources" if [ -f "$RESOURCES/copy_bundle.sh" ]; then "$RESOURCES/copy_bundle.sh" fi ``` These bundles should load automatically if you've integrated the [Inject](https://github.com/krzysztofzablocki/Inject) or [HotSwiftUI](https://github.com/johnno1962/HotSwiftUI) packages into your project. Otherwise, you can add the following code to run at startup of your app: ``` #if DEBUG if let path = Bundle.main.path(forResource: "iOSInjection", ofType: "bundle") ?? Bundle.main.path(forResource: "macOSInjection", ofType: "bundle") { Bundle(path: path)!.load() } #endif ``` The binary bundles also integrate [Nimble](https://github.com/Quick/Nimble) and a slightly modified version of the [Quick](https://github.com/Quick/Quick) testing framework to inhibit spec caching under their respective Apache licences. An alternative to loading a bundle is to add the following additional "Other Linker Flag": `/Applications/InjectionNext.app/Contents/Resources/lib$(PLATFORM_NAME)Injection.dylib` When your app runs it should connect to the `InjectionNext.app` and it's icon change to orange. After that, by parsing the messages from the "supervised" launch of Xcode it is possible to know when files are saved and exactly how to recompile them for injection. Injection on a device uses the same configuration but is opt-in through the menu item "Enable Devices" (as it needs to open a network port). You also need to select the project's "expanded codesigning identity" from the codesigning phase of your build logs in the window that pops up. Sometimes a device will not connect to the app first time after unlocking it. If at first it doesn't succeed, try again. The colours of the menu bar icon bar correspond to: * Blue when you first run the InjectionNext app. * Purple when you have launched Xcode using the app. * Orange when your client app has connected to it. * Green while it is recompiling a saved source. * Yellow if the source has failed to compile. To inject tests on a device: when enabling the "Enable Devices" menu item, if you select "Enable testing on device", the arguments shown will be added to the link of each dynamic library. As you do this, the command mentioned above will be inserted into the clipboard which you should paste into your project as a "Run Script" "Build Phase" of the main target to copy the required libraries into the app bundle. ### Cursor/VSCode/File-watcher mode. If you would like to use InjectionNext with the Cursor code editor, you can have it fall back to InjectionIII-style log parsing using the "...or Watch Project" menu item to select the project root you will be working under (or use the new "Proxy" mode below for Swift projects.) In this case, you shouldn't launch Xcode from inside the InjectionNext.app but you'll need to have built your app in Xcode at some point in the past for the logs to be available. You should build using the same version as that selected by `xcode-select`. With Xcode 16.3+, for this log parsing mode to continue working you'll need to add a custom build setting EMIT_FRONTEND_COMMAND_LINES. If you'd like the InjectionNext.app to automatically file watch your project, add the following environment variable to your scheme: `INJECTION_PROJECT_ROOT=$(SRCROOT)`. Also, Injection doesn't work with setting COMPILATION_CACHE_ENABLE_CACHING. ### Scheme environment variable INJECTION_TRACE If you add an environment variable INJECTION_TRACE to your scheme, logging aspects will be added to all functions in a file when you inject it so you can see they are being called and as an aid to debugging. This means you can turn on detailed logging for a file just by injecting it. For a full list of all environment variable that can be specified consult [this source file](https://github.com/johnno1962/InjectionLite/blob/main/Sources/InjectionImplC/include/InjectionImplC.h#L45). YMMV with global tracing but on a device it will likely be more reliable if you use the precompiled bundles with a copy_bundle.sh build phase. If you still see crashes, this can occur when an argument's type has been miscategorised. Add a INJECTION_TRACE_REPAIR schema variable and failing methods will be added to a list excluding them from logging when you re-run your app. ### Fallback compiler "proxy" mode (Not normally used). It is also possible to intercept swift compilation commands as a new proof of concept for when at some point in the future these are no longer captured in the Xcode logs (as was the case with Xcode 16.3 beta1). In this case, select "Intercept compiler" to patch the current toolchain slightly to capture all compilations using a script and send them to the InjectionNext.app. Once this patch has been applied you don't need to launch Xcode from the app and you can inject by starting a file watcher using the "...or Watch Project" menu item (though this should happen automatically when you recompile Swift sources). For proxy mode to work the app must be installed in /Applications. So, InjectionNext now has three ways which it can operate. The original mode of operation launching Xcode inside the app takes precedence and, if you have selected a file watcher and are intercepting the compiler commands this "proxy mode" is the next preference followed by the log parsing fallback using the [InjectionLite](https://github.com/johnno1962/InjectionLite) package which essentially works as InjectionIII did when the logs are available. For more information consult the [original InjectionIII README](https://github.com/johnno1962/InjectionIII) or for the bigger picture see [this swift evolution post](https://forums.swift.org/t/weve-been-doing-it-wrong-all-this-time/72015). You can run InjectionNext from the command line and have it open your project in Xcode automatically using the -projectPath option. open -a InjectionNext --args -projectPath /path/to/project Set a user default with the same name if you want to always open this project inside the selected Xcode on launching the app. After 100 injections you'll be reminded you can sponsor this project on GitHub. The fabulous app icon is thanks to Katya of [pixel-mixer.com](http://pixel-mixer.com/). ================================================ FILE: Sources/InjectionNext/InjectionNext.swift ================================================ // // InjectionNext.swift // InjectionNext Package // // Created by John Holdsworth on 30/05/2024. // // Client app side of injection using implementation of InjectionLite. // #if DEBUG || !SWIFT_PACKAGE import Foundation #if canImport(InjectionImpl) import InjectionImpl #endif #if canImport(InjectionNextC) @_exported import InjectionNextC #endif @objc(InjectionNext) open class InjectionNext: SimpleSocket { override class open func error(_ message: String) -> Int32 { let msg = String(format: message, strerror(errno)) print(APP_PREFIX+APP_NAME+": "+msg) if errno == EHOSTUNREACH { // No route to host print("ℹ️ "+APP_NAME+": Accept permission prompt on device.") } return errno } func log(_ msg: String) { print(APP_PREFIX+APP_NAME+": "+msg) } func error(_ msg: String) { log("⚠️ "+msg) } /// Connection from client app opened in ClientBoot.mm arrives here open override func runInBackground() { super.write(INJECTION_VERSION) #if targetEnvironment(simulator) || os(macOS) super.write(NSHomeDirectory()) #else if let bazelWorkspace = getenv(BUILD_WORKSPACE_DIRECTORY) { super.write(String(cString: bazelWorkspace)) } else { super.write(INJECTION_KEY) } #endif // Find client platform #if os(macOS) || targetEnvironment(macCatalyst) var platform = "Mac" #elseif os(tvOS) var platform = "AppleTV" #elseif os(visionOS) var platform = "XR" #elseif os(watchOS) var platform = "Watch" #else var platform = "iPhone" #endif #if targetEnvironment(simulator) platform += "Simulator" #else platform += "OS" #endif #if os(macOS) platform += "X" #endif Reloader.platform = platform #if arch(x86_64) let arch = "x86_64" #else let arch = "arm64" #endif // Let server side know the platform and architecture writeCommand(InjectionResponse.platform.rawValue, with: platform) super.write(arch) if let projectRoot = getenv(INJECTION_PROJECT_ROOT) ?? getenv(BUILD_WORKSPACE_DIRECTORY) { writeCommand(InjectionResponse.projectRoot.rawValue, with: String(cString: projectRoot)) } writeCommand(InjectionResponse.tmpPath.rawValue, with: NSTemporaryDirectory()) if let detail = getenv(INJECTION_DETAIL) { writeCommand(InjectionResponse.detail.rawValue, with: String(cString: detail)) } if let bazelTarget = getenv(INJECTION_BAZEL_TARGET) { writeCommand(InjectionResponse.bazelTarget.rawValue, with: String(cString: bazelTarget)) } if let executable = Bundle.main.executablePath { writeCommand(InjectionResponse.executable.rawValue, with: executable) } Reloader.injectionQueue.sync { tracingOptions() } log("\(arch) \(platform) connected to app, waiting for commands.") #if !SWIFT_PACKAGE if let build = Bundle(for: Self.self) .infoDictionary?["CFBundleVersion"] as? String { detail("Bundle build #"+build) } #endif processCommandsFromApp() log("Connection lost, disconnecting.") } func tracingOptions() { SwiftTrace.injectableSymbol = Reloader.injectableSymbol SwiftTrace.defaultMethodExclusions += // CoreFoundation #"|\[NS(Method|Tagged|Array|\w*Dict|Date|Data|Timer)|allocWithZone:|__unurl|_trueSelf"# + #"|InjectionBundle.|fast_dl"# for name in [INJECTION_TRACE_LOOKUP, INJECTION_TRACE_FILTER, INJECTION_TRACE_ALL, INJECTION_TRACE_FRAMEWORKS, INJECTION_TRACE_UIKIT, INJECTION_TRACE] { if let value = getenv(name) { setVariable(name: name, to: String(cString: value), first: true) } } } func setVariable(name: String, to value: String, first: Bool) { let wasSet = getenv(name) != nil if name == INJECTION_DLOPEN_MODE, let mode = Int32(value) { DLKit.dlOpenMode = mode } else if name == INJECTION_TRACE_FILTER { if value == UNSETENV_VALUE { if wasSet { SwiftTrace.traceFilterInclude = "." } } else { SwiftTrace.traceFilterInclude = value } } if value == UNSETENV_VALUE { unsetenv(name) return } setenv(name, value, 1) if !first && wasSet { return } switch name { /// Custom type lookup on tracing. case INJECTION_TRACE_LOOKUP: if value.hasPrefix("|") { SwiftTrace.defaultLookupExclusions += value } SwiftTrace.typeLookup = true /// Entire App bundle tracing. case INJECTION_TRACE_ALL: if value.hasPrefix("|") { SwiftTrace.defaultMethodExclusions += value } SwiftTrace.interposeEclusions = SwiftTrace.exclusionRegexp appBundleImages { imageName, _, _ in if SwiftTrace.interposeMethods(inBundlePath: imageName) == 0, strstr(imageName, "XCT") == nil { self.error(""" Unable to interpose to trace image \ \(String(cString: imageName)), have you added \ "Other Linker Flags" -Xlinker -interposable """) } SwiftTrace.trace(bundlePath: imageName) } /// Trace calls to framework e.g. SwiftUI,SwiftUICore case INJECTION_TRACE_FRAMEWORKS: var frmwks = value if frmwks == "" || frmwks == "1" { frmwks = "SwiftUI,SwiftUICore" } for frmwk in frmwks.components(separatedBy: ",") { if let dylib = DLKit.imageMap[frmwk] { Self.target = dylib appBundleImages { path, header, slide in rebind_symbols_trace(autoBitCast(header), slide, Self.tracer) } } else { error("Invalid trace framework \(frmwk)") } } /// Trace UIKit internals using swizzling case INJECTION_TRACE_UIKIT: var frmwks = value if frmwks == "" || frmwks == "1" { frmwks = "UIKitCore" } for frmwk in frmwks.components(separatedBy: ",") { if let bundle = DLKit.imageMap[frmwk]?.imageName { SwiftTrace.trace(bundlePath: bundle) } else { error("Invalid swizzle framework \(frmwk)") } } /// Function and class method tracing on injection. case INJECTION_TRACE: Reloader.traceHook = { (injected, name) in let name = SwiftMeta.demangle(symbol: name) ?? String(cString: name) detail("SwiftTracing \(name)") return autoBitCast(SwiftTrace.trace(name: name, original: injected)) ?? injected } default: break } } static var target: ImageSymbols? static var tracer: STTracer = { existing, symname in var traced = existing if SwiftTrace.injectableSymbol(symname), let info = trie_iterator(existing), target?.imageHeader == info.pointee.header, let name = SwiftMeta.demangle(symbol: symname) { detail("Tracing \(name) \(existing)") traced = autoBitCast(SwiftTrace .trace(name: " "+name, original: existing)) ?? existing } return traced } func processCommandsFromApp() { var loader = Reloader() // InjectionLite injection implementation func injectAndSweep(_ dylib: String) { Reloader.injectionNumber += 1 var succeeded = false if let (image, classes) = Reloader.injectionQueue .sync(execute: { loader.loadAndPatch(in: dylib) }) { loader.sweeper.sweepAndRunTests(image: image, classes: classes) succeeded = true let countKey = "__injectionsPerformed", howOften = 100 let count = UserDefaults.standard.integer(forKey: countKey)+1 UserDefaults.standard.set(count, forKey: countKey) if count % howOften == 0 && getenv("INJECTION_SPONSOR") == nil { log(""" ℹ️ Seems like you're using injection quite a bit. \ Have you considered sponsoring the project at \ https://github.com/johnno1962/\(APP_NAME) or \ asking your boss if they should? (This message \ prints every \(howOften) injections.) """) } } else { writeCommand(InjectionResponse.unhide.rawValue, with: nil) } writeCommand(succeeded ? InjectionResponse.injected.rawValue : InjectionResponse.failed.rawValue, with: nil) } while true { let commandInt = readInt() guard let command = InjectionCommand(rawValue: commandInt) else { error("Invalid command rawValue: \(commandInt)") break } switch command { case .invalid: return error("Connection did not validate. Have you upgraded?") case .log: if let msg = readString() { print(msg) } case .xcodePath: if let xcodePath = readString() { log("Xcode path: "+xcodePath) Reloader.xcodeDev = xcodePath+"/Contents/Developer" } case .sendFile: guard let path = readString() else { return error("Unable to read path") } if path.hasSuffix("/") { mkdir(path, 0o755) continue } recvFile(path) case .load: guard let dylib = readString() else { return error("Unable to read path") } injectAndSweep(dylib) case .inject: guard let dylibName = readString(), let data = readData() else { return error("Unable to read dylib") } let dylib = NSTemporaryDirectory() + dylibName try! data.write(to: URL(fileURLWithPath: dylib)) injectAndSweep(dylib) case .metrics: guard let metricsJSON = readString() else { return error("Unable to read metrics JSON") } if let data = metricsJSON.data(using: .utf8), let metricsDict = try? JSONSerialization.jsonObject(with: data) as? [String: Any], let notificationName = metricsDict["notification_name"] as? String { NotificationCenter.default.post( name: NSNotification.Name(notificationName), object: nil, userInfo: metricsDict ) } case .setenv: while let name = readString(), let value = readString() { setVariable(name: name, to: value, first: false) if readInt() != commandInt { break } } case .EOF: return default: return error("**** @unknown case \(commandInt) **** " + "Do you need to update the InjectionNext package?") } } } } #endif ================================================ FILE: Sources/InjectionNextC/ClientBoot.mm ================================================ // // ClientBoot.m // // // Created by John H on 31/05/2024. // #if DEBUG || !SWIFT_PACKAGE #import #import #import "InjectionImplC.h" #import "InjectionClient.h" #import "SimpleSocket.h" @interface InjectionNext : SimpleSocket @end @implementation NSObject(InjectionNext) static SimpleSocket *injectionClient; static dispatch_once_t onlyOneClient; /// Called on load of image containing this code + (void)load { if ([InjectionNext InjectionBoot_inPreview]) return; #if !TARGET_OS_MAC [self performSelectorOnMainThread:@selector(connectInBackground) withObject:nil waitUntilDone:NO]; } + (void)connectInBackground { #endif [self performSelectorInBackground:@selector(connectToInjection:) withObject:[InjectionNext self]]; } /// Attempt to connect to InjectionNext.app + (void)connectToInjection:(Class)clientClass { const char *hostip = getenv(INJECTION_HOST) ?: "127.0.0.1"; // Do we need to use broadcasts to find devlepers Mac on the network #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_OSX if (@available(iOS 14.0, *)) if (![NSProcessInfo processInfo].isiOSAppOnMac) { printf(APP_PREFIX APP_NAME": Locating developer's Mac. Have you selected \"Enable Devices\"?\n"); hostip = [SimpleSocket getMulticastService:HOTRELOADING_MULTICAST port:HOTRELOADING_PORT message:APP_PREFIX"Connecting to %s (%s)...\n"].UTF8String; } #endif // Have the address to connect to, connect and start local thread. NSString *socketAddr = [NSString stringWithFormat:@"%s%s", hostip, INJECTION_ADDRESS]; for (int retry=0, retrys=1; retry #include #include #include #include #include #if 0 #define SLog NSLog #else #define SLog while(0) NSLog #endif #define MAX_PACKET 16384 #define EOS ~0 NSString *INJECTION_KEY = @__FILE__; typedef union { struct { __uint8_t sa_len; /* total length */ sa_family_t sa_family; /* [XSI] address family */ }; struct sockaddr_storage any; struct sockaddr_in ip4; struct sockaddr addr; } sockaddr_union; @implementation SimpleSocket #if !SWIFT_PACKAGE + (void)initialize { // Pre-built bundles (+InjectionNext.app) INJECTION_KEY = [NSBundle bundleForClass:self] .infoDictionary[@"UserHome"] ?: [NSBundle mainBundle] .infoDictionary[@"InjectionUserHome"] ?: NSHomeDirectory(); } #endif + (int)error:(NSString *)message { NSLog([@"%@/" stringByAppendingString:message], self, strerror(errno)); return -1; } + (void)startServer:(NSString *)address { [self performSelectorInBackground:@selector(runServer:) withObject:address]; } + (void)forEachInterface:(void (^)(ifaddrs *ifa, in_addr_t addr, in_addr_t mask))handler { ifaddrs *addrs; if (getifaddrs(&addrs) < 0) { [self error:@"Could not getifaddrs: %s"]; return; } for (ifaddrs *ifa = addrs; ifa; ifa = ifa->ifa_next) if (ifa->ifa_addr->sa_family == AF_INET) handler(ifa, ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr, ((struct sockaddr_in *)ifa->ifa_netmask)->sin_addr.s_addr); freeifaddrs(addrs); } static int lastServerSocket; + (void)runServer:(NSString *)address { sockaddr_union serverAddr; [self parseV4Address:address into:&serverAddr.any]; int serverSocket = [self newSocket:serverAddr.sa_family]; if (serverSocket < 0) return; lastServerSocket = serverSocket; if (bind(serverSocket, &serverAddr.addr, serverAddr.sa_len) < 0) [self error:@"Could not bind service socket: %s"]; else if (listen(serverSocket, 50) < 0) [self error:@"Service socket would not listen: %s"]; else while (serverSocket) { sockaddr_union clientAddr; socklen_t addrLen = sizeof clientAddr; int clientSocket = accept(serverSocket, &clientAddr.addr, &addrLen); if (clientSocket > 0) { int yes = 1; if (setsockopt(clientSocket, SOL_SOCKET, SO_NOSIGPIPE, &yes, sizeof yes) < 0) [self error:@"Could not set SO_NOSIGPIPE: %s"]; @autoreleasepool { struct sockaddr_in *v4Addr = &clientAddr.ip4; printf("%s: Connection from %s:%d\n", object_getClassName(self), inet_ntoa(v4Addr->sin_addr), ntohs(v4Addr->sin_port)); SimpleSocket *client = [[self alloc] initSocket:clientSocket]; client.isLocalClient = v4Addr->sin_addr.s_addr == htonl(INADDR_LOOPBACK); [self forEachInterface:^(ifaddrs *ifa, in_addr_t addr, in_addr_t mask) { if (v4Addr->sin_addr.s_addr == addr) client.isLocalClient = TRUE; }]; [client run]; } } else if (lastServerSocket) [NSThread sleepForTimeInterval:.5]; else break; } close(serverSocket); } + (void)stopLastServer { if (lastServerSocket) close(lastServerSocket); lastServerSocket = 0; [NSThread sleepForTimeInterval:.5]; } + (instancetype)connectTo:(NSString *)address { sockaddr_union serverAddr; [self parseV4Address:address into:&serverAddr.any]; int clientSocket = [self newSocket:serverAddr.sa_family]; if (clientSocket < 0) return nil; if (connect(clientSocket, &serverAddr.addr, serverAddr.sa_len) < 0) { [self error:@"Could not connect: %s"]; return nil; } return [[self alloc] initSocket:clientSocket]; } + (int)newSocket:(sa_family_t)addressFamily { int newSocket, yes = 1; if ((newSocket = socket(addressFamily, SOCK_STREAM, 0)) < 0) [self error:@"Could not open service socket: %s"]; else if (setsockopt(newSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) < 0) [self error:@"Could not set SO_REUSEADDR: %s"]; else if (setsockopt(newSocket, SOL_SOCKET, SO_NOSIGPIPE, &yes, sizeof yes) < 0) [self error:@"Could not set SO_NOSIGPIPE: %s"]; else if (setsockopt(newSocket, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes) < 0) [self error:@"Could not set TCP_NODELAY: %s"]; else if (fcntl(newSocket, F_SETFD, FD_CLOEXEC) < 0) [self error:@"Could not set FD_CLOEXEC: %s"]; else return newSocket; return -1; } /** * Available formats * @"[:]" * where can be NNN.NNN.NNN.NNN or hostname, empty for localhost or * for all interfaces * The default port is 80 or a specific number to bind or an empty string to allocate any port */ + (BOOL)parseV4Address:(NSString *)address into:(struct sockaddr_storage *)serverAddr { NSArray *parts = [address componentsSeparatedByString:@":"]; struct sockaddr_in *v4Addr = (struct sockaddr_in *)serverAddr; bzero(v4Addr, sizeof *v4Addr); v4Addr->sin_family = AF_INET; v4Addr->sin_len = sizeof *v4Addr; v4Addr->sin_port = htons(parts.count > 1 ? parts[1].intValue : 80); const char *host = parts[0].UTF8String; if (!host[0]) v4Addr->sin_addr.s_addr = htonl(INADDR_LOOPBACK); else if (host[0] == '*') v4Addr->sin_addr.s_addr = htonl(INADDR_ANY); else if (isdigit(host[0])) v4Addr->sin_addr.s_addr = inet_addr(host); else if (struct hostent *hp = gethostbyname2(host, v4Addr->sin_family)) memcpy(&v4Addr->sin_addr, hp->h_addr, hp->h_length); else { [self error:[NSString stringWithFormat:@"Unable to look up host for %@", address]]; return FALSE; } return TRUE; } - (instancetype)initSocket:(int)socket { if ((self = [super init])) { clientSocket = socket; } return self; } - (void)run { [self performSelectorInBackground:@selector(runInBackground) withObject:nil]; } - (void)runInBackground { [[self class] error:@"-[SimpleSocket runInBackground] not implemented in subclass"]; } typedef ssize_t (*io_func)(int, void *, size_t); - (BOOL)perform:(io_func)io ofBytes:(const void *)buffer length:(size_t)length cmd:(SEL)cmd { size_t bytes, ptr = 0; SLog(@"#%d %s %lu [%p] %s", clientSocket, io == read ? "<-" : "->", length, buffer, sel_getName(cmd)); while (ptr < length && (bytes = io(clientSocket, (char *)buffer+ptr, MIN(length-ptr, MAX_PACKET))) > 0) ptr += bytes; if (ptr < length) { if (errno) NSLog(@"[%@ %s:%p length:%lu] error: %lu %s", self, sel_getName(cmd), buffer, length, ptr, strerror(errno)); return FALSE; } return TRUE; } - (BOOL)readBytes:(void *)buffer length:(size_t)length cmd:(SEL)cmd { return [self perform:read ofBytes:buffer length:length cmd:cmd]; } - (int)readInt { int32_t anint = EOS; if (![self readBytes:&anint length:sizeof anint cmd:_cmd]) return EOS; SLog(@"#%d <- %d", clientSocket, anint); return anint; } - (void *)readPointer { void *aptr = (void *)EOS; if (![self readBytes:&aptr length:sizeof aptr cmd:_cmd]) return aptr; SLog(@"#%d <- %p", clientSocket, aptr); return aptr; } - (NSData *)readData { size_t length = [self readInt]; if (length == EOS) return nil; void *bytes = malloc(length); if (!bytes || ![self readBytes:bytes length:length cmd:_cmd]) return nil; return [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:YES]; } - (NSString *)readString { size_t length = [self readInt]; if (length == EOS) return nil; void *bytes = malloc(length+1); if (!bytes || ![self readBytes:bytes length:length cmd:_cmd]) return nil; ((char *)bytes)[length] = 0; NSString *str = [[NSString alloc] initWithBytesNoCopy:bytes length:length encoding:NSUTF8StringEncoding freeWhenDone:YES]; SLog(@"#%d <- %d '%@'", clientSocket, (int)str.length, str); return str; } - (BOOL)writeBytes:(const void *)buffer length:(size_t)length cmd:(SEL)cmd { return [self perform:(io_func)write ofBytes:buffer length:length cmd:cmd]; } - (BOOL)writeInt:(int)length { SLog(@"#%d %d ->", clientSocket, length); return [self writeBytes:&length length:sizeof length cmd:_cmd]; } - (BOOL)writeCStr:(const char *)string { uint32_t len = (uint32_t)strlen(string); SLog(@"#%d %d '%s' ->", clientSocket, (int)len, string); return [self writeInt:len] && [self writeBytes:string length:len cmd:_cmd]; } - (BOOL)writePointer:(void *)ptr { SLog(@"#%d %p ->", clientSocket, ptr); return [self writeBytes:&ptr length:sizeof ptr cmd:_cmd]; } - (BOOL)writeData:(NSData *)data { uint32_t length = (uint32_t)data.length; SLog(@"#%d [%d] ->", clientSocket, length); return [self writeInt:length] && [self writeBytes:data.bytes length:length cmd:_cmd]; } - (BOOL)writeString:(NSString *)string { NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; SLog(@"#%d %d '%@' ->", clientSocket, (int)data.length, string); return [self writeData:data]; } - (BOOL)writeCommand:(int)command withString:(NSString *)string { return [self writeInt:command] && (!string || [self writeString:string]); } - (BOOL)failed:(SEL)sel file:(id)file { NSLog(@"-[%@ %s \"%@\"]: Could not fopen(), %s", self, sel_getName(sel), file, strerror(errno)); return FALSE; } - (BOOL)sendFile:(NSString *)path { struct stat st; FILE *input = fopen(path.UTF8String, "r"); if (!input || fstat(fileno(input), &st)) return [self writeInt:INT_MAX] && [self writeString:[NSString stringWithFormat: @"-[%@ %s \"%@\"]: Could not open to send, %s", self, sel_getName(_cmd), path, strerror(errno)]] && [self failed:_cmd file:path]; [self writeInt:(int)st.st_size]; off_t pos = 0, chunk; char buffer[MAX_PACKET]; while ((chunk = MIN(st.st_size-pos, sizeof buffer)) > 0 && (chunk = fread(buffer, 1, chunk, input)) > 0 && [self writeBytes:buffer length:chunk cmd:_cmd]) pos += chunk; fclose(input); return pos == st.st_size; } - (BOOL)recvFile:(NSString *)path { FILE *output = fopen(path.UTF8String, "w"); if (!output) return [self failed:_cmd file:path]; off_t sz = [self readInt], pos = 0, chunk; if (sz == INT_MAX) return [self failed:_cmd file:[self readString]]; char buffer[MAX_PACKET]; while ((chunk = MIN(sz-pos, sizeof buffer)) > 0 && [self readBytes:buffer length:chunk cmd:_cmd] && (chunk = fwrite(buffer, 1, chunk, output)) > 0) pos += chunk; fclose(output); return pos == sz; } - (void)dealloc { close(clientSocket); } /// Hash used to differentiate InjectionNext users broadcasting. /// Hash derived from path to user's home directory determined /// from path to this source file in project's DerivedData or for /// pre-built bundles, home directory taken from a value patched /// into the bundle's Info.plist as it is copied into client app. + (int)multicastHash { #if SWIFT_PACKAGE NSString *file = @__FILE__; INJECTION_KEY = [file stringByReplacingOccurrencesOfString: @"(/Users/[^/]+).*" withString: @"$1" options: NSRegularExpressionSearch range: NSMakeRange(0, file.length)]; #endif const char *key = INJECTION_KEY.UTF8String; int hash = 0; for (size_t i=0, len = strlen(key); i> 24) { case 10: // mobile network // case 172: // hotspot case 127: // loopback return; } int idx = if_nametoindex(ifa->ifa_name); setsockopt(multicastSocket, IPPROTO_IP, IP_BOUND_IF, &idx, sizeof idx); addr.sin_addr.s_addr = laddr | ~nmask; printf("Broadcasting to %s#%d:%s to locate Injection host...\n", ifa->ifa_name, idx, inet_ntoa(addr.sin_addr)); if (sendto(multicastSocket, &msgbuf, sizeof msgbuf, 0, (struct sockaddr *)&addr, sizeof addr) < 0) [self error:@"Could not send broadcast ping: %s"]; if (sendto(multicastSocket, &msgbuf, sizeof msgbuf, 0, (struct sockaddr *)&addr, sizeof addr) < 0) [self error:@"Could not send broadcast ping: %s"]; }]; socklen_t addrlen = sizeof addr; while (recvfrom(multicastSocket, &msgbuf, sizeof msgbuf, 0, (struct sockaddr *)&addr, &addrlen) < sizeof msgbuf) { [self error:@"%s: Error receiving from broadcast: %s"]; sleep(1); } const char *ipaddr = inet_ntoa(addr.sin_addr); printf(format, msgbuf.host, ipaddr); close(multicastSocket); return [NSString stringWithUTF8String:ipaddr]; } @end @implementation SimpleService static NSMutableDictionary *callbacks; + (void)startServer:(NSString *_Nonnull)address callback:(SimpleCallback)callback { if (!callbacks) callbacks = [NSMutableDictionary new]; callbacks[address] = callback; [super startServer:address]; } - (void)runInBackground { sockaddr_union addr; socklen_t len = sizeof addr; if (getsockname(clientSocket, &addr.addr, &len) < 0) [[self class] error:@"getsockname failed %s"]; NSString *address = [NSString stringWithFormat:@"*:%d", ntohs(addr.ip4.sin_port)]; if (SimpleCallback callback = callbacks[address]) callback(clientSocket); else [[self class] error:@"Missing callback"]; } @end @implementation SimpleHTTP + (void)startServer:(NSString *_Nonnull)address callback:(NSData *_Nullable (^_Nonnull)(char *_Nonnull header))callback { [super startServer:address callback:^(int socket) { FILE *stream = fdopen(socket, "r+"); size_t bsize = 1000, length; char *header = (char *)malloc(bsize), *hptr = header; while ((length = strlen(fgets(hptr, (int)(bsize - (hptr-header)), stream)?:"")) > 2 || (length && strcmp(hptr-2, "\r\n\r\n"))) if ((hptr += length)-header == bsize-1) { char *newbuff = (char *)realloc(header, bsize *= 2); hptr = newbuff + (hptr - header); header = newbuff; } NSData *body = callback(header) ?: [NSData new]; fprintf(stream, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n" "Content-Length: %ld\r\n\r\n", body.length); fwrite(body.bytes, body.length, 1, stream); fclose(stream); free(header); }]; } @end #endif ================================================ FILE: Sources/InjectionNextC/include/InjectionClient.h ================================================ // // InjectionClient.h // InjectionBundle // // Created by John Holdsworth on 06/11/2017. // Copyright © 2017 John Holdsworth. All rights reserved. // // $Id: //depot/InjectionNext/Sources/InjectionNextC/include/InjectionClient.h#60 $ // // Shared definitions between server and client. // #import #define HOTRELOADING_PORT ":8887" #define HOTRELOADING_MULTICAST "239.255.255.239" #define INJECTION_VERSION 4001 #define COMMANDS_PORT ":8896" #define INJECTION_ADDRESS HOTRELOADING_PORT extern NSString *INJECTION_KEY; #undef APP_NAME #define APP_NAME "InjectionNext" #define APP_PREFIX "🔥 " #define DYLIB_PREFIX "/eval_injection_" // Expected by DLKit.appImages #define INJECTION_APP_VERSION "INJECTION_APP_VERSION" #define INJECTION_DLOPEN_MODE "INJECTION_DLOPEN_MODE" #define UNSETENV_VALUE "__NULL__" @interface NSObject(HotReloading) + (void)runXCTestCase:(Class)aTestCase; @end @interface NSProcessInfo(iOSAppOnMac) @property BOOL isiOSAppOnMac; @end typedef NS_ENUM(int, InjectionCommand) { // commands to InjectionNext package InjectionLog, InjectionLoad, InjectionInject, InjectionXcodePath, InjectionSendFile, InjectionMetrics, InjectionSetenv, InjectionEndenv, InjectionInvalid = 1000, InjectionEOF = ~0 }; typedef NS_ENUM(int, InjectionResponse) { // responses from InjectionNext package InjectionPlatform, InjectionInjected, InjectionFailed, InjectionTmpPath, InjectionUnhide, InjectionProjectRoot, InjectionDetail, InjectionBazelTarget, InjectionExecutable, InjectionExit = ~0 }; ================================================ FILE: Sources/InjectionNextC/include/SimpleSocket.h ================================================ // // SimpleSocket.h // InjectionIII // // Created by John Holdsworth on 06/11/2017. // Copyright © 2017 John Holdsworth. All rights reserved. // // $Id: //depot/HotReloading/Sources/HotReloadingGuts/include/SimpleSocket.h#16 $ // #import #import @interface SimpleSocket : NSObject { @protected int clientSocket; } @property BOOL isLocalClient; + (void)startServer:(NSString *_Nonnull)address; + (void)runServer:(NSString *_Nonnull)address; + (int)error:(NSString *_Nonnull)message; + (void)stopLastServer; + (instancetype _Nullable)connectTo:(NSString *_Nonnull)address; + (BOOL)parseV4Address:(NSString *_Nonnull)address into:(struct sockaddr_storage *_Nonnull)serverAddr; + (void)multicastServe:(const char *_Nonnull)multicast port:(const char *_Nonnull)port; + (NSString *_Nonnull)getMulticastService:(const char *_Nonnull)multicast port:(const char *_Nonnull)port message:(const char *_Nonnull)format; - (instancetype _Nonnull)initSocket:(int)socket; - (void)run; - (void)runInBackground; - (int)readInt; - (void * _Nullable)readPointer; - (NSData *_Nullable)readData; - (NSString *_Nullable)readString; - (BOOL)readBytes:(void * _Nonnull)buffer length:(size_t)length cmd:(SEL _Nonnull)cmd; - (BOOL)writeInt:(int)length; - (BOOL)writeCStr:(const char *_Nonnull)string; - (BOOL)writePointer:(void * _Nullable)pointer; - (BOOL)writeData:(NSData *_Nonnull)data; - (BOOL)writeString:(NSString *_Nonnull)string; - (BOOL)writeCommand:(int)command withString:(NSString *_Nullable)string; - (BOOL)sendFile:(NSString *_Nonnull)path; - (BOOL)recvFile:(NSString *_Nonnull)path; @end @interface SimpleService: SimpleSocket typedef void (^_Nonnull SimpleCallback)(int socket); + (void)startServer:(NSString *_Nonnull)address callback:(SimpleCallback)callback; @end @interface SimpleHTTP: SimpleService + (void)startServer:(NSString *_Nonnull)address callback:(NSData *_Nullable (^_Nonnull)(char *_Nonnull header))callback; @end ================================================ FILE: Tests/InjectionNextTests/InjectionNextTests.swift ================================================ import XCTest @testable import InjectionBundle final class InjectionNextTests: XCTestCase { func testExample() throws { // XCTest Documentation // https://developer.apple.com/documentation/xctest // Defining Test Cases and Test Methods // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods } } ================================================ FILE: mcp-server/.gitignore ================================================ node_modules/ ================================================ FILE: mcp-server/README.md ================================================ # InjectionNext MCP Server MCP (Model Context Protocol) server that lets AI agents control InjectionNext — start file watching, check status, read debug logs, toggle device injection, and more. ## Prerequisites - **macOS** with Xcode installed - **Node.js** 18.14.1+ - **InjectionNext.app** built from this repo (with ControlServer support) ## Step 1: Build InjectionNext with ControlServer ```bash # Clone and init submodules git clone cd InjectionNext git submodule update --init --recursive # Build the app (ad-hoc signing for local dev) cd App xcodebuild -project InjectionNext.xcodeproj \ -scheme InjectionNext \ -configuration Debug build \ CODE_SIGN_IDENTITY="-" \ CODE_SIGNING_REQUIRED=NO \ CODE_SIGNING_ALLOWED=NO # The built app is at: # ~/Library/Developer/Xcode/DerivedData/InjectionNext-*/Build/Products/Debug/InjectionNext.app ``` Optionally copy it to `/Applications`: ```bash cp -R ~/Library/Developer/Xcode/DerivedData/InjectionNext-*/Build/Products/Debug/InjectionNext.app /Applications/ ``` ## Step 2: Enable the ControlServer The TCP control server is **opt-in**. Enable it via a UserDefault before launching the app: ```bash defaults write com.johnholdsworth.InjectionNext mcpServer -bool true ``` To disable it later: ```bash defaults delete com.johnholdsworth.InjectionNext mcpServer ``` ## Step 3: Install the MCP server ```bash cd mcp-server npm install ``` ## Step 4: Configure Cursor Add to your Cursor MCP config at `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per-project): ```json { "mcpServers": { "injection-next": { "command": "node", "args": ["/absolute/path/to/InjectionNext/mcp-server/index.js"] } } } ``` Replace `/absolute/path/to` with the actual path to this repo. ## Step 5: Launch InjectionNext Start the app before using the MCP tools: ```bash open /Applications/InjectionNext.app # or from DerivedData: open ~/Library/Developer/Xcode/DerivedData/InjectionNext-*/Build/Products/Debug/InjectionNext.app ``` You should see the InjectionNext icon in the menu bar. ## Step 6: Test it ### Quick test from terminal (no MCP needed) ```bash # Check status echo '{"action":"status"}' | nc -w 3 localhost 8919 # Start watching a project echo '{"action":"watch_project","path":"/path/to/your/project"}' | nc -w 3 localhost 8919 # Read debug logs echo '{"action":"get_logs"}' | nc -w 3 localhost 8919 # Stop watching echo '{"action":"stop_watching"}' | nc -w 3 localhost 8919 ``` ### Test from Cursor After configuring the MCP server, open Cursor and ask the AI: > "Use the injection-next MCP to check the status of InjectionNext" or: > "Watch my project at /path/to/project for hot reloading" or: > "Show me the InjectionNext debug logs" ## Available Tools | Tool | Description | |------|-------------| | `get_status` | Full app status: Xcode, watched dirs, compiler, clients | | `watch_project` | Start file-watching a directory for hot-reloading | | `stop_watching` | Stop all file watchers | | `launch_xcode` | Launch Xcode via InjectionNext with SourceKit logging | | `get_compiler_state` | Check if Swift compiler is intercepted | | `enable_devices` | Toggle device/simulator injection support | | `unhide_symbols` | Fix default-argument symbol visibility issues | | `get_last_error` | Get last compilation error | | `prepare_swiftui_source` | Add injection annotations to current SwiftUI file | | `prepare_swiftui_project` | Prepare all SwiftUI files in target | | `set_xcode_path` | Point to a different Xcode.app | | `get_logs` | Read debug console (supports `since` for polling) | | `clear_logs` | Clear the log buffer | ## Architecture ``` ┌─────────────┐ TCP :8919 ┌──────────────────────┐ │ MCP Server │◄───────────────────────►│ InjectionNext.app │ │ (Node.js) │ JSON commands/resp │ ┌─────────────────┐ │ │ │ │ │ ControlServer │ │ │ stdio ↕ │ │ │ (TCP listener) │ │ │ │ │ └────────┬────────┘ │ │ Cursor / │ │ │ │ │ AI Agent │ │ ┌────────▼────────┐ │ └─────────────┘ │ │ AppDelegate │ │ │ │ IBActions │ │ │ └────────┬────────┘ │ │ │ │ │ ┌────────▼────────┐ │ │ │ LogBuffer │ │ │ │ (ring buffer) │ │ │ └─────────────────┘ │ └──────────────────────┘ ``` ## Troubleshooting **"Cannot connect to InjectionNext on port 8919"** - Make sure InjectionNext.app is running (check menu bar icon) - Make sure you built the version with ControlServer support (from this repo) - Check: `lsof -i :8919` should show InjectionNext listening **Port already in use** - Kill any stale InjectionNext processes: `pkill -f InjectionNext` - Re-launch the app **Logs are empty** - Logs only capture events that happen while the app is running - Try `watch_project` to generate some activity, then `get_logs` ================================================ FILE: mcp-server/index.js ================================================ #!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import net from "node:net"; const CONTROL_PORT = 8919; const CONTROL_HOST = "127.0.0.1"; function sendCommand(action, params = {}) { return new Promise((resolve, reject) => { const client = new net.Socket(); const timeout = setTimeout(() => { client.destroy(); reject(new Error("Connection timed out. Is InjectionNext running with ControlServer?")); }, 5000); client.connect(CONTROL_PORT, CONTROL_HOST, () => { const payload = JSON.stringify({ action, ...params }) + "\n"; client.write(payload); }); let data = ""; client.on("data", (chunk) => { data += chunk.toString(); if (data.includes("\n")) { clearTimeout(timeout); client.destroy(); try { resolve(JSON.parse(data.trim())); } catch { reject(new Error("Invalid JSON response from InjectionNext")); } } }); client.on("error", (err) => { clearTimeout(timeout); if (err.code === "ECONNREFUSED") { reject(new Error( "Cannot connect to InjectionNext on port 8919. " + "Make sure InjectionNext.app is running (build with ControlServer support)." )); } else { reject(err); } }); }); } function formatResponse(result) { if (!result.success) { return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true }; } const text = result.data ? JSON.stringify(result.data, null, 2) : "OK"; return { content: [{ type: "text", text }] }; } const server = new McpServer({ name: "injection-next", version: "1.0.0", }); server.tool( "get_status", "Get the current status of InjectionNext: Xcode state, watched directories, compiler interception, connected clients, and last error", {}, async () => { const result = await sendCommand("status"); return formatResponse(result); } ); server.tool( "watch_project", "Start file-watching a project directory for hot-reloading. This enables injection for Cursor/VS Code workflows without launching Xcode through the app.", { path: z.string().describe("Absolute path to the project directory to watch") }, async ({ path }) => { const result = await sendCommand("watch_project", { path }); return formatResponse(result); } ); server.tool( "stop_watching", "Stop watching all project directories", {}, async () => { const result = await sendCommand("stop_watching"); return formatResponse(result); } ); server.tool( "launch_xcode", "Launch Xcode through InjectionNext with SOURCEKIT_LOGGING enabled for full injection support", {}, async () => { const result = await sendCommand("launch_xcode"); return formatResponse(result); } ); server.tool( "get_compiler_state", "Check whether the Swift compiler is currently intercepted (patched) by InjectionNext", {}, async () => { const result = await sendCommand("intercept_compiler"); return formatResponse(result); } ); server.tool( "enable_devices", "Enable or disable device injection support (opens TCP port for device/simulator connections)", { enable: z.boolean().describe("true to enable, false to disable") }, async ({ enable }) => { const result = await sendCommand("enable_devices", { enable }); return formatResponse(result); } ); server.tool( "unhide_symbols", "Unhide default-argument symbols in the current build to fix injection loading failures", {}, async () => { const result = await sendCommand("unhide_symbols"); return formatResponse(result); } ); server.tool( "get_last_error", "Get the last compilation error from InjectionNext", {}, async () => { const result = await sendCommand("get_last_error"); return formatResponse(result); } ); server.tool( "prepare_swiftui_source", "Automatically add .enableInjection() and @ObserveInjection to the currently edited SwiftUI source file", {}, async () => { const result = await sendCommand("prepare_swiftui_source"); return formatResponse(result); } ); server.tool( "prepare_swiftui_project", "Prepare all SwiftUI source files in the current target for hot-reloading injection", {}, async () => { const result = await sendCommand("prepare_swiftui_project"); return formatResponse(result); } ); server.tool( "set_xcode_path", "Set the path to the Xcode installation to use", { path: z.string().describe("Absolute path to Xcode.app (e.g. /Applications/Xcode.app)") }, async ({ path }) => { const result = await sendCommand("set_xcode_path", { path }); return formatResponse(result); } ); server.tool( "get_logs", "Read the InjectionNext debug console — returns recent log entries including injection events, compilation output, errors, file watcher activity, and client connections. Use 'since' (unix timestamp) to get only new logs since your last read.", { since: z.number().optional().describe("Unix timestamp — only return logs after this time. Omit to get all recent logs."), limit: z.number().optional().describe("Max entries to return (default 200, max 500)"), }, async ({ since, limit }) => { const params = {}; if (since !== undefined) params.since = since; if (limit !== undefined) params.limit = limit; const result = await sendCommand("get_logs", params); if (!result.success) { return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true }; } const logs = result.data?.logs ?? []; if (logs.length === 0) { return { content: [{ type: "text", text: "No new log entries." }] }; } const lines = logs.map((e) => { const ts = new Date(e.timestamp * 1000).toISOString().slice(11, 23); const tag = e.level !== "info" ? ` [${e.level.toUpperCase()}]` : ""; return `${ts}${tag} ${e.message}`; }); const header = `--- ${logs.length} log entries (${result.data.count} total buffered) ---`; return { content: [{ type: "text", text: header + "\n" + lines.join("\n") }] }; } ); server.tool( "clear_logs", "Clear the InjectionNext log buffer", {}, async () => { const result = await sendCommand("clear_logs"); return formatResponse(result); } ); const transport = new StdioServerTransport(); await server.connect(transport); ================================================ FILE: mcp-server/package.json ================================================ { "name": "injection-next-mcp", "version": "1.0.0", "description": "MCP server for controlling InjectionNext hot-reloading app", "type": "module", "main": "index.js", "engines": { "node": ">=18.14.1" }, "bin": { "injection-next-mcp": "./index.js" }, "scripts": { "start": "node index.js" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", "zod": "^3.23.8" } }