Showing preview only (356K chars total). Download the full file or copy to clipboard to get everything.
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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
================================================
FILE: App/InjectionBundle/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2017 John Holdsworth. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
================================================
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 <Foundation/Foundation.h> // 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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="23504" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23504"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate">
<connections>
<outlet property="codeSignBox" destination="iKZ-P7-T7T" id="KiT-vS-8mL"/>
<outlet property="deviceTesting" destination="SQv-Cq-gPC" id="wzU-n5-tvW"/>
<outlet property="enableDevicesItem" destination="s0A-3D-ThA" id="xl4-eT-1mM"/>
<outlet property="lastErrorField" destination="yfA-aU-ldx" id="oxQ-Xy-ugr"/>
<outlet property="launchXcodeItem" destination="B7f-B2-5ZH" id="7Pb-Cs-Q9n"/>
<outlet property="librariesField" destination="rw0-Sb-xW5" id="iLm-Qn-E1h"/>
<outlet property="patchCompilerItem" destination="cw9-XM-GH8" id="uKH-nc-2sg"/>
<outlet property="restartDeviceItem" destination="6cM-gd-Yoh" id="vaG-Ug-i8f"/>
<outlet property="selectXcodeItem" destination="XlL-4Y-oKk" id="lAe-fa-JsE"/>
<outlet property="statusMenu" destination="V8Q-mq-A2f" id="Epo-HD-J21"/>
<outlet property="watchDirectoryItem" destination="fAp-yw-BqE" id="hwC-GA-RNY"/>
</connections>
</customObject>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="InjectionIII" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="InjectionIII" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About InjectionIII" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Services" id="NMo-om-nkz">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
<menuItem title="Hide InjectionIII" keyEquivalent="h" id="Olw-nP-bQN">
<connections>
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
</connections>
</menuItem>
<menuItem title="Show All" id="Kd2-mp-pUS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
<menuItem title="Quit InjectionIII" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="File" id="dMs-cI-mzQ">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="File" id="bib-Uj-vzu">
<items>
<menuItem title="New" keyEquivalent="n" id="Was-JA-tGl">
<connections>
<action selector="newDocument:" target="-1" id="4Si-XN-c54"/>
</connections>
</menuItem>
<menuItem title="Open…" keyEquivalent="o" id="IAo-SY-fd9">
<connections>
<action selector="openDocument:" target="-1" id="bVn-NM-KNZ"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
<menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
<connections>
<action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/>
</connections>
</menuItem>
<menuItem title="Save…" keyEquivalent="s" id="pxx-59-PXV">
<connections>
<action selector="saveDocument:" target="-1" id="teZ-XB-qJY"/>
</connections>
</menuItem>
<menuItem title="Save As…" keyEquivalent="S" id="Bw7-FT-i3A">
<connections>
<action selector="saveDocumentAs:" target="-1" id="mDf-zr-I0C"/>
</connections>
</menuItem>
<menuItem title="Revert to Saved" keyEquivalent="r" id="KaW-ft-85H">
<connections>
<action selector="revertDocumentToSaved:" target="-1" id="iJ3-Pv-kwq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="aJh-i4-bef"/>
<menuItem title="Page Setup…" keyEquivalent="P" id="qIS-W8-SiK">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="runPageLayout:" target="-1" id="Din-rz-gC5"/>
</connections>
</menuItem>
<menuItem title="Print…" keyEquivalent="p" id="aTl-1u-JFS">
<connections>
<action selector="print:" target="-1" id="qaZ-4w-aoO"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="5QF-Oa-p0T">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items>
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections>
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
<connections>
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
<connections>
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
<connections>
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
<connections>
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
</connections>
</menuItem>
<menuItem title="Delete" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
<menuItem title="Find" id="4EN-yA-p0u">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="1b7-l0-nxx">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
<connections>
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
</connections>
</menuItem>
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
<connections>
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
<connections>
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
<connections>
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
<connections>
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
<items>
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
<connections>
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
</connections>
</menuItem>
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
<connections>
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
</connections>
</menuItem>
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="9ic-FL-obx">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
<items>
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
</connections>
</menuItem>
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
</connections>
</menuItem>
<menuItem title="Smart Links" id="cwL-P1-jid">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
</connections>
</menuItem>
<menuItem title="Data Detectors" id="tRr-pd-1PS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
</connections>
</menuItem>
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Transformations" id="2oI-Rn-ZJC">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
<items>
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
</connections>
</menuItem>
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
</connections>
</menuItem>
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="xrE-MZ-jX0">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
<items>
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Format" id="jxT-CU-nIS">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Format" id="GEO-Iw-cKr">
<items>
<menuItem title="Font" id="Gi5-1S-RQB">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Font" systemMenu="font" id="aXa-aM-Jaq">
<items>
<menuItem title="Show Fonts" keyEquivalent="t" id="Q5e-8K-NDq">
<connections>
<action selector="orderFrontFontPanel:" target="YLy-65-1bz" id="WHr-nq-2xA"/>
</connections>
</menuItem>
<menuItem title="Bold" tag="2" keyEquivalent="b" id="GB9-OM-e27">
<connections>
<action selector="addFontTrait:" target="YLy-65-1bz" id="hqk-hr-sYV"/>
</connections>
</menuItem>
<menuItem title="Italic" tag="1" keyEquivalent="i" id="Vjx-xi-njq">
<connections>
<action selector="addFontTrait:" target="YLy-65-1bz" id="IHV-OB-c03"/>
</connections>
</menuItem>
<menuItem title="Underline" keyEquivalent="u" id="WRG-CD-K1S">
<connections>
<action selector="underline:" target="-1" id="FYS-2b-JAY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="5gT-KC-WSO"/>
<menuItem title="Bigger" tag="3" keyEquivalent="+" id="Ptp-SP-VEL">
<connections>
<action selector="modifyFont:" target="YLy-65-1bz" id="Uc7-di-UnL"/>
</connections>
</menuItem>
<menuItem title="Smaller" tag="4" keyEquivalent="-" id="i1d-Er-qST">
<connections>
<action selector="modifyFont:" target="YLy-65-1bz" id="HcX-Lf-eNd"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kx3-Dk-x3B"/>
<menuItem title="Kern" id="jBQ-r6-VK2">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Kern" id="tlD-Oa-oAM">
<items>
<menuItem title="Use Default" id="GUa-eO-cwY">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardKerning:" target="-1" id="6dk-9l-Ckg"/>
</connections>
</menuItem>
<menuItem title="Use None" id="cDB-IK-hbR">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffKerning:" target="-1" id="U8a-gz-Maa"/>
</connections>
</menuItem>
<menuItem title="Tighten" id="46P-cB-AYj">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="tightenKerning:" target="-1" id="hr7-Nz-8ro"/>
</connections>
</menuItem>
<menuItem title="Loosen" id="ogc-rX-tC1">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="loosenKerning:" target="-1" id="8i4-f9-FKE"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Ligatures" id="o6e-r0-MWq">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Ligatures" id="w0m-vy-SC9">
<items>
<menuItem title="Use Default" id="agt-UL-0e3">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardLigatures:" target="-1" id="7uR-wd-Dx6"/>
</connections>
</menuItem>
<menuItem title="Use None" id="J7y-lM-qPV">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffLigatures:" target="-1" id="iX2-gA-Ilz"/>
</connections>
</menuItem>
<menuItem title="Use All" id="xQD-1f-W4t">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useAllLigatures:" target="-1" id="KcB-kA-TuK"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Baseline" id="OaQ-X3-Vso">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Baseline" id="ijk-EB-dga">
<items>
<menuItem title="Use Default" id="3Om-Ey-2VK">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unscript:" target="-1" id="0vZ-95-Ywn"/>
</connections>
</menuItem>
<menuItem title="Superscript" id="Rqc-34-cIF">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="superscript:" target="-1" id="3qV-fo-wpU"/>
</connections>
</menuItem>
<menuItem title="Subscript" id="I0S-gh-46l">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="subscript:" target="-1" id="Q6W-4W-IGz"/>
</connections>
</menuItem>
<menuItem title="Raise" id="2h7-ER-AoG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="raiseBaseline:" target="-1" id="4sk-31-7Q9"/>
</connections>
</menuItem>
<menuItem title="Lower" id="1tx-W0-xDw">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowerBaseline:" target="-1" id="OF1-bc-KW4"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="Ndw-q3-faq"/>
<menuItem title="Show Colors" keyEquivalent="C" id="bgn-CT-cEk">
<connections>
<action selector="orderFrontColorPanel:" target="-1" id="mSX-Xz-DV3"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="iMs-zA-UFJ"/>
<menuItem title="Copy Style" keyEquivalent="c" id="5Vv-lz-BsD">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="copyFont:" target="-1" id="GJO-xA-L4q"/>
</connections>
</menuItem>
<menuItem title="Paste Style" keyEquivalent="v" id="vKC-jM-MkH">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteFont:" target="-1" id="JfD-CL-leO"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Text" id="Fal-I4-PZk">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Text" id="d9c-me-L2H">
<items>
<menuItem title="Align Left" keyEquivalent="{" id="ZM1-6Q-yy1">
<connections>
<action selector="alignLeft:" target="-1" id="zUv-R1-uAa"/>
</connections>
</menuItem>
<menuItem title="Center" keyEquivalent="|" id="VIY-Ag-zcb">
<connections>
<action selector="alignCenter:" target="-1" id="spX-mk-kcS"/>
</connections>
</menuItem>
<menuItem title="Justify" id="J5U-5w-g23">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="alignJustified:" target="-1" id="ljL-7U-jND"/>
</connections>
</menuItem>
<menuItem title="Align Right" keyEquivalent="}" id="wb2-vD-lq4">
<connections>
<action selector="alignRight:" target="-1" id="r48-bG-YeY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="4s2-GY-VfK"/>
<menuItem title="Writing Direction" id="H1b-Si-o9J">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Writing Direction" id="8mr-sm-Yjd">
<items>
<menuItem title="Paragraph" enabled="NO" id="ZvO-Gk-QUH">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem id="YGs-j5-SAR">
<string key="title"> Default</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionNatural:" target="-1" id="qtV-5e-UBP"/>
</connections>
</menuItem>
<menuItem id="Lbh-J2-qVU">
<string key="title"> Left to Right</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionLeftToRight:" target="-1" id="S0X-9S-QSf"/>
</connections>
</menuItem>
<menuItem id="jFq-tB-4Kx">
<string key="title"> Right to Left</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionRightToLeft:" target="-1" id="5fk-qB-AqJ"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="swp-gr-a21"/>
<menuItem title="Selection" enabled="NO" id="cqv-fj-IhA">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem id="Nop-cj-93Q">
<string key="title"> Default</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionNatural:" target="-1" id="lPI-Se-ZHp"/>
</connections>
</menuItem>
<menuItem id="BgM-ve-c93">
<string key="title"> Left to Right</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionLeftToRight:" target="-1" id="caW-Bv-w94"/>
</connections>
</menuItem>
<menuItem id="RB4-Sm-HuC">
<string key="title"> Right to Left</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionRightToLeft:" target="-1" id="EXD-6r-ZUu"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="fKy-g9-1gm"/>
<menuItem title="Show Ruler" id="vLm-3I-IUL">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleRuler:" target="-1" id="FOx-HJ-KwY"/>
</connections>
</menuItem>
<menuItem title="Copy Ruler" keyEquivalent="c" id="MkV-Pr-PK5">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="copyRuler:" target="-1" id="71i-fW-3W2"/>
</connections>
</menuItem>
<menuItem title="Paste Ruler" keyEquivalent="v" id="LVM-kO-fVI">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="pasteRuler:" target="-1" id="cSh-wd-qM2"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="View" id="H8h-7b-M4v">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="View" id="HyV-fh-RgO">
<items>
<menuItem title="Show Toolbar" keyEquivalent="t" id="snW-S8-Cw5">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="toggleToolbarShown:" target="-1" id="BXY-wc-z0C"/>
</connections>
</menuItem>
<menuItem title="Customize Toolbar…" id="1UK-8n-QPP">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="runToolbarCustomizationPalette:" target="-1" id="pQI-g3-MTW"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="hB3-LF-h0Y"/>
<menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE">
<modifierMask key="keyEquivalentModifierMask" control="YES" option="YES" command="YES"/>
<connections>
<action selector="toggleSourceList:" target="-1" id="iwa-gc-5KM"/>
</connections>
</menuItem>
<menuItem title="Enter Full Screen" keyEquivalent="↩" id="4J7-dP-txa">
<modifierMask key="keyEquivalentModifierMask" control="YES" option="YES" command="YES"/>
<connections>
<action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Help" id="wpr-3q-Mcd">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
<items>
<menuItem title="InjectionIII Help" keyEquivalent="?" id="FKE-Sm-Kum">
<connections>
<action selector="showHelp:" target="-1" id="y7X-2Q-9no"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
<point key="canvasLocation" x="141" y="154"/>
</menu>
<menu autoenablesItems="NO" id="V8Q-mq-A2f">
<items>
<menuItem title="Launch Xcode" toolTip="Luach supervised Xcode" id="B7f-B2-5ZH">
<modifierMask key="keyEquivalentModifierMask"/>
<accessibility description="Launch Xcode"/>
<connections>
<action selector="runXcode:" target="Voe-Tx-rLC" id="HLd-Lq-tj9"/>
</connections>
</menuItem>
<menuItem title="Select Xcode" toolTip="Select Path to desired Xcode" id="XlL-4Y-oKk">
<modifierMask key="keyEquivalentModifierMask"/>
<accessibility description="Select Xcode"/>
<connections>
<action selector="selectXcode:" target="Voe-Tx-rLC" id="rCK-bY-XeP"/>
</connections>
</menuItem>
<menuItem title="Auto Restart Xcode" toolTip="Restart Xcode on crash" id="6cM-gd-Yoh">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="updateXcodeRestart:" target="Voe-Tx-rLC" id="KgB-zd-aHd"/>
</connections>
</menuItem>
<menuItem title="...or Watch Project" toolTip="Start file watcher for project." id="fAp-yw-BqE">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="watchProject:" target="Voe-Tx-rLC" id="8cH-nD-fji"/>
</connections>
</menuItem>
<menuItem title="Intercept Compiler" toolTip="Enable(Disable) interception of swift-frontend commands by replacing it with a script." id="cw9-XM-GH8">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="patchCompiler:" target="Voe-Tx-rLC" id="tSN-cc-ZSG"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="UYl-f8-xeq"/>
<menuItem title="Enable Devices" toolTip="Enable injecting on a device" id="s0A-3D-ThA">
<modifierMask key="keyEquivalentModifierMask"/>
<accessibility description="Evable Devices"/>
<connections>
<action selector="deviceEnable:" target="Voe-Tx-rLC" id="OAF-ab-cCp"/>
</connections>
</menuItem>
<menuItem title="Prepare SwiftUI" id="M7c-Z3-Oni">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Prepare SwiftUI" id="G29-S5-KaB">
<items>
<menuItem title="Single Source" toolTip="Prepare a SwiftUI source for injection." id="92r-y9-TU2">
<modifierMask key="keyEquivalentModifierMask"/>
<accessibility description="Prepare Source"/>
<connections>
<action selector="prepareSource:" target="Voe-Tx-rLC" id="FhK-O3-A0w"/>
</connections>
</menuItem>
<menuItem title="Entire Project" toolTip="Prepare a SwiftUI sources in a target for injection." id="E0g-Zl-9YZ">
<modifierMask key="keyEquivalentModifierMask"/>
<accessibility description="Prepare Project"/>
<connections>
<action selector="prepareProject:" target="Voe-Tx-rLC" id="ork-tg-HB8"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Unhide Symbols" toolTip="Make a pass over object files "unhiding" defualt argument generators" id="FEE-8j-48e">
<modifierMask key="keyEquivalentModifierMask"/>
<accessibility description="Unhide symbols"/>
<connections>
<action selector="unhideSymbols:" target="Voe-Tx-rLC" id="6qL-3s-BHb"/>
</connections>
</menuItem>
<menuItem title="Show Last Error" toolTip="Show the last reported error." id="KKx-re-pmY">
<modifierMask key="keyEquivalentModifierMask"/>
<accessibility description="Show Last Error"/>
<connections>
<action selector="showlastError:" target="Voe-Tx-rLC" id="B1J-ZX-nPf"/>
</connections>
</menuItem>
<menuItem title="Reset Unhiding" hidden="YES" toolTip="Reset internal storage" id="ysc-5y-buR">
<modifierMask key="keyEquivalentModifierMask"/>
<accessibility description="Rest Unhiding"/>
<connections>
<action selector="resetUnhiding:" target="Voe-Tx-rLC" id="wee-I2-883"/>
</connections>
</menuItem>
<menuItem title="Quit InjectionNext" toolTip="Quit InjectionNext" id="LUw-n8-2Gj">
<modifierMask key="keyEquivalentModifierMask"/>
<accessibility description="Quit"/>
<connections>
<action selector="terminate:" target="-3" id="rW8-Je-8vQ"/>
</connections>
</menuItem>
</items>
<point key="canvasLocation" x="3" y="613"/>
</menu>
<userDefaultsController representsSharedInstance="YES" id="mc2-qN-cFK"/>
<window title="Codesigning Identity" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="p7X-fx-AEQ" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="1355" y="786" width="737" height="204"/>
<rect key="screenRect" x="0.0" y="0.0" width="2048" height="1127"/>
<view key="contentView" id="Qow-ez-PwQ">
<rect key="frame" x="0.0" y="0.0" width="737" height="204"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="rw0-Sb-xW5">
<rect key="frame" x="20" y="20" width="697" height="80"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" title="-framework XCTest -lXCTestSwiftSupport" drawsBackground="YES" id="XCE-C6-jqh">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<action selector="updateLibraries:" target="Voe-Tx-rLC" id="Rcu-9B-dga"/>
</connections>
</textField>
<comboBox focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="iKZ-P7-T7T">
<rect key="frame" x="19" y="138" width="701" height="23"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<comboBoxCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" drawsBackground="YES" completes="NO" numberOfVisibleItems="5" id="A2I-vj-Z1G">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<objectValues>
<string>Item 1</string>
<string>Item 2</string>
<string>Item 3</string>
</objectValues>
</comboBoxCell>
</comboBox>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="haQ-Mn-2en">
<rect key="frame" x="18" y="168" width="169" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Select codesigning identity" id="9V5-ch-7QQ">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="SQv-Cq-gPC">
<rect key="frame" x="18" y="107" width="321" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Enable testing on device (paste into build phase)" bezelStyle="regularSquare" imagePosition="left" inset="2" id="WAY-D9-HRe">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="testingEnable:" target="Voe-Tx-rLC" id="SgW-E7-bxy"/>
</connections>
</button>
</subviews>
</view>
<point key="canvasLocation" x="-1244.5" y="757"/>
</window>
<window title="Last Compilation Error" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="HvJ-jZ-ITN" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="1355" y="786" width="688" height="268"/>
<rect key="screenRect" x="0.0" y="0.0" width="2048" height="1127"/>
<view key="contentView" id="kR1-2l-DFV">
<rect key="frame" x="0.0" y="0.0" width="688" height="268"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<scrollView fixedFrame="YES" borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" translatesAutoresizingMaskIntoConstraints="NO" id="959-C8-bvK">
<rect key="frame" x="8" y="10" width="670" height="251"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" drawsBackground="NO" id="Jya-oY-Tsg">
<rect key="frame" x="0.0" y="0.0" width="670" height="251"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textView wantsLayer="YES" editable="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" spellingCorrection="YES" smartInsertDelete="YES" id="yfA-aU-ldx">
<rect key="frame" x="0.0" y="0.0" width="670" height="251"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<size key="minSize" width="670" height="251"/>
<size key="maxSize" width="675" height="10000000"/>
</textView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="xKv-Ny-an6">
<rect key="frame" x="-100" y="-100" width="225" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="cCD-KO-rn7">
<rect key="frame" x="655" y="0.0" width="15" height="251"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
</subviews>
</view>
<point key="canvasLocation" x="69" y="1096"/>
</window>
</objects>
</document>
================================================
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<Int32>.size))
var addr = sockaddr_in()
addr.sin_len = UInt8(MemoryLayout<sockaddr_in>.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<sockaddr_in>.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<sockaddr_in>.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..<n])
if data.contains(UInt8(ascii: "\n")) { break }
if data.count > 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<String> = ["-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<Int>? = 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)+)(?<!#endif\n)\2\}\n
"""#.anchorsMatchLines, count: changes] = """
$2#if DEBUG
$2@ObserveInjection var forceRedraw
$2#endif
$1$2 .enableInjection()
$2}
"""
if changes?.pointee != before {
print("Patched", source)
}
if (patched.contains("class AppDelegate") ||
patched.contains("@main\n")) &&
!patched.contains("InjectionObserver") {
if !patched.contains("import SwiftUI") {
patched += "\nimport SwiftUI\n"
}
patched += """
#if canImport(HotSwiftUI)
@_exported import HotSwiftUI
#elseif canImport(Inject)
@_exported import Inject
#else
// This code can be found in the Swift package:
// https://github.com/johnno1962/HotSwiftUI or
// https://github.com/krzysztofzablocki/Inject
#if DEBUG
import Combine
public class InjectionObserver: ObservableObject {
public static let shared = InjectionObserver()
@Published var injectionNumber = 0
var cancellable: AnyCancellable? = nil
let publisher = PassthroughSubject<Void, Never>()
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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string>App.icns</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>14094</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.developer-tools</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2017-20 John Holdsworth. All rights reserved.</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>
================================================
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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<false/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.print</key>
<false/>
<key>com.apple.security.files.bookmarks.app-scope</key>
<false/>
</dict>
</plist>
================================================
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<String> = [
"-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..<arguments.count {
arguments[i][#"^(\w+)-apple-ios"#] = target
}
}
// Call compiler process with timing
let compilationStartTime = Date.timeIntervalSinceReferenceDate
var env: [String: String]?
if let environment = stored.env {
env = [String: String]()
for (key, value): (String, String) in environment[
#"^(\w+)=(.*)"#.anchorsMatchLines] {
env?[key] = value
}
}
let compile = Topen(exec: compiler,
arguments: arguments + languageSpecific,
cd: stored.workingDir, env: env)
var errors = ""
while let line = compile.readLine() {
if let slow: String = line[Reloader.typeCheckRegex] {
log(slow)
}
errors += line+"\n"
}
if errors.contains(" error: ") {
error("Failed compilation: "+([compiler] + arguments +
languageSpecific).joined(separator: " "))
error("Recompile failed for: \(source)\n"+errors)
Self.lastError = errors
return nil
}
// Log successful compilation with timing
let now = Date.timeIntervalSinceReferenceDate
let compilationTimeMs = (now - compilationStartTime) * 1000
detail(String(format: "⚡ Compiled for \(name) in %.0fms",
compilationTimeMs))
let compilationCommand = (arguments + languageSpecific)
.map { $0[#"([ $()])"#, "\\\\$1"] }.joined(separator: " ")
Reloader.extractLinkCommand(from: compilationCommand)
return object
}
/// Link and object file to create a dynamic library
func link(object: String, dylib: String, arch: String) -> (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 <Cocoa/Cocoa.h>
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 = "<group>"; };
224E57FC2C08BAE200B71C79 /* InjectionClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = InjectionClient.h; path = ../../Sources/InjectionNextC/include/InjectionClient.h; sourceTree = "<group>"; };
224E57FD2C08BBE300B71C79 /* InjectionServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectionServer.swift; sourceTree = "<group>"; };
224E57FF2C08BC0E00B71C79 /* MonitorXcode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonitorXcode.swift; sourceTree = "<group>"; };
22B6459F2C18DD9D00F99B61 /* Unhider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Unhider.swift; path = ../../InjectionLite/Sources/InjectionImpl/Unhider.swift; sourceTree = "<group>"; };
BB037DF31FAD808B004B267C /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
BB037DF41FAD80D0004B267C /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
BB16653925E9A5F0001407AE /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
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 = "<group>"; };
BB42F8D32EB626F100FDBBCC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
BB42F8D42EB626F100FDBBCC /* StringIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringIndex.swift; sourceTree = "<group>"; };
BB42F8D52EB626F100FDBBCC /* SwiftArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftArgs.swift; sourceTree = "<group>"; };
BB42F8D62EB626F100FDBBCC /* SwiftAspects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftAspects.swift; sourceTree = "<group>"; };
BB42F8D72EB626F100FDBBCC /* SwiftInterpose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftInterpose.swift; sourceTree = "<group>"; };
BB42F8D82EB626F100FDBBCC /* SwiftInvoke.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftInvoke.swift; sourceTree = "<group>"; };
BB42F8D92EB626F100FDBBCC /* SwiftLifetime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftLifetime.swift; sourceTree = "<group>"; };
BB42F8DA2EB626F100FDBBCC /* SwiftMeta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftMeta.swift; sourceTree = "<group>"; };
BB42F8DB2EB626F100FDBBCC /* SwiftStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftStack.swift; sourceTree = "<group>"; };
BB42F8DC2EB626F100FDBBCC /* SwiftStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftStats.swift; sourceTree = "<group>"; };
BB42F8DD2EB626F100FDBBCC /* SwiftSwizzle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftSwizzle.swift; sourceTree = "<group>"; };
BB42F8DE2EB626F100FDBBCC /* SwiftTrace.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftTrace.h; sourceTree = "<group>"; };
BB42F8DF2EB626F100FDBBCC /* SwiftTrace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTrace.swift; sourceTree = "<group>"; };
BB42F91A2EB639D100FDBBCC /* Demangling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Demangling.swift; sourceTree = "<group>"; };
BB42F91B2EB639D100FDBBCC /* DLKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DLKit.swift; sourceTree = "<group>"; };
BB42F91C2EB639D100FDBBCC /* FileSymbols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSymbols.swift; sourceTree = "<group>"; };
BB42F91D2EB639D100FDBBCC /* ImageSymbols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageSymbols.swift; sourceTree = "<group>"; };
BB42F91E2EB639D100FDBBCC /* Interposing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interposing.swift; sourceTree = "<group>"; };
BB42F91F2EB639D100FDBBCC /* Iterators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Iterators.swift; sourceTree = "<group>"; };
BB42F9272EB639E500FDBBCC /* DLKitC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DLKitC.h; sourceTree = "<group>"; };
BB42F9292EB639E500FDBBCC /* DLKitC.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = DLKitC.c; sourceTree = "<group>"; };
BB42F92A2EB639E500FDBBCC /* trie_dladdr.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = trie_dladdr.mm; sourceTree = "<group>"; };
BB42F92B2EB639E500FDBBCC /* trie_dlops.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = trie_dlops.mm; sourceTree = "<group>"; };
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 = "<group>"; };
BB42F9322EB63C2900FDBBCC /* Generics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Generics.swift; sourceTree = "<group>"; };
BB42F9332EB63C2900FDBBCC /* KeyPaths.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPaths.swift; sourceTree = "<group>"; };
BB42F9342EB63C2900FDBBCC /* Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Metadata.swift; sourceTree = "<group>"; };
BB42F9352EB63C2900FDBBCC /* Reloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reloader.swift; sourceTree = "<group>"; };
BB42F9362EB63C2900FDBBCC /* Sweeper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sweeper.swift; sourceTree = "<group>"; };
BB42F9402EB63E3400FDBBCC /* FileWatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileWatcher.swift; sourceTree = "<group>"; };
BB42F9412EB63E3400FDBBCC /* InjectionBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectionBase.swift; sourceTree = "<group>"; };
BB42F9422EB63E3400FDBBCC /* InjectionLite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectionLite.swift; sourceTree = "<group>"; };
BB42F9432EB63E3400FDBBCC /* LogParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogParser.swift; sourceTree = "<group>"; };
BB42F9442EB63E3400FDBBCC /* Recompiler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Recompiler.swift; sourceTree = "<group>"; };
BB42F94F2EB63EFF00FDBBCC /* BazelActionQueryHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BazelActionQueryHandler.swift; sourceTree = "<group>"; };
BB42F9502EB63EFF00FDBBCC /* BazelAQueryParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BazelAQueryParser.swift; sourceTree = "<group>"; };
BB42F9512EB63EFF00FDBBCC /* BazelInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BazelInterface.swift; sourceTree = "<group>"; };
BB42F9522EB63EFF00FDBBCC /* FilenameMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilenameMatcher.swift; sourceTree = "<group>"; };
BB42F9532EB63EFF00FDBBCC /* GitIgnoreParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitIgnoreParser.swift; sourceTree = "<group>"; };
BB4654802C257F110080EC40 /* NextCompiler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextCompiler.swift; sourceTree = "<group>"; };
BB47887C2EB6B6E700464AB4 /* InjectionImplC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InjectionImplC.h; sourceTree = "<group>"; };
BB47887E2EB6B6E700464AB4 /* InjectionBoot.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = InjectionBoot.mm; sourceTree = "<group>"; };
BB4788822EB6B81A00464AB4 /* fishhook.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = fishhook.h; sourceTree = "<group>"; };
BB4788832EB6B81A00464AB4 /* SwiftTrace.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftTrace.h; sourceTree = "<group>"; };
BB4788852EB6B81A00464AB4 /* fast_dladdr.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = fast_dladdr.mm; sourceTree = "<group>"; };
BB4788862EB6B81A00464AB4 /* fishhook.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = fishhook.c; sourceTree = "<group>"; };
BB4788872EB6B81A00464AB4 /* ObjCBridge.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ObjCBridge.mm; sourceTree = "<group>"; };
BB4788882EB6B81A00464AB4 /* SwiftTrace.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SwiftTrace.mm; sourceTree = "<group>"; };
BB4788892EB6B81A00464AB4 /* SwiftTrace-Swift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SwiftTrace-Swift.h"; sourceTree = "<group>"; };
BB47888A2EB6B81A00464AB4 /* Trampolines.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Trampolines.mm;
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
SYMBOL INDEX (7 symbols across 3 files)
FILE: Sources/InjectionNextC/include/InjectionClient.h
function end (line 33) | end
FILE: Sources/InjectionNextC/include/SimpleSocket.h
function interface (line 14) | interface SimpleSocket : NSObject {
type sockaddr_storage (line 27) | struct sockaddr_storage
FILE: mcp-server/index.js
constant CONTROL_PORT (line 8) | const CONTROL_PORT = 8919;
constant CONTROL_HOST (line 9) | const CONTROL_HOST = "127.0.0.1";
function sendCommand (line 11) | function sendCommand(action, params = {}) {
function formatResponse (line 52) | function formatResponse(result) {
Condensed preview — 55 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (367K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 644,
"preview": "# These are supported funding model platforms\n\ngithub: johnno1962\npatreon: # Replace with a single Patreon username\nopen"
},
{
"path": ".gitignore",
"chars": 2169,
"preview": "# Xcode\n#\n# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore\n\n"
},
{
"path": ".gitmodules",
"chars": 451,
"preview": "[submodule \"fishhook\"]\n\tpath = fishhook\n\turl = https://github.com/johnno1962/fishhook\n[submodule \"DLKit\"]\n\tpath = DLKit\n"
},
{
"path": ".swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "App/InjectionBundle/Info.plist",
"chars": 859,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "App/InjectionBundle/InjectionBundle-Bridging-Header.h",
"chars": 324,
"preview": "//\n// Use this file to import your target's public headers that you would like to expose to Swift.\n//\n\n#import <Foundat"
},
{
"path": "App/InjectionNext/AppDelegate.swift",
"chars": 10662,
"preview": "//\n// AppDelegate.swift\n// InjectionNext\n//\n// Created by John Holdsworth on 06/11/2017.\n// Copyright © 2017 John Ho"
},
{
"path": "App/InjectionNext/Assets.xcassets/AppIcon.appiconset/Contents.json",
"chars": 903,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"mac\",\n \"size\" : \"16x16\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : "
},
{
"path": "App/InjectionNext/Base.lproj/MainMenu.xib",
"chars": 62964,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion"
},
{
"path": "App/InjectionNext/ControlServer.swift",
"chars": 11803,
"preview": "//\n// ControlServer.swift\n// InjectionNext\n//\n// Local TCP control server for MCP integration.\n// Listens on localho"
},
{
"path": "App/InjectionNext/Defaults.swift",
"chars": 2024,
"preview": "//\n// Defaults.swift\n// InjectionNext\n//\n// Created by John Holdsworth on 24/07/2024.\n// Copyright © 2024 John Holds"
},
{
"path": "App/InjectionNext/Experimental.swift",
"chars": 1421,
"preview": "//\n// Experimental.swift\n// InjectionIII\n//\n// Created by User on 20/10/2020.\n// Copyright © 2020 John Holdsworth. A"
},
{
"path": "App/InjectionNext/FrontendServer.swift",
"chars": 18031,
"preview": "//\n// FrontendServer.swift\n// InjectionNext\n//\n// Created by John Holdsworth on 23/02/2025.\n// Copyright © 2025 John"
},
{
"path": "App/InjectionNext/Info.plist",
"chars": 1211,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "App/InjectionNext/InjectionHybrid.swift",
"chars": 6830,
"preview": "//\n// InjectionHybrid.swift\n// InjectionNext\n//\n// Created by John Holdsworth on 09/11/2024.\n// Copyright © 2024 Joh"
},
{
"path": "App/InjectionNext/InjectionNext-Bridging-Header.h",
"chars": 219,
"preview": "//\n// Use this file to import your target's public headers that you would like to expose to Swift.\n//\n\n#import \"SimpleS"
},
{
"path": "App/InjectionNext/InjectionNext.entitlements",
"chars": 480,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "App/InjectionNext/InjectionServer.swift",
"chars": 9677,
"preview": "//\n// InjectionServer.swift\n// InjectionNext\n//\n// Created by John H on 30/05/2024.\n// Copyright © 2024 John Holdswo"
},
{
"path": "App/InjectionNext/MonitorXcode.swift",
"chars": 8479,
"preview": "//\n// RunXcode.swift\n// InjectionNext\n//\n// Created by John H on 30/05/2024.\n// Copyright © 2024 John Holdsworth. Al"
},
{
"path": "App/InjectionNext/NextCompiler.swift",
"chars": 18723,
"preview": "//\n// Recompiler.swift\n// InjectionNext\n//\n// Created by John Holdsworth on 21/06/2024.\n// Copyright © 2024 John Hol"
},
{
"path": "App/InjectionNext/build_bundle.sh",
"chars": 2116,
"preview": "#!/bin/sh -x\n\n# build_bundle.sh\n# InjectionNext\n#\n# Created by John Holdsworth on 22/07/2024.\n# Copyright © 2024 Joh"
},
{
"path": "App/InjectionNext/build_bundles.sh",
"chars": 2850,
"preview": "#!/bin/sh\n\n# build_bundles.sh\n# InjectionNext\n#\n# Created by John Holdsworth on 22/07/2024.\n# Copyright © 2024 John "
},
{
"path": "App/InjectionNext/copy_bundle.sh",
"chars": 5127,
"preview": "#!/bin/bash -x\n#\n# copy_bundle.sh\n# InjectionIII\n#\n# Copies injection bundle for on-device injection.\n# Thanks @oryo"
},
{
"path": "App/InjectionNext/main.m",
"chars": 254,
"preview": "//\n// main.m\n// InjectionIII\n//\n// Created by John Holdsworth on 06/11/2017.\n// Copyright © 2017 John Holdsworth. Al"
},
{
"path": "App/InjectionNext/swift-frontend.sh",
"chars": 373,
"preview": "#!/bin/bash\n\n# swift-frontend.sh\n# InjectionNext\n#\n# Created by John Holdsworth on 23/02/2025.\n# Copyright © 2025 Jo"
},
{
"path": "App/InjectionNext.xcodeproj/project.pbxproj",
"chars": 78993,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 70;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "App/InjectionNext.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "App/InjectionNext.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "App/InjectionNext.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
"chars": 181,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "App/InjectionNext.xcodeproj/xcshareddata/xcschemes/InjectionNext.xcscheme",
"chars": 3248,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1520\"\n version = \"1.7\">\n <BuildAction\n "
},
{
"path": "App/LICENSE",
"chars": 1073,
"preview": "MIT License\n\nCopyright (c) 2024 John Holdsworth \n\nPermission is hereby granted, free of charge, to any person obtaining "
},
{
"path": "App/NIMBLE.md",
"chars": 11307,
"preview": "Apache License\n Version 2.0, January 2004\n http://www.apache.org/licens"
},
{
"path": "App/QUICK.md",
"chars": 11308,
"preview": "Apache License\n Version 2.0, January 2004\n http://www.apache.org/licens"
},
{
"path": "App/feedcommands/main.mm",
"chars": 1325,
"preview": "//\n// main.mm\n// feedcommands\n//\n// Created by John Holdsworth on 23/02/2025.\n// Copyright © 2025 John Holdsworth. A"
},
{
"path": "BAZEL.md",
"chars": 1871,
"preview": "## Bazel Support\n\nThis version includes enhanced Bazel build system support with automatic target discovery and optimize"
},
{
"path": "INTRO.md",
"chars": 3990,
"preview": "\n## A lightning introduction to Injection\n\nAs it says in the README.md, using a feature of Apple's linker Code Injection"
},
{
"path": "LICENSE",
"chars": 1072,
"preview": "MIT License\n\nCopyright (c) 2024 John Holdsworth\n\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "Package.swift",
"chars": 1296,
"preview": "// swift-tools-version: 5.4\n// The swift-tools-version declares the minimum version of Swift required to build this pack"
},
{
"path": "README.md",
"chars": 9749,
"preview": "# InjectionNext\n\n### The fourth evolution of Code Injection for Xcode\n\nUsing a feature of Apple's linker this implementa"
},
{
"path": "Sources/InjectionNext/InjectionNext.swift",
"chars": 12342,
"preview": "//\n// InjectionNext.swift\n// InjectionNext Package\n//\n// Created by John Holdsworth on 30/05/2024.\n//\n// Client app "
},
{
"path": "Sources/InjectionNextC/ClientBoot.mm",
"chars": 2589,
"preview": "//\n// ClientBoot.m\n// \n//\n// Created by John H on 31/05/2024.\n//\n\n#if DEBUG || !SWIFT_PACKAGE\n#import <Foundation/Fou"
},
{
"path": "Sources/InjectionNextC/SimpleSocket.mm",
"chars": 20041,
"preview": "//\n// SimpleSocket.mm\n// InjectionIII\n//\n// Created by John Holdsworth on 06/11/2017.\n// Copyright © 2017 John Holds"
},
{
"path": "Sources/InjectionNextC/include/InjectionClient.h",
"chars": 1625,
"preview": "//\n// InjectionClient.h\n// InjectionBundle\n//\n// Created by John Holdsworth on 06/11/2017.\n// Copyright © 2017 John "
},
{
"path": "Sources/InjectionNextC/include/SimpleSocket.h",
"chars": 2053,
"preview": "//\n// SimpleSocket.h\n// InjectionIII\n//\n// Created by John Holdsworth on 06/11/2017.\n// Copyright © 2017 John Holdsw"
},
{
"path": "Tests/InjectionNextTests/InjectionNextTests.swift",
"chars": 371,
"preview": "import XCTest\n@testable import InjectionBundle\n\nfinal class InjectionNextTests: XCTestCase {\n func testExample() thro"
},
{
"path": "mcp-server/.gitignore",
"chars": 14,
"preview": "node_modules/\n"
},
{
"path": "mcp-server/README.md",
"chars": 5320,
"preview": "# InjectionNext MCP Server\n\nMCP (Model Context Protocol) server that lets AI agents control InjectionNext — start file w"
},
{
"path": "mcp-server/index.js",
"chars": 6521,
"preview": "#!/usr/bin/env node\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport "
},
{
"path": "mcp-server/package.json",
"chars": 416,
"preview": "{\n \"name\": \"injection-next-mcp\",\n \"version\": \"1.0.0\",\n \"description\": \"MCP server for controlling InjectionNext hot-r"
}
]
// ... and 6 more files (download for full content)
About this extraction
This page contains the full source code of the johnno1962/InjectionNext GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 55 files (337.7 KB), approximately 85.3k tokens, and a symbol index with 7 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.